梳理老羅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
一般不用,置空