标签:
梳理老罗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 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
;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
一般不用,置空
标签:
原文地址:http://blog.csdn.net/lanwanjunxixihaha/article/details/46534193