標籤:
24.1 程式的結構
(1)try/except架構
__try{ //被保護的代碼塊 ……}__except(except fileter/*異常過濾程式*/){ //例外處理常式}
(2)說明
①當__try塊中的代碼發生異常時,__except()中的過濾程式就被調用。
②過濾程式可以是一個簡單的運算式或一個函數(傳回值應為EXCEPTION_CONTINUE_SEARCH、EXCEPT_CONTINUE_EXECUTE或EXCEPT_EXECUTE_HANDLER之一)
③過濾運算式中可以調用GetExceptionCode和GetExceptionInformation函數取得正在處理的異常資訊。但這兩個函數不能在例外處理常式中使用。
④與try/finally不同,try/except中可以使用return、goto、continue和break,它們並不會導致局部展開。
24.2 異常過濾程式
(1)傳回值
標識 |
值 |
說明 |
EXCEPTION_EXECUTE_HANDLER |
1 |
執行except花括弧內代碼,同時執行全域展開。最後程式從except花括弧後面的第一句代碼繼續運行。 |
EXCEPTION_CONTINUE_SEARCH |
0 |
向外層尋找帶except的try塊,並調用對應的異常過濾程式。 |
EXCEPTION_CONTINUE_EXECUTE |
-1 |
重新執行導致異常的那條CPU指令本身。 |
(2)全域展開——異常過濾程式返回EXCEPTION_EXECUTE_HANDLER是會執行全域執開。
①當某個__try塊中的代碼觸發了異常時(也可能是__try塊中調用的函數中引發異常),作業系統會從最靠近引發異常代碼的地方開始從下層往上層尋找__except塊(這裡的層是指__try塊的嵌套層),對於找到的每一個__except塊,會先計算它的異常過濾器,如果過濾器返回EXCEPTION_CONTINUE_SEARCH,則說明此__except塊不處理此類異常,需要繼續往上層尋找,如果某過濾器返回EXCEPTION_EXECUTE_HANDLER則說明此__except塊可以處理此類異常,即找到了異常的處理代碼,此時停止尋找,但是在執行該__except塊中的異常處理代碼之前,要先進行全域展開。
②全域展開的過程與尋找__except塊的過程類似,只不過這次是尋找從底層向上尋找__finally塊,尋找過程中遇到的每一個__finally塊中的代碼都被執行,直到尋找到前面說的處理異常的__except塊那一層停止,這時全域展開完成,然後執行__except塊中的異常處理代碼。
③執行完異常處理代碼之後,指令流從__except塊後的第一條指令開始。從這裡也可以看出全域展開也是為了保證__finally語義的正確性,因為指令流從引發異常代碼轉到到__except異常處理代碼時也導致了指令流從__try塊嵌套層中所有與__finally對應的__try塊中流出,由前面的__finally語義說明可知,此時必須要執行全域展開過程以包成__finally語義的正確性。
(3)停止全域展開——將return置於finally塊中可阻止全域展開。【未定義行為,VC2013直接報錯了!】
(4)慎用EXCEPTION_CONTINUE_EXECUTION
①嘗試修複錯誤,出現失敗的執行個體分析
*pchBuffer = TEXT("J");//C/C++語句 //編譯後的產生的機器指令 MOV EAX,DWORD PTR[pchBuffer] MOV WORD PTR[EAX], ‘J‘ //導致異常的指令。當異常過濾程式捕獲該異常後,修正 //pchBuffer,讓其指向一個正確的地址。並讓系統重新 //執行第二要CPU指令。問題在於寄存器不可能自動更新 //以反映變數pchBuffer的更新,於是該異常又致另一個導 //異常,程式陷入了死迴圈
②虛擬記憶體結合SEH可實現按需調撥儲存空間,有時能寫出運行速度快和高效的應用程式(見第15章的《如何預訂大塊地址空間和為地址空間稀疏調撥儲存空間》
③系統為線程棧建了一個SEH框。當線程試圖訪問棧中尚未調撥儲存空間的地區時,會引發一個異常。系統內部的異常過濾程式會捕獲到該異常並在內部調用VirtualAlloc為線程棧調撥更多的儲存,並且返回EXCEPTION_CONTINUE_EXECUTION讓原先拋出異常的指令重新執行下去。
【SEHAndMemory】示範虛擬記憶體的按需調撥
#include <windows.h>#include <tchar.h>#include <locale.h>#define PAGELIMIT 80LPBYTE lpNxtPage;DWORD dwPages = 0;DWORD dwPageSize;//頁面大小,一般為4KBINT PageFualtExceptionFilter(DWORD dwCode){ LPVOID lpvResult; //不是非法訪問記憶體 if (dwCode !=EXCEPTION_ACCESS_VIOLATION){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的例外處理常式代碼 } //當超過指定的頁面數時 if (dwPages >=PAGELIMIT){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的例外處理常式代碼 } //非法訪問記憶體,則為預訂的空間提交下一頁實體儲存體器 lpvResult = VirtualAlloc((LPVOID)lpNxtPage, dwPageSize, MEM_COMMIT, PAGE_READWRITE); if (lpvResult == NULL){ return EXCEPTION_EXECUTE_HANDLER;//執行except塊的例外處理常式代碼 } //提交成功 dwPages++; lpNxtPage += dwPageSize; _tprintf(_T("第%d頁提交成功!\n"), dwPages); return EXCEPTION_CONTINUE_EXECUTION; //重新執行觸發異常的那條CPU指令}int main(){ _tsetlocale(LC_ALL, _T("chs")); LPVOID lpvBase;LPTSTR lpPtr;BOOL bSuccess; SYSTEM_INFO sSysInfo; GetSystemInfo(&sSysInfo); dwPageSize = sSysInfo.dwPageSize; _tprintf(_T("CPU頁面大小為%dKB.\n"), sSysInfo.dwPageSize / 1024); //預訂儲存空間 lpvBase = VirtualAlloc(NULL, PAGELIMIT*dwPageSize, MEM_RESERVE, PAGE_NOACCESS); lpPtr = (LPTSTR)(lpNxtPage = (LPBYTE)lpvBase); for (DWORD i = 0; i < PAGELIMIT*dwPageSize/sizeof(TCHAR);i++){ __try{ lpPtr[i] = _T(‘a‘);//寫入一個位元組的資料 } __except (PageFualtExceptionFilter(GetExceptionCode())){ _tprintf(_T("異常被處理\n")); //ExitProcess(GetLastError()); } } bSuccess = VirtualFree(lpvBase, 0, MEM_RELEASE); _tprintf(_T("釋放操作%s.\n"), bSuccess ? _T("成功") : _T("失敗")); _tsystem(_T("PAUSE")); return 0;}
24.3 GetExceptionCode
(1)GetExceptionCode是個內嵌函式,其代碼直接嵌入到被調用的地方(注意與函數調用的區別),它的傳回值表明剛剛發生的異常的類型(定義在WinBase.h中,如EXCEPTION_ACCESS_VIOLATION)
(2)該函數只能在異常過濾程式裡(即__except之後的小括弧內)或者例外處理常式的代碼裡調用(__except塊後面的花括弧內),但不能在異常過濾函數中使用。
//合法代碼 __try{ y = 0; x = / y; } __except ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH){ //在__except塊的小括內使用,合法 switch (GetExceptionCode()){ //__except塊的花括中使用,合法! ...... } } |
//非法代碼 LONG MyFilter(void){} { //在異常過濾函數中使用GetExceptionCode,不合法! return ((GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH); } __try{ y = 0; x = 4 / y; } __except(MyFilter()){ //可改成將GetExceptionCode作為參數傳給MyFilter的形式。 //處理異常 } |
(3)異常錯誤碼的規則
位 |
31-30 |
29 |
28 |
27-16 |
15-0 |
內容 |
嚴重性 |
Microsoft/ Customer |
保留位 |
裝置代碼 |
異常代碼 |
含義 |
0=Success 1=Informational 2=Warning 3=error |
0=Mircosoft所定義的代碼 1=Customer所定義的代碼 |
一直為0 |
前256個值為Micorsoft所保留。(如FACILITY_NULL(0)表示該異常可以在系統任何裝置出現,並不只發生在一些特定的裝置上) |
由Microsoft/ Customer所定義的代碼 |
24.4 GetExceptionInformation
(1)GetExceptionInformation可擷取異常發生時,系統向發生異常的線程棧中壓入的EXCEPTION_RECORD、CONTEXT和EXCEPTION_POINTERS結構中的異常資訊或CPU有關的資訊
(2)這個函數只能在異常過濾程式中調用(即__except塊的小括弧),因為EXCEPT_RECORD、CONTEXT和EXCEPTION_POINTER資料結構只有在系統計算異常過濾程式時才有效。一旦控制流程被轉移到其他地方,這些棧上的資料結構會被銷毀。但我們可以自己儲存他們,以備後用。
//儲存棧中異常資訊的方法void FuncSkunk(){ //聲明一些可以儲存異常資訊的結構體,須在try塊外面聲明 EXCEPTION_RECORD SavedExceptRec; CONTEXT SavedContext; __try{ } __except ( //注意逗號運算式,取最後一個運算式為整個運算式的值。 SavedExceptRec = *(GetExceptionInformation())->ExceptionRecord, SavedContext = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER){ //異常處理 }}
(3)EXCEPTION_RECORD結構體——剛發生的異常的詳細資料
欄位 |
說明 |
DWORD ExceptionCode |
異常代碼,就是GetExceptionCode函數的傳回值 |
DWORD ExceptionFlags |
異常標誌 0—表示繼續的異常;EXCEPTION_NONCONTINUABLE—不可繼續的異常,如果程式試圖在一個不可繼續的異常之後繼續執行,會引發EXCEPTION_NONCONTINUABLE_EXCEPTION異常。 |
PEXCEPTION_RECORD pExceptionRecord |
指向另一個未處理異常的EXCEPTION_RECORD結構。(即嵌套異常發生時,異常會形成異常鏈) |
PVOID ExceptionAddress |
導致異常的CPU指令的地址 |
DWORD NumberParameters |
ExceptionInformation數組裡元素的個數。對絕大部分的異常來說,這個值為0。 |
ULONG_PTR ExceptionInformation [EXCEPTION_MAXIMUM_PARAMETERS] |
描述異常的附加參數數組,對絕大部分的異常來說,這個數組元素都未定義。 |
24.5 軟體異常——RaiseException函數
參數 |
說明 |
DWORD dwExceptionCode |
要拋出異常的標識符,可參考《異常錯誤碼規則》來編寫 |
DWORD dwExceptionFlags |
必須下列兩者之一 0: EXCEPTION_NONCONTINUABLE:異常不可繼續,即不能再異常過濾程式中返回EXCEPTION_CONTINUE_EXECUTE,否則重新執行那條導致錯誤的CPU指令會繼續拋出一個新的EXCEPTION_NONCONTINUABLE_EXCEPTION異常。 |
DWORD nNumberOfArguments |
用來傳遞有關拋出異常的附加資訊。一般不需要。可將nNumberOfArgument設為0。pArguments設為NULL。 |
Const ULONG_PTR* pArguments |
傳回值 |
void |
【RaiseException程式】——示範自己拋出的軟體異常
#include <tchar.h>#include <windows.h>DWORD FilterFunction(){ _tprintf(_T("1")); //第1句被輸出的語句 return EXCEPTION_EXECUTE_HANDLER;}int main(){ __try{ __try{ RaiseException(1, 0, 0, NULL); } __finally{ _tprintf(_T("2")); //第2句被輸出的語句 } } __except (FilterFunction()){ _tprintf(_T("3\n")); //第3句被輸出的語句 } _tsystem(_T("PAUSE"));
return 0;}
第24章 SEH結構化異常處理—異常處理及軟體異常