每次我们定义了一个新的SEH异常处理回调函数,EXCEPTION_REGISTRATION结构的prev字段都被要求填写上一个EXCEPTION_REGISTRATION结构的地址,随着应用程序对模块的调用一层层深入下去的时候,那么最后回调函数会形成一个SEH链
当程序中有多个线程在运行的时候,每个线程中都会存在各自的SEH链,这些SEH链中指定了多个回调函数,除他们以外,系统中可能还会存在一个全局性的筛选器,再者如果进程被调试,调试器进程也相当于一个异常处理的程序存在.那么当一个异常发生的时候,系统究竟该听谁的呢?
在这种情况下,系统按一定的步骤选择一个回调函数并执行他,如果这个回调函数可以执行这个异常,那么其他的回调函数都不会执行,否则系统执行下一个回调函数
(1).系统查看产生异常的进程是否正在被调试,如果正在被调试的话,那么向调试器发送EXCEPTION_DEBUG_EVENT事件
(2).如果进程没有被调试或者调试器不去处理这个异常,那么系统 检查异常所处的线程,并在这个线程环境中查看fs:[0]来确定是否安装有SEH异常处理回调函数,如果有则调用它.
(3).回调函数尝试处理这个异常,如果可以正确处理的话,则修正错误并将返回值设置为ExceptionContinueExecution,这时系统将结束整个查找过程
(4).如果回调函数返回ExceptionContinueSearch,告知系统他无法处理这个异常,那么系统将根据SEH链中的prev字段得到上一个回调函数地址,并重复步骤(3)过程,直到链中某个回调函数返回ExceptionContinueExecution为止,查找结束
(5).如果到了SEH链的尾部,却没有一个回调函数愿意处理这个异常,那么系统将再次检测进程是否正在被调试,如果被调试的话,则再次通知调试器
(6).如果调试器还是不去处理这个异常或者进程没有被调试 ,那么系统检查有没有安装筛选器回调函数,如果有,则去调用他,筛选器回调函数返回的时候,系统默认的异常处理程序根据这个返回值将做出相应的动作
(7).如果没有安装筛选器回调函数,系统直接 调用默认的异常处理程序终止进程
一个 比较形象的比喻就是:
Windows拿着一份异常处理的活挨个问每个回调函数
"你干不干" ,"不干","你呢","我也不干"... ... 当问到某一个的时候,
他说:"那我来干好了!"那么Windows就不会在去问其他人了,于是相安无事
有时,问完一圈后,谁都不愿意干(当然是干不了)Windows大怒:"谁都不干,看我炒了你们!"于是就把整个进程终止掉,所有的异常处理回调函数全部完蛋啦
(筛选器-全局的-进程的 SEH-线程的)
完整的异常处理回调函数
<span style="font-family:Microsoft YaHei;font-size:13px;">_Handler1 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext;C调用方式-调用者自己平衡堆栈.if (异常代码 == 0c0000027h) || (异常标志 & EXCEPTION_UNWINDING) || (异常标志 & EXCEPTION_UNWINDING_FOR_EXIT);进行资源释放等扫尾工作mov eax,ExceptionContinueSearch.elseif 异常代码 == 可以处理的异常代码;处理异常,对CONTEXT进行修正;进行展开操作mov eax,ExceptionContinueExecution.else;其他无法处理的异常代码mov eax,ExceptionContinueSearch.endifret_Handler1 endp</span>
二.展开操作(Unwinding)
如果把第一个SEH处理的函数的返回值改成ExceptionContinueSearch这个时候函数将会进行循环查找
这个时候回调函数应该进行一些扫尾工作,因为其将被卸载
注册 Unwinding操作可以使用RtlUnwinding函数
invoke RtlUnwinding,lpLastStackFrame,lpCodeLabel,lpExceptionRecord,dwRet
(1).lpLastStackFrame:这个参数设置为NULL,那么他将对所有的SEH链进行展开操作,这时所有的回调函数参数中的异常标志在带有EXCEPTION_UNWINDING的同时也带有EXCEPTION_UNWINDING_FOR_EXIT标志位,这种方式称为展开退出
指定为当前回调函数的EXCEPTION_REGISTRATION结构的地址的话,表示对当前回调函数之后的所有其他回调函数进行展开操作,这样RtlUnwinding函数调用的每一个回调函数时,异常标记位都会带有EXCEPTION_UNWINDING标记位
(2).lpCodeLabel参数指明函数将要返回的位置,如果位NULL,那么RtlUnwinding函数将返回到其后面的一条指令,否则函数直接返回到lpCodeLabel指定的位置
(3).lpExceptionRecord指定一个EXCEPTION_RECORD结构,这个结构将在调用的时候传给每一个回调函数
(4).dwRet参数一般不被使用,它可以指明为NULL
使用RtlUnwinding函数时要注意的是:这个函数并不像其他API函数一样保存esi,edi和ebx寄存器的值,在函数返回时候这些寄存器的值可能会改变,所以如果程序用到这些寄存器的话,必须手动去保存和恢复他们
<span style="font-family:Microsoft YaHei;font-size:13px;"> .386.model flat,stdcalloption casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libL macro var:VARARGLOCAL @lbl.const@lbl db var,0.codeexitm <offset @lbl>
endm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.data
szMsg1 db '这是外层异常处理程序(将处理异常)',0dh,0ahdb '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szMsg2 db '这是内层异常处理程序(对异常不进行处理)',0dh,0ahdb '异常发生位置:%08X,异常代码:%08X,标志:%08X',0
szCaption db '提示信息',0
szBeforeUnwind db '现在将开始 Unwind,当前的 FS:[0] = %08X',0
szAfterUnwind db 'Unwind 返回,当前的 FS:[0] = %08X',0
szSafe1 db '回到了外层子程序的安全位置!',0
szSafe2 db '回到了内层子程序的安全位置!',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 外层错误 Handler,将处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler1 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContextlocal @szBuffer[256]:bytepushadmov esi,_lpExceptionRecordmov edi,_lpContextassume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT,fs:nothinginvoke wsprintf,addr @szBuffer,addr szMsg1,\[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlagsinvoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
;********************************************************************
; 将 EIP 指向安全的位置并恢复堆栈
;********************************************************************mov eax,_lpSEHpush [eax + 8]pop [edi].regEippush _lpSEHpop [edi].regEsp
;********************************************************************
; 对前面的 Handler 进行 Unwind 操作
;********************************************************************invoke wsprintf,addr @szBuffer,addr szBeforeUnwind,dword ptr fs:[0]invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OKinvoke RtlUnwind,_lpSEH,NULL,NULL,NULLinvoke wsprintf,addr @szBuffer,addr szAfterUnwind,dword ptr fs:[0]invoke MessageBox,NULL,addr @szBuffer,addr szCaption,MB_OK
;********************************************************************assume esi:nothing,edi:nothingpopadmov eax,ExceptionContinueExecutionret_Handler1 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 内层错误 Handler,不处理异常
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Handler2 proc C _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContextlocal @szBuffer[256]:bytepushadmov esi,_lpExceptionRecordmov edi,_lpContextassume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXTinvoke wsprintf,addr @szBuffer,addr szMsg2,\[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlagsinvoke MessageBox,NULL,addr @szBuffer,NULL,MB_OKassume esi:nothing,edi:nothingpopadmov eax,ExceptionContinueSearchret_Handler2 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test2 procassume fs:nothingpush offset _SafePlacepush offset _Handler2push fs:[0]mov fs:[0],esp
;********************************************************************
; 会引发异常的指令
;********************************************************************pushadxor eax,eaxmov dword ptr [eax],0popad ;这一句将无法被执行
_SafePlace:invoke MessageBox,NULL,L("回到了内层子程序的安全位置!"),L("提示信息"),MB_OKpop fs:[0]add esp,8ret_Test2 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Test1 procassume fs:nothingpush offset _SafePlacepush offset _Handler1push fs:[0]mov fs:[0],espinvoke _Test2
_SafePlace:invoke MessageBox,NULL,L("回到了外层子程序的安全位置!"),L("提示信息"),MB_OKpop fs:[0]add esp,8ret_Test1 endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:invoke _Test1invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>end start
</span>