📄 014.txt
字号:
EXCEPTION_ILLEGAL_INSTRUCTION
0C000001Dh
遇到无效指令
EXCEPTION_IN_PAGE_ERROR
0C0000006h
存取不存在的内存页面
EXCEPTION_INT_DIVIDE_BY_ZERO
0C0000094h
除零错误
EXCEPTION_SINGLE_STEP
080000004h
单步中断
EXCEPTION_STACK_OVERFLOW
0C00000FDh
堆栈溢出
EXCEPTION_UNWIND
0C0000027h
展开操作
例子中的mov dword ptr [eax],0指令去写一个没有写权限的地址,所以会引发一个EXCEPTION_ACCESS_VIOLATION异常,从例子程序运行后显示的消息中可以验证这点,读者也可以尝试将这条指令修改为各式各样的错误指令,看看它们引发的异常对应哪个异常原因代码。
异常原因代码的含义是按照数据位划分的,其规则如表14.2所示。
表14.2 异常原因代码各数据位的含义
数 据 位
含 义
取 值 说 明
位31~30
严重性系数
00=成功,01=信息,10=警告,11=错误
位29
定义者
0=Microsoft定义,1=客户应用程序定义
位28
保留位
必须为0
位27~16
设备代码
表示引发异常的位置
位15~0
异常代码
用来分辨产生异常的原因
其中的位27~16定义了设备代码,用来表示异常代码发生在哪个特定的设备中,当前已经定义的设备代码如表14.3所示。
表14.3 异常原因代码中的设备代码定义
设 备 代 码
取 值
设 备 代 码
取 值
FACILITY_NULL
FACILITY_CONTROL
10
FACILITY_RPC
1
FACILITY_CERT
11
FACILITY_DISPATCH
2
FACILITY_INTERNET
12
FACILITY_STORAGE
3
FACILITY_MEDIASERVER
13
FACILITY_ITF
4
FACILITY_MSMQ
14
FACILITY_WIN32
7
FACILITY_SETUPAPI
15
FACILITY_WINDOWS
8
FACILITY_SCARD
16
FACILITY_SECURITY
9
FACILITY_COMPLUS
17
举例来讲,断点异常和单步中断并不属于程序错误,所以这两种异常代码中的严重性系数10,表示属于警告信息而非错误,但是遇到内存越权访问、除零错误等时候,这两位的值就是11了,表示这个错误让线程无法继续执行。
EXCEPTION_RECORD结构中的ExceptionCode字段定义了异常标志,它由一系列的数据位构成,定义如下:
● 位0——代表发生的异常是否允许被恢复执行。当位0被复位的时候,表示回调函数在对异常进行处理后可以指定让程序继续运行,置位的时候表示这个异常是不可恢复的,这时程序最好进行退出前的扫尾工作并选择终止程序,如果这时非要指定让程序继续执行的话,Windows会再次以EXCEPTION_NONCONTINUABLE_EXCEPTION异常代码调用回调函数。为了程序的可读性,可以通过两个预定义值EXCEPTION_CONTINUABLE(定义为0)和EXCEPTION_NONCONTINUABLE(定义为1)来测试这个标志位。
● 位1——EXCEPTION_UNWINDING标志。表示回调函数被调用的原因是进行展开操作(详见14.3.4小节)。
● 位2——EXCEPTION_UNWINDING_FOR_EXIT标志。表示回调函数被调用的原因是进行最终退出前的展开操作。
当处理异常的代码设计得不完善而在运行中引发新的异常时,回调函数会被嵌套调用。在这种情况下,EXCEPTION_RECORD结构中的pExceptionRecord字段会指向下一个EXCEPTION_RECORD结构,这条EXCEPTION_RECORD结构链定义了嵌套发生的多个异常的情况;如果没有嵌套的异常,pExceptionRecord的值为NULL。
结构中的ExceptionAddress字段定义了引发异常的指令的地址。
有了这些信息后,回调函数就可以根据类型来对不同的异常进行不同的处理,比如,对那些“计划内”的异常执行功能性的代码,而发生其他非致命异常的时候转到“安全”位置去执行。
2. 修正错误
在筛选器异常处理回调函数被系统调用的时候,参数中指定的EXCEPTION_POINTERS结构中的ContextRecord字段指向一个CONTEXT结构,这个结构中保存了异常发生时刻的运行环境,也就是所有寄存器的值。程序也可以通过这个结构中的regEip字段来得知异常发生的位置。在例子中,程序用一个对话框显示出异常代码、异常标志和CONTEXT结构中的EIP值,对比一下就可以发现,显示的EIP值正是那句产生异常的mov [eax],0指令的地址。
在第12章介绍多线程时,已经介绍过操作系统为每个线程保存单独的寄存器环境和单独的堆栈,那么当异常发生的时候,CONTEXT结构指出的环境会对应哪个线程的环境呢?其实答案很简单:Windows将会在产生异常的线程中运行回调函数,CONTEXT结构对应的是出错线程的环境,回调函数使用的堆栈也是这个线程的堆栈,在这样的安排下,回调函数才可能去修复线程中的错误。
修正错误的操作反映在对这个CONTEXT结构的修改上,当回调函数修改了结构中的值并返回后,系统会将线程的运行环境设置为新的值,所以要修正某个寄存器中的错误取值,只要修改这个CONTEXT结构就可以了。在例子中,异常处理程序将regEip字段的值修改 为_SafePlace标号的地址,这样线程恢复运行时是从_SafePlace标号的地方开始执行的。当然,这样处理后,在错误指令和_SafePlace标号之间的其他指令就不会被执行了。
3. 回调函数的返回值
回调函数返回后,Windows执行默认的异常处理程序,这个程序会根据回调函数的返回值决定如何进行下一步动作。
回调函数的返回值可以有3种取值:EXCEPTION_EXECUTE_HANDLER(定义为1)、EXCEPTION_CONTINUE_SEARCH(定义为0)和EXCEPTION_CONTINUE_EXECUTION(定义为-1)。
当返回值是EXCEPTION_EXECUTE_HANDLER时,进程将被终止,但是在终止之前系统不会显示出错提示对话框;当返回值是EXCEPTION_CONTINUE_SEARCH时,系统同样将终止程序的执行,但是在终止前会首先显示出错提示对话框。使用这两种返回值的时候,异常处理程序完成的工作一般是退出前的扫尾工作。
而返回值是EXCEPTION_CONTINUE_EXECUTION时,系统将CONTEXT设置回去并继续执行程序,例子程序中就是这样使用的。
当异常标志中包含EXCEPTION_NONCONTINUABLE标志位时,不应该使用EXCEPTION_CONTINUE_EXECUTION作为返回值,这样只会引发一个新的异常。(例子程序中为了简化代码,没有判断并处理异常标志为EXCEPTION_NONCONTINUABLE的情况)
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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -