关于SEH(结构化异常处理)的一些知识

来源:互联网 发布:sql update数值 编辑:程序博客网 时间:2024/06/11 07:59

梳理老罗win32汇编关于SEH一章的知识。

异常处理方式有两种: 筛选器异常处理和结构化异常处理,筛选器是全局性的,无法为一个线程或一个子程序单独设置一个异常处理回调函数,而结构化异常处理(Structured Exception Handing)SEH提供了每个线程之间独立的异常处理方法。

以下以两个例子来学习SEH

例子1:不含栈展开操作的异常处理(栈展开会在例子二中介绍)

.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;include
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
lpOldHandler dd?
.const
szMsg db "异常发生位置:%08X,%08X,异常代码:%08X,标志:%08X",0
szSafe db "回到了安全的地方",0
szTitle db "SEH的例子",0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
_Handler proc c _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
LOCAL @szBuffer[1024]:byte
pushad

mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,[esi].ExceptionCode

;由于栈展开操作而被调用
.if eax == STATUS_UNWIND
popad
mov eax,ExceptionContinueSearch
;访问非法
.elseif eax == EXCEPTION_ACCESS_VIOLATION
;ExceptionCode中包含了,异常的类型,严重性,触发异常的设置,是内存
;还是网络,还是CPU,还是接口卡,还是多媒体设置
;ExceptionFlag 表是否继续执行,还是进行栈展开
invoke wsprintf,addr @szBuffer,addr szMsg,\
[edi].regEip,[esi].ExceptionAddress,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK

mov eax,_lpSEH

;mov [edi].regEip,offset _SafePlace

push [eax+0ch]
pop [edi].regEbp
push [eax+8]
pop [edi].regEip
push eax
pop [edi].regEsp

popad
mov eax,ExceptionContinueExecution
.else
popad
mov eax,ExceptionContinueSearch
.endif

assume esi:nothing,edi:nothing
ret
_Handler endp


_Test proc
assume fs:nothing
push ebp
push offset _SafePlace
push offset _Handler
;fs:[0]处保留了EXCEPTION_REGISTRATION结构的地址
push fs:[0]
;修改了fs:[0]的指向,栈中的前8个字段刚好吻合了EXCEPTION_REGISTRATION结构中的ExceptionList字段
;pre EXCEPTION_REGISTRATION
;handler
mov fs:[0],esp

xor eax,eax
mov dword ptr [eax],0

_SafePlace:
invoke MessageBox,NULL,addr szSafe,addr szTitle,MB_OK
pop fs:[0]
add esp,0ch
ret
_Test endp


start:
invoke _Test
invoke ExitProcess,NULL
end start

需要注意的地方:

1 在有可能发生异常之前需要在栈上保存异常处理的相关变量,在栈上保存有利于模块化。

2 在回调函数中,根据ExceptionCode异常代码中区分何种异常是可处理的,可处理异常返回 ExceptionContinueExecution,否则返回 ExceptionContinueSearch,注意一定要与筛选器异常处理的返回值区分开来,虽然名字上有些相似,但值完全不同,若搞混,则会出现回调函数不断被调用的问题,因为如果返回 EXCEPTION_CONTINUE_EXECUTION,,则是-1,表示该回调无法处理异常(实际本意并非如此),则交由系统处理,系统以ExceptionCode 为EXCEPTION_UNWIND_FOR_EXIT的EXCEPTION_REGISTRATION再次调用回调函数。

例子二:含栈展开操作的异常处理

.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;include
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data
lpOldHandler dd?
.const
szMsg db "外异常发生位置:%08X,异常代码:%08X,标志:%08X",0
szInMsg db "内异常发生位置:%08X,异常代码:%08X,标志:%08X",0
szSafe db "回到了外安全的地方",0
szInSafe db "回到了内安全的地方",0
szTitle db "SEH的例子",0
szFSMsg db "FS:[0] %08X",0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code


_InTest proc
assume fs:nothing
push ebp
push offset _InSafePlace
push offset _InHandler
push fs:[0]
mov fs:[0],esp

xor eax,eax
mov dword ptr [eax],0

_InSafePlace:
invoke MessageBox,NULL,addr szInSafe,addr szTitle,MB_OK
pop fs:[0]
add esp,0ch
ret
_InTest endp

_InHandler procc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
LOCAL @szBuffer[1024]:byte
pushad

mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,[esi].ExceptionCode

;ExceptionCode中包含了,异常的类型,严重性,触发异常的设置,是内存
;还是网络,还是CPU,还是接口卡,还是多媒体设备
;ExceptionFlag 有啥用?
invoke wsprintf,addr @szBuffer,addr szInMsg,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK

popad
mov eax,ExceptionContinueSearch

assume esi:nothing,edi:nothing
ret
_InHandler endp

_Handler proc c _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatcherContext
LOCAL @szBuffer[1024]:byte
pushad

mov esi,_lpExceptionRecord
mov edi,_lpContext
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov eax,[esi].ExceptionCode

;由于栈展开操作而被调用
.if eax == STATUS_UNWIND
popad
mov eax,ExceptionContinueSearch
;访问非法
.elseif eax == EXCEPTION_ACCESS_VIOLATION
;ExceptionCode中包含了,异常的类型,严重性,触发异常的设置,是内存
;还是网络,还是CPU,还是接口卡,还是多媒体设置
;ExceptionFlag 标识符,继续执行,还是栈展开?
invoke wsprintf,addr @szBuffer,addr szMsg,\
[edi].regEip,[esi].ExceptionCode,[esi].ExceptionFlags
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK

mov eax,_lpSEH

push [eax+0ch]
pop [edi].regEbp
push [eax+8]
pop [edi].regEip
push eax
pop [edi].regEsp

invoke wsprintf,addr @szBuffer,addr szFSMsg,dword ptr fs:[0]
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
invoke RtlUnwind,_lpSEH,NULL,NULL,NULL
invoke wsprintf,addr @szBuffer,addr szFSMsg,dword ptr fs:[0]
invoke MessageBox,NULL,addr @szBuffer,NULL,MB_OK
popad
mov eax,ExceptionContinueExecution
.else
popad
mov eax,ExceptionContinueSearch
.endif

assume esi:nothing,edi:nothing
ret
_Handler endp

_Test proc
assume fs:nothing
push ebp
push offset _SafePlace
push offset _Handler
;fs:[0]处保留了EXCEPTION_REGISTRATION结构的地址
push fs:[0]
;修改了fs:[0]的指向,栈中的前8个字段刚好吻合了EXCEPTION_REGISTRATION结构中的ExceptionList字段
mov fs:[0],esp

invoke _InTest

_SafePlace:
invoke MessageBox,NULL,addr szSafe,addr szTitle,MB_OK
pop fs:[0]
add esp,0ch
ret
_Test endp


start:
invoke _Test
invoke ExitProcess,NULL
end start

 什么是栈展开,当回调进行栈展开操作时,从fs:[0]处的回调函数开始,逐个以EXCEPTION_UNWIND代码调用回调,一直到自身为止,然后将之前遍历的所有回调都卸载,即把fs:[0]指向自己的EXCEPTION_REGISTRATION结构。为什么要进行栈展开呢?原因一:让被卸载的回调有机会进行扫尾工作。原因二:会发生异常,分析如下:函数展开完毕后,会把堆栈恢愎到异常前的位置,那么之前的fs:[0]所指向的位置就超出栈顶了,将来会被其它的数据覆盖,而如果再次发生异常,从fs:[0]开始,就出错了。

如何进行栈展开?

invoke RtlUnwind,lpLastStackFrame,lpCodeLabel,lpExceptionRecord,dwRet

参数1:lpLastStackFrame

若不为空,则从fs:[0]开始,直到边LastStackFrame这个EXCEPTION_REGISTRATION之前的回调都会调用

右为空,则调用所有的注册的回调

参数2:lpCodeLabel

若不为空,函数调用完毕后,返回到边CodeLabel所指向的指令区

若为空,则采用正常的函数返回方式

参数3:lpExceptionRecord

展开时传给每个回调的结构,若为空,则系统自动生成该结构

参数4:dwRet

一般不用,置空


0 0