WIN32汇编语言教程:第14章 异常处理 · 14.2 使用筛选器处理异常(1)
Windows下的异常处理可以有两种方式:筛选器异常处理和SEH异常处理。
筛选器异常处理的方式是由程序指定一个异常处理回调函数(在下面将统一简称为“回调函数”),当发生异常的时候,系统将调用这个回调函数,并根据回调函数的返回值决定如何进行下一步操作,这种方法和DOS系统中使用INT 24h中断来处理异常的方法是很像的。
在进程范围内,筛选器异常处理回调函数是惟一的,设置了一个新的回调函数后,原来的就失效了。
14.2.1 注册回调函数
可以使用SetUnhandledExceptionFilter函数来设置一个筛选器异常处理回调函数,准确地讲,这个回调函数不是替换了系统默认的异常处理程序,而是在它前面进行了一些预处理,操作的结果还是会被送到系统默认的异常处理程序中去,这个过程就相当于对异常进行一次“筛选”,这正是函数名中“Filter”一词的含义。
SetUnhandledExceptionFilter函数的使用方法是:
invoke SetUnhandledExceptionFilter,offset _Handler
mov lpPrevHandler,eax
函数的惟一参数是回调函数的地址,如果地址参数被指定为NULL的话,那么系统将去掉这个“筛子”而直接将异常送往系统默认的异常处理程序。函数的返回值是上一次设置的回调函数的入口地址,如果原来没有安装“筛子”,那么返回值将为NULL。
如果原来已经设置了一个回调函数的话,那么新的回调函数将换掉原来的回调函数,注意:不是在原先的回调函数前面再挂上一个新的回调函数。也就是说,当异常发生的时候,系统调用新的回调函数,在这个函数返回的时候并不会再去调用上一次设置的回调函数。一个形象的比喻就是Windows系统不会用两层筛子去筛东西。
本书所附光盘的Chapter14\TopHandler目录中有一个简单的筛选器异常处理的例子,其中的汇编源代码如下:
.386
.model flat,stdcall
option casemap:none
;####################################################################
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;####################################################################
; 数据段
;####################################################################
.data
lpOldHandler dd ?
.const
szMsg db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szSafe db '回到了安全的地方!',0
szCaption db '筛选器异常处理的例子',0
.code
;####################################################################
; Exception Handler 异常处理程序
;####################################################################
_Handler proc _lpExceptionPoint
local @szBuffer[256]:byte
pushad
mov esi,_lpExceptionPoint
assume esi:ptr EXCEPTION_POINTERS
mov edi,[esi].ContextRecord
mov esi,[esi].pExceptionRecord
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,EXCEPTION_CONTINUE_EXECUTION
ret
_Handler endp
;####################################################################
start:
invoke SetUnhandledExceptionFilter,addr _Handler
mov lpOldHandler,eax
;********************************************************************
; 会引发异常的指令
;********************************************************************
xor eax,eax
mov dword ptr [eax],0 ;产生异常,然后_Handler被调用
; ...
; 如果这中间有指令,这些指令将不会被执行!
; ...
_SafePlace:
invoke MessageBox,NULL,addr szSafe,addr szCaption,MB_OK
invoke SetUnhandledExceptionFilter,lpOldHandler
invoke ExitProcess,NULL
;####################################################################
end start
程序的入口处使用SetUnhandledExceptionFilter函数将_Handler子程序指定为异常处理回调函数,函数返回的原回调函数地址被保存到lpOldHandler变量中(当然,在这个例子中,这个值肯定为0,程序中进行这一步操作是为了演示保存和恢复的方法),在程序退出之前会再次使用SetUnhandledExceptionFilter函数将这个地址设置回去。
在设置好回调函数后,程序人为地产生一个读异常:在xor eax,eax指令将eax清零以后,mov dword ptr [eax],0指令将导致读写0地址处的内存,而这是不允许的,所以这条指令执行时会产生一个异常,系统将捕获到它并调用程序设置的_Handler子程序来进行预处理。在_Handler子程序中,将跳过产生异常的指令并将程序转移到由_SafePlace标号指定的“安全”位置去执行。
14.2.2 异常处理回调函数
筛选器异常处理回调函数的格式如下所示:
_Handler proc l pExceptionInfo
回调函数带一个参数,这个参数是个指针,指向一个包含所发生异常详细信息的EXCEPTION_POINTERS结构,结构定义如下:
EXCEPTION_POINTERS STRUCT
pExceptionRecord DWORD ?
ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
要处理一个异常,必须详细了解这个异常的各种信息,EXCEPTION_POINTERS结构中包含的正是这些内容,其中的pExceptionRecord字段指向一个EXCEPTION_RECORD结构,这个结构中包含了异常产生的原因、产生的位置等情况,而ContextRecord字段指向一个CONTEXT结构,结构中记录了异常产生时刻的运行环境。这两个结构的定义在13.3.3小节中介绍调试API的时候介绍过。
上页:第14章 异常处理 · 14.1 异常处理的用途 下页:第14章 异常处理 · 14.2 使用筛选器处理异常(2)