通过前面的例子我们可以看出,所谓异常处理就是在异常处理函数中分析系统传递给它的参数,根据其中 的信息做出相应的反应,是不是和消息循环很像呢?确实很像,因为它们都是 M$ 制造。下面开始看一下线程 相关异常处理。 线程相关异常处理只会对指定的线程进行监视。我仍旧按照前面的模式来讲述它的实现。同 样,线程相关的 SEH 也需要设置一个回调函数,但设置方法却与进程相关 SEH 截然不同,在这里,API SetUnhandledExceptionFilter()已经用不上了,具体的实现过程还得从线程的初始化说起,线程初始化时,系 统会为线程设置一个TIB(Thread Infomation Block)结构,这个结构有点复杂,我们没必要深究,SEH用到的只 是它的第一个字段 EXCEPTION_REGISTRATION *,这个字段又是一个结构,它的定义如下: typedef struct _EXCEPTION_REGISTRATION{ struct _EXCEPTION_REGISTRATION * ddPrev; PROC ddHandler; }EXCEPTION_REGISTRATION;
简单解释一下: struct _EXCEPTION_REGISTRATION * ddPrev 指向本结构的一个指针,很显然,由于它的存在,很容易就可以实现链表。 PROC ddHandler 一个函数指针,这就是我们需要的,用它来指定异常处理函数。 事实上,它就是一个链表,系统在初始化线程时,已经为线程设置了默认的异常处理函数,如果我们想把 它替换成我们自己的函数,只需要在链表上添加一个节点,形成一个函数链就可以了,这是不是有点像 Windows 的HOOK?一母所生怎会不像?再有一点需要说明的是,线程初始化时,FS 寄存器就指向 TIB,这 么一来,我们的处理方法就呼之欲出了!伪代码表示就是: EXCEPTION_REGISTRATION myExp; myExp.ddPrev = FS[0]; myExp.ddHandler = ExceptionFilterProc; FS[0] = &myExp; 接下来,就是编写异常处理函数了,先看一下该函数的定义。 long __cdecl ExceptionFilter( EXCEPTION_RECORD * lpRecord, EXCEPTION_REGISTRATION * lpRegist, CONTEXT * lpContext, DWORD * dwParam); 和进程相关的异常处理函数稍微有点不同,返回值还是long,调用规则变成了__cdecl, 参数变成了四个。 参数说明: 第一、第三两个参数就是将进程相关异常处理函数的参数分开来传; 第二个参数就是FS[0]了,一般我们不用理会; 第四个参数用途不明,也不去理会它就是了。
返回值说明: 这里的返回值只有两个。 EXCEPTION_CONTINUE_SEARCH = 1 不处理异常,转交系统处理 EXCEPTION_CONTINUE_EXECUTION = 0 修复错误,从异常发生处继续执行 总结一下线程相关异常处理函数的简要流程 C/C++ 写法: long __cdecl ExceptionFilter( EXCEPTION_RECORD * lpRecord, EXCEPTION_REGISTRATION * lpRegist, CONTEXT * lpContext, DWORD * dwParam) { . . . return 0;//(or 1) } ASM 写法 ExceptionFilter PROC ;取得参数 EXCEPTION_RECORD * MOV ESI,[ESP + 4] ;取得参数 CONTEXT * MOV EDI,[ESP + 12] ;异常处理 . . . ;设置返回值 = 0 OR 1 MOV EAX,return_value ;注意 __cdecl规则调用,返回时不用做堆栈修正 RET ExceptionFilter ENDP 为了突出重点,接下来的例程相对来说简单一点,仅仅设置了一个非法除0错误,处理方式和前一个例程基本一 致。 ;*************************************************** ;线程相关异常处理实例 ;***************************************************
.386 .MODEL FLAT include ..\INCLUDE\PERELATION.INC EXTRN MessageBoxA:PROC EXTRN ExitProcess:PROC .Data szTitle DB "标题",0 szMessage DB "应用程序发生除 0 错误,是否修复?",0 .Code _Header: ;这是一条伪指令,编译器要求。 ASSUME FS:NOTHING PUSH EBP ;将 EXCEPTION_REGISTRATION 定义在栈中 ;用栈挂接异常处理函数,当然也可以用静态变量 PUSH OFFSET _ExceptionFilter PUSH DWORD PTR FS:[0] MOV FS:[0],ESP
;触发除 0 异常 XOR EBX,EBX DIV BL ;清除 SEH 节点,恢复堆栈 POP DWORD PTR FS:[0] ADD ESP,4 POP EBP PUSH 0 CALL ExitProcess ;异常处理函数 _ExceptionFilter PROC MOV EAX,ESP PUSHAD ;注意堆栈状态 ;[ESP + 16] DWOR * ;[ESP + 12] CONTEXT * ;[ESP + 8] EXCEPTION_REGISTRATION * ;[ESP + 4] EXCEPTION_RECORD * ;[ESP] Return address ;取得参数EXCEPTION_RECORD MOV ESI,[EAX + 4] ;取得参数CONTEXT MOV EDI,[EAX + 12] ;分析异常代码 MOV EAX,[ESI].ExceptionCode ;是否除0异常 CMP EAX,0C0000094H JE _IsDivZero ;其他异常则转交系统处理 JMP _ExceptOther ;除0异常处理 _IsDivZero: ;询问一下是否修复 PUSH MB_YESNO PUSH OFFSET szTitle PUSH OFFSET szMessage PUSH NULL CALL MessageBoxA ;选择“NO”则不修复,转交系统处理 CMP EAX,IDNO JE _ExceptOther ;改变 EBX 的值 INC [EDI].C_Ebx POPAD ;返回值 = 0,异常已修复,继续执行 XOR EAX,EAX RET _ExceptOther: POPAD ;返回值 = 1,不处理异常,转交系统处理 XOR EAX,EAX INC EAX RET _ExceptionFilter ENDP END _Header

|