通过前面的例子我们可以看出,所谓异常处理就是在异常处理函数中分析系统传递给它的参数,根据其中
的信息做出相应的反应,是不是和消息循环很像呢?确实很像,因为它们都是 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