關於SEH(結構化異常處理)的一些知識

梳理老羅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

一般不用,置空


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章