编译器扩展SEH
文章目錄
- 編譯器擴(kuò)展的SEH
- 為什么需要編譯器擴(kuò)展的SEH
- 編譯器支持的SEH
- 過濾表達(dá)式
- try_except的實現(xiàn)細(xì)節(jié)
- 手動掛入鏈表
- 自動掛入鏈表
- _try_except嵌套重復(fù)
- 拓展的_EXCEPTION_REGISTRATION_RECORD結(jié)構(gòu)體
- scopetable成員分析
- try_finally
編譯器擴(kuò)展的SEH
為什么需要編譯器擴(kuò)展的SEH
之前我們已經(jīng)了解過SEH的異常處理流程,SEH的處理流程實際上就是在當(dāng)前的堆棧中掛一個鏈表。
這種處理方式有一個比較麻煩的地方,如果我們想使用SEH這種方式來處理異常,那就意味著我們必須要做如下的幾件事:
這些操作相對來說比較復(fù)雜,而編譯器對這一異常處理機(jī)制進(jìn)行了拓展。
編譯器支持的SEH
_try 1) 掛入鏈表 {} _except(過濾表達(dá)式) 2) 異常過濾 {異常處理程序 3) 異常處理程序 }我們只需要將可能出現(xiàn)異常的代碼放到try塊里,編譯器就會替我們把異常的處理程序掛到Exception鏈表。
過濾表達(dá)式
except里的過濾表達(dá)式用于異常過濾,只能有以下三個值:
過濾表達(dá)式只能有三種寫法:
表達(dá)式寫法
_try{_asm{xor edx,edxxor ecx,ecxmov eax,10idiv ecx //EDX = EAX /ECX}}//如果異常碼為0xC0000094返回1否則返回0 _except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("如果出現(xiàn)異常 此處處理\n");}調(diào)用函數(shù)寫法:
//參數(shù)根據(jù)需要來寫,可以不要參數(shù) int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) {pExceptionInfo->ContextRecord->Ecx = 1;//異常處理return EXCEPTION_CONTINUE_EXECUTION;//返回出錯位置重新執(zhí)行 }int main() {_try{_asm{xor edx,edxxor ecx,ecxmov eax,10idiv ecx //EDX = EAX /ECX}}//GetExceptionInformation獲取異常結(jié)構(gòu)指針_except(ExceptFilter(GetExceptionInformation())){printf("如果出現(xiàn)異常 此處處理\n");}getchar(); }try_except的實現(xiàn)細(xì)節(jié)
下面我們來了解一下編譯器是如何通過try_except將異常處理函數(shù)掛入鏈表的
手動掛入鏈表
_asm { mov eax,FS:[0] mov temp,eax lea ecx,myException mov FS:[0],ecx }自動掛入鏈表
示例代碼如下:
void TestException() {_try{}_except(1){} }將程序載入OD,分析編譯器生成的代碼。
編譯器會自動幫我們修改FS:[0],將異常處理函數(shù)掛入SEH鏈表
_try_except嵌套重復(fù)
每個使用_try_except的函數(shù),不管其內(nèi)部嵌套或反復(fù)使用多少次,_try_except都只注冊一遍,即只將一個EXCEPTION_REGISTRATION_RECORD掛入當(dāng)前線程的SEH鏈表中
對于遞歸函數(shù),每一次調(diào)用都會創(chuàng)建一個 _EXCEPTION_REGISTRATION_RECORD,并掛入線程的異常鏈表中
typedef struct _EXCEPTION_REGISTRATION_RECORD {struct _EXCEPTION_REGISTRATION_RECORD *Next;PEXCEPTION_ROUTINE Handler; } EXCEPTION_REGISTRATION_RECORD;示例代碼如下:
_try{_try{}_except(1){}}_except(1){}_try{}_except(1){}只要掛一次SEH鏈就會修改一次 FS[0]
push ebpmov ebp,esppush 0FFhpush offset string "i386\\chkesp.c"+0FFFFFFD4h (00422020)push offset __except_handler3 (00401224)mov eax,fs:[00000000]push eaxmov dword ptr fs:[0],espadd esp,0B8hpush ebxpush esipush edimov dword ptr [ebp-18h],esplea edi,[ebp-58h]mov ecx,10hmov eax,0CCCCCCCChrep stos dword ptr [edi]_trymov dword ptr [ebp-4],0{_trymov dword ptr [ebp-4],1{}mov dword ptr [ebp-4],0jmp $L16979+0Ah (0040107c)_except(1)mov eax,1retmov esp,dword ptr [ebp-18h]{}mov dword ptr [ebp-4],0}mov dword ptr [ebp-4],0FFFFFFFFhjmp $L16975+0Ah (00401095)_except(1)mov eax,1retmov esp,dword ptr [ebp-18h]{}mov dword ptr [ebp-4],0FFFFFFFFh_trymov dword ptr [ebp-4],2{}mov dword ptr [ebp-4],0FFFFFFFFhjmp $L16983+0Ah (004010b5)_except(1)mov eax,1retmov esp,dword ptr [ebp-18h]{}mov dword ptr [ebp-4],0FFFFFFFFhmov ecx,dword ptr [ebp-10h]mov dword ptr fs:[0],ecxpop edipop esipop ebxmov esp,ebppop ebpret不管其內(nèi)部嵌套或反復(fù)使用多少try_except,都只掛一次,指向的異常處理函數(shù)也是確定的。3個異常處理函數(shù)就應(yīng)該有3個鏈,它只掛了一個,這時怎么實現(xiàn)的呢?
拓展的_EXCEPTION_REGISTRATION_RECORD結(jié)構(gòu)體
struct _EXCEPTION_REGISTRATION{struct _EXCEPTION_REGISTRATION *prev;void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);struct scopetable_entry *scopetable;int trylevel;int _ebp; };第一個成員和第二個成員和原來的EXCEPTION_REGISTRATION_RECORD含義一樣,后面多了三個成員。堆棧中的SEH鏈也發(fā)生了變化
堆棧中的結(jié)構(gòu)對應(yīng)匯編代碼如圖:
接下來分析學(xué)習(xí)的三個成員的含義,第三個成員比較好理解,就是當(dāng)前堆棧的ebp,剩下的兩個成員才是關(guān)鍵
struct _EXCEPTION_REGISTRATION{struct _EXCEPTION_REGISTRATION *prev;void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);struct scopetable_entry *scopetable;int trylevel;int _ebp; };scopetable成員分析
這個成員是一個結(jié)構(gòu)體指針,結(jié)構(gòu)體包含以下三個成員
struct scopetable_entry {DWORD previousTryLevel //上一個try{}結(jié)構(gòu)編號 PDWRD lpfnFilter //過濾函數(shù)的起始地址PDWRD lpfnHandler //異常處理程序的地址 }try_finally
之前的課程我們講解了__try __except的的執(zhí)行細(xì)節(jié),但編譯器還提供了另外一組程序塊
__try{ //可能出錯的代碼} __finally{ //一定要執(zhí)行的代碼}總結(jié)
- 上一篇: 用户层和内核层异常的处理流程
- 下一篇: 001 从人物血量学习数据查找