6.编译器拓展SEH
編譯器支持的SEH,SEH是windows平臺下特有的換在別的平臺下它是不支持的。
//這里的代碼底層實現就類似上面的代碼。 _try //1.掛入鏈表{}_except(過濾表達式) //2.異常過濾{異常處理程序 //3.異常處理程序}異常過濾表達式常量值 1) EXCEPTION_EXECUTE_HANDLER (1) 執行except代碼 2) EXCEPTION_CONTINUE_SEARCH (0) 尋找下一個異常處理函數 3) EXCEPTION_CONTINUE_EXECUTION (-1) 返回出錯位置重新執行過濾表達式3種方式
表達式寫法
_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("如果出現異常 此處處理\n");}調用函數寫法
//參數根據需要來寫,可以不要參數 int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo) {pExceptionInfo->ContextRecord->Ecx = 1;//異常處理return EXCEPTION_CONTINUE_EXECUTION;//返回出錯位置重新執行 }int main() {_try{_asm{xor edx,edxxor ecx,ecxmov eax,10idiv ecx //EDX = EAX /ECX}}//GetExceptionInformation獲取異常結構指針_except(ExceptFilter(GetExceptionInformation())){printf("如果出現異常 此處處理\n");}getchar(); }這是沒用異常的函數匯編代碼的生成
這是用了異常的函數匯編代碼生成
異常處理函數:_except_handler3(不同的編譯器這個也是不一樣的)
可以看到用了異常函數的多了很多代碼,新增的代碼其實就是讓 fs[0] 指向新增的異常處理鏈。
如果我們自己寫SEH,用一個異常處理函數就要手動掛入一個SEH鏈表,兩個就掛入兩次,以此類推…,但用編譯器的就不需要。
_try_except嵌套重復
每個使用_try _except的函數,不管其內部嵌套或反復使用多少try_except,都只注冊一遍,即只將一個_EXCEPTION_REGISTRATION_RECORD掛入當前線程的異常鏈表中(對于遞歸函數,每一次調用都會創建一個 _EXCEPTION_REGISTRATION_RECORD,并掛入線程的異常鏈表中)。
我這用了3個_try _except
_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不管其內部嵌套或反復使用多少try_except,都只掛一次,指向的異常處理函數也是確定的。
3個異常處理函數就應該有3個鏈,它只掛了一個,這時怎么實現的呢?
拓展SEH鏈結構
struct _EXCEPTION_REGISTRATION { struct _EXCEPTION_REGISTRATION* prev;//下一個異常鏈void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);//指向異常函數struct scopetable_entry scopetable;int trylevel; //當前代碼執行在那個try里int _ebp; };名字有變不過叫什么名字無所謂了,第一和第二個成員沒變,后面加了3個成員后面再講…
無論怎么拓展,有兩個成員必須要有,一個是鏈表節點一個是異常處理函數。
。
_EXCEPTION_REGISTRATION .scopetable成員分析
struct scopetable_entry {DWORD previousTryLevel; //上一個try{}結構編號PDWORD lpfnFilter; //指向過濾函數的起始地址,_except()括號內那個PDWORD lphnHandle; //異常處理程序的地址 }; int ExceptionFilter() {return EXCEPTION_CONTINUE_EXECUTION; }void a() {_try{//異常點A}_except(EXCEPTION_EXECUTE_HANDLER){printf("異常處理函數1\n");}_try{//異常點B_try{//異常點C }_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("異常處理函數C \n");}}_except(ExceptionFilter()){printf("異常處理函數2\n");}}int main() {a(); }
我這寫了3個異常就會有3塊,scopetable_entry結構的第2第3我們知道了,第一個成員是什么?
previousTryLevel第一和第二的值都是 -1 ,第三個是 1
第一個它沒有上一個try它是-1,第二個它是最外層它也是-1,第三個它的上一個是 1 。(結合下面的下標理解)
struct scopetable;這個結構已經將所有你所有的過濾函數指針,異常處理函數指針,全部放進數組里了, 所以無論它使用多少try_except,都只需要注冊一次。
_EXCEPTION_REGISTRATION .trylevel
int ExceptionFilter() {return 1; }void a() {_try{_try{}_except(EXCEPTION_EXECUTE_HANDLER){printf("異常處理函數\n");}}_except(EXCEPTION_EXECUTE_HANDLER){printf("異常處理函數\n");}_try{_try{}_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){printf("異常處理函數\n");}}_except(ExceptionFilter()){printf("異常處理函數\n");}}
這個 -1 就是當前trylevel的值
struct scopetable_entry雖然記錄了異常信息但異常觸發了,它不知道查誰所以需要trylevel來記錄當前的代碼執行在哪個try里。
當它要進入try時第一件事就是將trylevel修改為對應的scopetable[ ]索引值。
退出時又將 trylevel 改為 - 1。
_except_handler3執行過程
<1>根據 trylevel 選擇 scopetable 數組
<2>調用 scopetable 數組中對應的 IpfnFilter 函數
<3>如果IpfnFilter 函數返回1,調用lpfnHandler指向的異常處理函數
<3>如果IpfnFilter 函數返回0,尋址下一個異常處理函數
<3>如果IpfnFilter 函數返回-1,重新執行觸發異常的代碼
_try_finally
_try {//可能出現錯誤的代碼 } _finally {//一定會執行的代碼 }
_finally里的代碼就算你直接將他return或者觸發異常,它一樣會執行,下面看看它是然后實現的。
scopetable[0].previousTryLevel =-1;
scopetable[0].IpfnFilter=0;
scopetable[0].lfnHandler =0x401179;
通過IpfnFilter來判斷如果它的值為0,就是 _try_finally{};
它進入try時馬上就掉用了 _local_unwind2 這個函數,_local_unwind2 這個函數就叫局部展開。
調用了lfnHandler 指向的函數。
全局展開_global_unwind2
void a() {_try{_try{_try{*(int*)0 = 10;}_finally{printf("一定會執行的代碼A\n");}}_finally{printf("一定會執行的代碼B\n");}} _except(1){printf("異常處理函數\n");}}記得在_except(1)那下斷,往下跟就能看到
_global_unwind2 全局展開
調用局部展開
總結
以上是生活随笔為你收集整理的6.编译器拓展SEH的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 5.SEH(结构化异常处理)
- 下一篇: 7.未处理异常