WIN32汇编语言教程:第14章 异常处理 · 14.3 使用SEH处理异常(1)
使用筛选器异常处理程序是最简单的处理异常的方法,但在使用中也存在一些不便之处,最明显的就是不便于模块的封装:由于筛选器异常处理程序是全局性的,无法为一个线程或一个子程序单独设置一个异常处理回调函数,这样就无法将私有的异常处理代码封装进某个模块中。
Windows系统中还提供了另一种在每个线程之间独立的异常处理方法——结构化异常处理(Structured Exception Handling,简称SEH),SEH是Win32系统中为数不多的应用广泛却又未被公开的特征之一。
SEH和筛选器异常处理之间有一些共同点:首先是两者的异常处理程序都是以回调函数的方式提供的;另外,系统都会根据回调函数的返回值选择不同的操作。
但是它们之间也存在很多的不同点:
● 使用SEH可以为每个线程设置不同的异常处理程序,而且可以为每个线程设置多个异常处理程序。
● 两者的回调函数的参数定义和返回值的定义都是不同的。
● SEH使用了与硬件平台相关的数据指针,所以在不同硬件平台中使用SEH的方法会有所不同(也许这正是SEH未被Microsoft公开的原因)。
接下来首先看一个使用SEH处理异常的例子,这个例子与前面的例子很相似,都是在回调函数中显示异常代码和发生异常的位置,并将程序修正到_SafePlace标号去执行。例子的源代码可以在本书所附光盘的Chapter14\SEH01目录中找到,其中的SEH.asm的内容如下:
.386
.model flat,stdcall
option casemap:none
;####################################################################
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;####################################################################
; 数据段
;####################################################################
.const
szMsg db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szSafe db '回到了安全的地方!',0
szCaption db 'SEH例子',0
.code
;####################################################################
; SEH Handler 异常处理程序
;####################################################################
_Handler proc _lpExceptionRecord,_lpSEH,\
_lpContext,_lpDispatcherContext
local @szBuffer[256]:byte
pushad
mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
invoke wsprintf,addr @szBuffer,addr szMsg,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
mov [edi].regEip,offset _SafePlace
assume esi:nothing,edi:nothing
popad
mov eax,ExceptionContinueExecution
ret
_Handler endp
;####################################################################
start:
;********************************************************************
; 在堆栈中构造一个 EXCEPTION_REGISTRATION 结构
;********************************************************************
assume fs:nothing
push offset _Handler
push fs:[0]
mov fs:[0],esp
;********************************************************************
; 会引发异常的指令
;********************************************************************
xor eax,eax
mov dword ptr [eax],0 ;产生异常,然后_Handler被调用
; ...
; 如果这中间有指令,这些指令将不会被执行!
; ...
_SafePlace:
invoke MessageBox,NULL,addr szSafe,addr szCaption,MB_OK
;********************************************************************
; 恢复原来的 SEH 链
;********************************************************************
pop fs:[0]
pop eax
invoke ExitProcess,NULL
;####################################################################
end start
14.3.1 注册回调函数
在例子程序中,SEH异常处理回调函数的设置由下面3条指令完成:
push offset _Handler
push fs:[0]
mov fs:[0],esp
为什么这3句简单的指令就可以完成设置工作,为什么又要使用fs段选择器呢?这要从线程信息块(Thread Information Block/TIB)说起。
Win32为每个线程定义了一个线程信息块,其中保存了线程的一些属性数据,线程信息块的格式被定义为NT_TIB结构:
NT_TIB STRUCT
ExceptionList dd ? ;SEH链入口
StackBase dd ? ;堆栈基址
StackLimit dd ? ;堆栈大小
SubSystemTib dd ?
FiberData dd ?
ArbitraryUserPointer dd ?
Self dd ? ;本NT_TIB结构自身的线性地址
NT_TIB ENDS
NT_TIB结构的第一个字段ExceptionList指向一个EXCEPTION_REGISTRATION结构,SEH异常处理回调函数的入口地址就是由EXCEPTION_REGISTRATION结构指定的,这个结构的定义如下:
EXCEPTION_REGISTRATION STRUCT
prev dd ? ;前一个EXCEPTION_REGISTRATION结构的地址
handler dd ? ;异常处理回调函数地址
EXCEPTION_REGISTRATION ENDS
当异常发生时,系统从TIB中取出ExceptionList字段,然后从ExceptionList字段指定的EXCEPTION_REGISTRATION结构中取出handler字段,并根据其中的地址去调用回调函数,整个过程如图14.1所示,所以只要构建一个含有回调函数地址的EXCEPTION_REGISTRATION结构,然后修改TIB中的ExceptionList字段,指向这个结构就可以注册一个SEH异常处理回调函数。
图14.1 SEH异常处理程序入口地址的定义
上页:第14章 异常处理 · 14.2 使用筛选器处理异常(2) 下页:第14章 异常处理 · 14.3 使用SEH处理异常(2)