Windows 系统编程初探 (五)结构化异常处理之二:线程相关异常处理

类别:编程语言 点击:0 评论:0 推荐:

        通过前面的例子我们可以看出,所谓异常处理就是在异常处理函数中分析系统传递给它的参数,根据其中

的信息做出相应的反应,是不是和消息循环很像呢?确实很像,因为它们都是 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

本文地址:http://com.8s8s.com/it/it22248.htm