第24章 SEH結構化異常處理—異常處理及軟體異常

來源:互聯網
上載者:User

標籤:

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結構化異常處理—異常處理及軟體異常

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.