簡記Windows結構化異常處理之二

=============================================================

標題:簡記Windows結構化異常處理之二

備註:參考《Windows核心編程》

日期:2011.3.28

姓名:朱銘雷

=============================================================

異常處理程序的語法結構:

__try

{

    //

}

__except(// 異常過濾)

{

// 異常處理

}

示例1

#include <iostream>

#include <excpt.h>

using namespace std;

int Div(int i)

{

       int n = 10;

       __try

       {

              n = n / i;

       }

       __except(EXCEPTION_EXECUTE_HANDLER)

       {

              n = 10;

       }

       return n;

}

int _tmain(int argc, _TCHAR* argv[])

{

       int i = 2, ii = 0;

       int ir = Div(i);

       int iir = Div(ii);

       cout << ir << endl;

       cout << iir << endl;

       system("PAUSE");

       return 0;

}

Div(i)Div函數正常執行,n / i的結果爲5,然後將n的值返回。

Div(ii)n / 0會產生硬件異常,控制流轉到異常過濾,異常過濾表達式的值是EXCEPTION_EXECUTE_HANDLER,則將執行異常處理程序,n=10,最後返回n的值。

執行結果:

系統異常處理流程圖(摘自《Windows核心編程》):

 

這個圖中非常重要的一步是“全局展開”的過程,以及三個異常過濾標識符的導向。

經驗法則:使用異常處理程序,只處理那些我們知道怎麼處理的異常。

示例2

PBYTE RobustMemDup(PBYTE pbSrc, size_t cb)

{

       PBYTE pbDup = NULL;

       __try

       {

              pbDup = (PBYTE)malloc(cb);

              memcpy(pbDup, pbSrc, cb);

       }

       __except(EXCEPTION_EXECUTE_HANDLER)

       {

              free(pbDup);

              pbDup = NULL;

       }

       return pbDup;

}

如果pbSrc參數傳入非法地址或者malloc函數失敗,會導致memcpy拋出訪問違規異常,導致異常過濾程序執行,過濾程序又將程序控制流交給except塊。在except塊裏,釋放內存並將puDup至爲NULL。如果調用者給函數傳入合法地址,並且malloc函數調用成功,則函數正常執行,實現內存複製,並將新分配的內存塊地址返回給調用者。

全局展開導致所有已經開始執行但尚未完成的try_finally塊得以繼續執行。

全局展開流程圖:

 

全局展開示例:

void Fun1()

{

       // 1. Do any processing here.

       ...

       __try

       {

              // 2. Call another function.

              Fun2();

              // Code here never executes.

       }

       __except(/* 6. Evaluate filter.*/EXCEPTION_EXECUTE_HANDLER)

       {

              // 8. After the unwind, the exception handler executes.

              MessageBox(...);

       }

       // 9. Exception handled--continue execution.

}

void Fun2()

{

       DWORD dwTemp = 0;

       // 3. Do any processing here.

       ...

       __try

       {

              // 4. Request permission to access protected data.

              WaitForSingleObject(g_hSem, INFINITE);

              // 5. Modify the data, an exception is generated here.

              g_dwProtectedData = 5 / dwTemp;

       }

       __finally

       {

              // 7. Global unwind occurs because filter evaluated to EXCEPTION_EXECUTE_HANDLER.

              // Allow others to use protected data.

              ReleaseSemaphore(g_hSem, 1, NULL);

       }

       // Continue processing--never executes.

       ...

}

通過流程圖和這個示例,可以理解全局展開的執行順序。

finally塊中的return語句將阻止全局展開,示例:

void Fun1()

{

    __try

    {

        Fun2(); // 1.

    }

    __except(/* 4. */EXCEPTION_EXECUTE_HANDLER)

    {

        MessageBeep(0);

    }

    MessageBox(...); // 7.

}

 

void Fun2()

{

    Fun3(); // 2.

    MessageBox(...); // 6.

}

 

void Fun3()

{

    __try

    {

        strcpy(NULL, NULL); // 3.

    }

    __finally

    {

        return; // 5.

    }

}

數字標號標示執行順序。

上面幾個示例程序中,異常過濾程序均簡單的指定了一個常量,如EXCEPTION_EXECUTION_HANDLER,實際上,可以讓異常過濾程序調用一個函數,在該函數中根據實際情況來返回三個標識符中的其中一個。

經驗法則:“在異常過濾程序中,試圖糾正錯誤,然後返回EXCEPTION_CONTINUE_EXECUTION,讓程序繼續執行”,這一方法要謹慎使用。

GetExceptionCode

DWORD GetExceptionCode(void);

該函數返回一個標識(異常代碼),以表明剛剛發生了什麼類型的異常。如EXCEPTION_INT_DIVIDE_BY_ZERO,表示程序試圖做整數除0運算。該函數只能在異常過濾程序或者異常處理程序中使用,而且不能在異常濾過程序所調用的函數中使用。

異常代碼遵循Windows錯誤代碼規則

GetExceptionInformation

異常發生時,操作系統向線程棧壓入三個數據結構,EXCEPTION_POINTERSEXCEPTION_RECORDCONTEXTEXCEPTION_POINTERS結構如下,它包含的兩個指針成員,分別指向被壓入棧的EXCEPTION_RECORDCONTEXT

typedef struct _EXCEPTION_POINTERS {

  PEXCEPTION_RECORD ExceptionRecord;

  PCONTEXT ContextRecord;

} EXCEPTION_POINTERS;

CONTEXT包含的信息和CPU有關,EXCEPTION_RECORD結構中包含與CPU無關的異常信息。

typedef struct _EXCEPTION_RECORD {

  DWORD ExceptionCode;

  DWORD ExceptionFlags;

  struct _EXCEPTION_RECORD* ExceptionRecord;

  PVOID ExceptionAddress;

  DWORD NumberParameters;

  DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];

} EXCEPTION_RECORD;

通過GetExceptionInformation函數能夠獲取指向該EXCEPTION_POINTERS得指針。不過該函數只能在異常過濾程序中調用,因爲上面三個結構僅在這時纔有效。不過我們當然可以將EXCEPTION_RECORD結構保存起來備用。

LPEXCEPTION_POINTERS GetExceptionInformation(void);

EXCEPTION_RECORD結構:ExceptionCode是異常代碼,同GetExceptionCode返回值。ExceptionFlags,如果是0,標示可以繼續的異常,如果是EXCEPTION_NONCONTINUABLE,標示不能繼續的異常。ExceptionRecord指向剛剛發生的另一個未處理異常的EXCEPTION_RECORD結構。ExceptionAddress是導致異常的cpu指令地址。NumberParametersExceptionInformation一起,用來進一步描述異常信息,通常不使用。

軟件異常,在自己的程序中通過RaiseException函數拋出的異常。

void RaiseException(

  DWORD dwExceptionCode,

  DWORD dwExceptionFlags,

  DWORD nNumberOfArguments,

  const DWORD* lpArguments

);

很多時候,拋出異常比返回錯誤代碼更好,可以避免調用者頻繁判斷調用成功與否並做清理,代碼效率會更高,也更簡潔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章