結構化異常處理

結構化異常處理，是Windows作業系統上，Microsoft對C/C++程式語言做的語法擴充套件，用於處理異常事件的程式控制結構。異常事件是打斷程式正常執行流程的不在期望之中的硬體、軟體事件。硬體異常是CPU丟擲的如“除0”、數值溢位等；軟體異常是作業系統與程式透過RaiseException語句丟擲的異常。Microsoft擴充套件了C語言的語法，用 try-except與try-finally語句來處理異常。異常處理程式可以釋放已經獲取的資源、顯示出錯資訊與程式內部狀態供除錯、從錯誤中恢復、嘗試重新執行出錯的程式碼或者關閉程式等等。一個__try語句不能既有__except，又有__finally。但try-except與try-finally語句可以巢狀使用。try-except.__try// 受保護執行的程式碼__except ( 過濾表示式 )// 異常處理程式碼首先，__try複合語句中的受保護的程式碼被執行。如果沒有異常發生，則繼續執行__except複合語句之後的程式碼。如果__try複合語句中的受保護執行的程式碼發生了異常，或受保護執行的程式碼呼叫的函式內部發生了異常並要求呼叫者來處理該異常，__except語句的過濾表示式被求值，根據其結果來決定如何處理異常：內在函式GetExceptionCode返回一個32位整型值，表示異常的型別。內在函式GetExceptionInformation返回異常的詳細資訊及現場資訊實現的函式。Windows作業系統的應用程式的main()函式受到結構化異常的try-except語句保護，因此程式的未被處理的異常都會被捕獲。這是因為，Windows作業系統在載入使用者程序時，Kernel32.dll中的BaseProcessStart函式在__try塊中呼叫了使用者程序的入口函式mainCRTStartup，因此使用者程式的所有異常都會被捕獲、得到處理。可以使用C++執行時庫中的_set_se_translator()函式把結構化異常轉為丟擲一個C++物件的C++異常。此外，C++異常的catch(...)語句也能直接捕捉結構化異常。可以使用作業系統執行時庫kernel32中的SetUnhandledExceptionFilter()函式設定頂層未處理異常過濾器。try-finally語句.__try {// 受保護執行的程式碼__finally {// 清理用途的程式碼__try複合語句中受保護的程式碼以任何方式執行結束後，不論__try複合語句是因為出現異常而非正常結束，還是沒有出現異常而正常結束，__finally複合語句中的程式碼都會被執行。如果__try複合語句中受保護的程式碼的執行沒有出現異常，包括用goto語句或longjump系統函式跳出__try複合語句等情形，這時將執行__finally複合語句，然後執行try-finally語句之後的其他程式碼。在__finally複合語句中使用內在函式AbnormalTermination判斷是正常結束還是非正常結束__try複合語句。執行順序.如果__try複合語句中受保護的程式碼執行中出現了異常，首先按函式呼叫順序從新向舊搜尋所有包含了出現異常的執行點的try-except語句，執行每個except的過濾函式，直到某個try-except語句的過濾函式結果值為EXCEPTION_EXECUTE_HANDLER。這時，再從包含異常出現的執行點的最內層try-finally語句開始由內向外執行每個finally塊中的程式碼，直至回退到那個過濾函式結果值為EXCEPTION_EXECUTE_HANDLER的try-except語句執行其except塊，最後執行該try-except語句後面的其他程式碼。__leave關鍵字用在 try-finally 語句的受保護節中，其效果是跳轉到受保護節的結尾處。 與goto語句跳出受保護節不同，__leave語句更為有效，因為它不會導致堆疊展開。例子.int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep)printf("在異常表示式中\n");if (code == EXCEPTION_ACCESS_VIOLATION) {printf("接受處理訪問違例異常\n");return EXCEPTION_EXECUTE_HANDLER;else {printf("其他異常都不處理\n");return EXCEPTION_CONTINUE_SEARCH;int main()int* p = 0x00000000; // pointer to NULLprintf("開始主程式\n");__try{printf("進入外層的try\n");__try{printf("進入內層的try\n");int *p=0; // 空指標*p = 13; // 導致訪問衝突異常}__finally{printf("在finally內部。");printf(AbnormalTermination() ? "非正常終止\n" : "正常終止\n");}__except(filter(GetExceptionCode(), GetExceptionInformation())){printf("在except內部\n");printf("主函式結束\n");輸出結果為：開始主程式進入外層的try進入內層的try在異常表示式中接受處理訪問違例異常在finally內部。非正常終止在except內部主函式結束與Windows異常處理機制的關係.Windows作業系統儲存了異常處理幀連結串列的表頭項的地址。程式執行遇到異常事件而中斷時，作業系統的RtlDispatchException函式會從FS:[0]指向的連結串列表頭依次呼叫每個節點包含異常處理回撥函式，直到某個異常處理回撥函式的返回值為0表示已經處理該異常，該執行緒可以恢復執行。連結串列最末一項是作業系統在裝入執行緒時設定的指向kernel32!UnhandledExceptionFilter函式，該函式總是向用戶顯示“Application error”對話方塊。上述異常處理器程式及連結串列，是由使用者程式自己安裝的。連結串列各節點儲存在程式呼叫棧上。Windows異常處理機制支援巢狀異常的處理，即在執行異常處理回撥函式時再次發生異常。這種情況下仍遵照普通異常處理機制，作業系統RtlDispatchException函式再入處理新出現的巢狀的異常。巢狀的異常的處理函式得到的DispatcherContext引數值即為在執行時發生了新異常的異常幀的地址。各種程式語言基於上述Windows異常處理機制，設計了各自的異常處理語句控制結構。Microsoft擴充套件了C語言語法，設計了結構化異常處理的try-except與try-finally語句。一個函式的所有在函式的的try-except與try-finally形成了一個基於包含關係的森林 (資料結構)。一個函式內如果有__try語句，則在函式的入口與結尾處，編譯器插入了EH_prolog與EH_epilog程式碼，把函式內所有在try塊中被保護的程式碼包了起來。 EH_prolog在呼叫棧上建立一個_EXCEPTION_REGISTRATION_RECORD，作為異常處理連結串列的新的表頭，其中包含了Visual C++ 的執行時庫msvcrt.dll的__except_handler4函式地址。在函式塊的結尾處，EH_epilog把這項_EXCEPTION_REGISTRATION_RECORD從連結串列頭移除，恢復其原來的表頭。__except語句中的過濾表示式，由掛在連結串列中的異常處理回撥函式MSVCR100D!__except_handler4來呼叫執行，返回值即為過濾表示式的求值結果。實際上，編譯器實現結構化異常時，把連結串列每項的資料結構由2個成員擴充套件為5個成員，即在高地址方向追加了一個scopetable_entries型別結構體陣列的指標、一個整型項表示執行點位於當前函式的哪個try塊中、一個儲存暫存器EBP的整數項。此後。異常發生時，作業系統的異常處理機制的ntdll!RtlDispatchException函式會從FS:[0]指向的連結串列表頭依次呼叫異常幀連結串列的每個節點所包含的異常處理回撥函式MSVCR100D!__except_handler4，根據該回調函式的返回值來確定異常是否已經被處理，可以根據異常上下文；然後，對能處理當前異常的函式，從包含執行點的最記憶體__try語句，直到能處理異常的try-except塊，呼叫區域性展開函式_EH4_LocalUnwind；再執行能處理異常的try-except塊的異常處理程式碼；最後，繼續執行try-except之後的其他程式碼。全域性展開，是由_EH4_GlobalUnwind呼叫ntdll!RtlUnwind，RtlUnwind遍歷訪問異常幀連結串列，把從表頭幀到目標幀呼叫。異常處理回撥函式根據當前的異常碼與異常標誌，對當前異常幀所在函式，從包含了產生異常的執行點的最內層__try語句開始，直至函式內的最外層try塊，依次呼叫try-finally的清理用途程式碼。這一步實際上是呼叫_EH4_LocalUnwind函式來完成。區域性展開，由_EH4_LocalUnwind函式實現，是從包含了產生異常的執行點的最內層__try語句開始，按著程式碼的包含關係向外直至目標try-except語句為止，依次呼叫try-finally的清理用途程式碼。SAFESEH.為了防止篡改異常處理函式的地址，自Windows XP SP2起引入了檢查異常處理函式的地址是否有效。但這僅用於x86。而x64或ARM已經把所有異常處理函式放在了PDATA節中；該節叫做“.pdata”或者Exception Directory,用於存放處理異常的資訊。連結器選項codice_1用於設定是否需要開啟。如果codice_2被使用，連結器生成的可執行映像檔案包含它的安全的異常處理函式的清單。如果codice_2未使用，連結器將檢查如果所有的模組都兼容於安全的異常處理函式特性，則生成的可執行映像檔案不包含安全的異常處理函式的清單。如果codice_4被使用，連結器生成的可執行映像檔案不包含安全的異常處理函式的清單。Visual C++編譯選項.Visual C++編譯器的編譯選項/EH設定異常處理模型 (Exception Handling Model)：