WIN32汇编语言教程:第14章 异常处理 · 14.3 使用SEH处理异常(1)

使用筛选器异常处理程序是最简单的处理异常的方法,但在使用中也存在一些不便之处,最明显的就是不便于模块的封装:由于筛选器异常处理程序是全局性的,无法为一个线程或一个子程序单独设置一个异常处理回调函数,这样就无法将私有的异常处理代码封装进某个模块中。

Windows系统中还提供了另一种在每个线程之间独立的异常处理方法——结构化异常处理(Structured Exception Handling,简称SEH),SEH是Win32系统中为数不多的应用广泛却又未被公开的特征之一。

SEH和筛选器异常处理之间有一些共同点:首先是两者的异常处理程序都是以回调函数的方式提供的;另外,系统都会根据回调函数的返回值选择不同的操作。

但是它们之间也存在很多的不同点:

● 使用SEH可以为每个线程设置不同的异常处理程序,而且可以为每个线程设置多个异常处理程序。

● 两者的回调函数的参数定义和返回值的定义都是不同的。

● SEH使用了与硬件平台相关的数据指针,所以在不同硬件平台中使用SEH的方法会有所不同(也许这正是SEH未被Microsoft公开的原因)。

接下来首先看一个使用SEH处理异常的例子,这个例子与前面的例子很相似,都是在回调函数中显示异常代码和发生异常的位置,并将程序修正到_SafePlace标号去执行。例子的源代码可以在本书所附光盘的Chapter14\SEH01目录中找到,其中的SEH.asm的内容如下:

                  .386

                  .model flat,stdcall

                  option casemap:none

;####################################################################

include       windows.inc

include       user32.inc

includelib    user32.lib

include       kernel32.inc

includelib    kernel32.lib

;####################################################################

; 数据段

;####################################################################

                 .const

szMsg          db '异常发生位置:%08X,异常代码:%08X,标志:%08X',0

szSafe         db '回到了安全的地方!',0

szCaption      db 'SEH例子',0

 

                 .code

;####################################################################

; SEH Handler 异常处理程序

;####################################################################

_Handler   proc      _lpExceptionRecord,_lpSEH,\

                         _lpContext,_lpDispatcherContext

                 local  @szBuffer[256]:byte

 

                 pushad

                 mov    esi,_lpExceptionRecord

                 mov    edi,_lpContext

                 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,ExceptionContinueExecution

                 ret

 

_Handler   endp

;####################################################################

start:

;********************************************************************

; 在堆栈中构造一个 EXCEPTION_REGISTRATION 结构

;********************************************************************

                  assume fs:nothing

                  push      offset _Handler

                  push      fs:[0]

                  mov    fs:[0],esp

;********************************************************************

; 会引发异常的指令

;********************************************************************

                  xor    eax,eax

                   mov    dword ptr [eax],0  ;产生异常,然后_Handler被调用

;            ...

; 如果这中间有指令,这些指令将不会被执行!

;             ...

_SafePlace:

                  invoke MessageBox,NULL,addr szSafe,addr szCaption,MB_OK

;********************************************************************

; 恢复原来的 SEH 链

;********************************************************************

                 pop    fs:[0]

                  pop    eax

                  invoke ExitProcess,NULL

;####################################################################

       end start

14.3.1 注册回调函数

在例子程序中,SEH异常处理回调函数的设置由下面3条指令完成:

       push      offset _Handler

       push      fs:[0]

       mov    fs:[0],esp

为什么这3句简单的指令就可以完成设置工作,为什么又要使用fs段选择器呢?这要从线程信息块(Thread Information Block/TIB)说起。

Win32为每个线程定义了一个线程信息块,其中保存了线程的一些属性数据,线程信息块的格式被定义为NT_TIB结构:

NT_TIB STRUCT

   ExceptionList dd    ?      ;SEH链入口

   StackBase dd        ?      ;堆栈基址

   StackLimit dd       ?      ;堆栈大小

   SubSystemTib dd     ?

   FiberData dd        ?

   ArbitraryUserPointer dd ?

   Self dd ?                   ;本NT_TIB结构自身的线性地址

NT_TIB ENDS

NT_TIB结构的第一个字段ExceptionList指向一个EXCEPTION_REGISTRATION结构,SEH异常处理回调函数的入口地址就是由EXCEPTION_REGISTRATION结构指定的,这个结构的定义如下:

EXCEPTION_REGISTRATION STRUCT

   prev dd ?      ;前一个EXCEPTION_REGISTRATION结构的地址

   handler dd ? ;异常处理回调函数地址

EXCEPTION_REGISTRATION ENDS

当异常发生时,系统从TIB中取出ExceptionList字段,然后从ExceptionList字段指定的EXCEPTION_REGISTRATION结构中取出handler字段,并根据其中的地址去调用回调函数,整个过程如图14.1所示,所以只要构建一个含有回调函数地址的EXCEPTION_REGISTRATION结构,然后修改TIB中的ExceptionList字段,指向这个结构就可以注册一个SEH异常处理回调函数。


图14.1 SEH异常处理程序入口地址的定义

上页:第14章 异常处理 · 14.2 使用筛选器处理异常(2) 下页:第14章 异常处理 · 14.3 使用SEH处理异常(2)

第14章 异常处理

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号