1. 程式人生 > >windows 異常處理

windows 異常處理

為了程式的健壯性,windows 中提供了異常處理機制,稱為結構化異常,異常一般分為硬體異常和軟體異常,硬體異常一般是指在執行機器指令時發生的異常,比如試圖向一個擁有隻讀保護的頁面寫入內容,或者是硬體的除0錯誤等等,而軟體異常則是由程式設計師,呼叫RaiseException顯示的丟擲的異常。對於一場處理windows封裝了一整套的API,平臺上提供的異常處理機制被叫做結構化異常處理(SEH)。不同於C++的異常處理,SEH擁有更為強大的功能,並且採用C風給的程式碼編寫方式。

異常處理機制的流程簡介

一般當程式發生異常時,使用者程式碼停止執行,並將CPU的控制權轉交給作業系統,作業系統接到控制權後,將當前執行緒的環境儲存到結構體CONTEXT中,然後查詢針對此異常的處理函式。系統利用結構EXCEPTION_RECORD儲存了異常描述資訊,它與CONTEXT一同構成了結構體EXCEPTION_POINTERS,一般在異常處理中經常使用這個結構體。
異常資訊EXCEPTION_RECORD的定義如下

typedef struct _EXCEPTION_RECORD
 { 
 DWORD ExceptionCode;  //異常碼
 DWORD ExceptionFlags;  //標誌異常是否繼續,標誌異常處理完成後是否接著之前有問題的程式碼
 struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一個異常節點的指標,這是一個連結串列結構
 PVOID ExceptionAddress; //異常發生的地址
 DWORD NumberParameters; //異常附加資訊
 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //異常的字串
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;

當系統在使用者程式中查詢異常處理程式碼時主要通過查詢當前的這個連結串列。
下面詳細說明異常發生時作業系統是如何處理的:
1. 如果程式是被除錯執行的(比如我們在VS編譯器中除錯執行程式),當異常發生時,系統首先將異常資訊交給除錯程式,如果除錯程式處理了那麼程式繼續執行,否則系統便在發生異常的執行緒棧中查詢可能的處理程式碼。若找到則處理異常,並繼續執行程式
2. 如果線上程棧中沒有找到,則再次通知除錯程式,如果這個時候仍然不能處理這個異常,那麼作業系統會對異常程序預設處理,比如強制終止程式。

SEH的基本框架

結構化異常處理一般有下面3個部分組成:
1. 保護程式碼體
2. 過濾表示式
3. 異常處理塊
其中保護程式碼體:是指有可能發生異常的程式碼,一般在SEH中是用__try{}包含的那部分
過濾表示式:是在__except表示式的括號中的部分,一般可以是函式或者表示式,過濾表示式一般只能返回3個值:EXCEPTION_CONTINUE_SEARCH表示繼續向下尋找異常處理的程式,也就是說本__exception不能處理這個異常;EXCEPTION_CONTINUE_EXECUTION表示異常已被處理,繼續執行當初發生異常的程式碼;EXCEPTION_EXECUTE_HANDLER:表示異常已被處理,直接跳轉到__exception(){}程式碼塊中執行,這個時候就有點像C++中的異常處理了。一般一個__try塊可以跟隨多個__except塊
異常處理塊:是指__except大括號中的程式碼塊
另外可以在過濾表示式中呼叫GetExceptionCode和GetExceptionInformagtion函式取得正在處理的異常資訊,這兩個函式不能再過濾表示式中使用,但是可以作為過濾表示式中的函式引數。
下面是一個異常處理的簡單的例子:

#define PAGELIMIT   1024
DWORD dwPageCnt = 0;
LPVOID lpPrePage = NULL;
DWORD dwPageSize = 0;
INT FilterFunction(DWORD dwExceptCode)
{
    if(EXCEPTION_ACCESS_VIOLATION != dwExceptCode)
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    if(dwPageCnt >= PAGELIMIT)
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    if(NULL == VirtualAlloc(lpPrePage, dwPageSize, MEM_COMMIT, PAGE_READWRITE))
    {
        return EXCEPTION_EXECUTE_HANDLER;
    }

    lpPrePage = (char*)lpPrePage + dwPageSize;
    dwPageCnt++;
    return EXCEPTION_CONTINUE_EXECUTION;
}

int _tmain(int argc, TCHAR *argv[])
{
    SYSTEM_INFO si = {0};
    GetSystemInfo(&si);
    dwPageSize = si.dwPageSize;
    char* lpBuffer = (char*)VirtualAlloc(NULL, dwPageSize * PAGELIMIT, MEM_RESERVE, PAGE_READWRITE);
    lpPrePage = lpBuffer;
    for(int i = 0; i < PAGELIMIT * dwPageSize; i++)
    {
        __try
        {
            lpBuffer[i] = 'a';
        }
        __except(FilterFunction(GetExceptionCode()))
        {
            ExitProcess(0);
        }
    }

    VirtualFree(lpBuffer, dwPageSize * PAGELIMIT, MEM_FREE);
    return 0;
}

這段程式碼我們通過結構化異常處理實現了記憶體的按需分配,首先程式保留了4M的地址空間,但是並沒有對映到具體的實體記憶體,接著向這4M的空間中寫入內容,這個時候會造成非法的記憶體訪問異常,系統會執行過濾表示式中呼叫的函式,在函式中校驗異常的異常碼,如果不等於EXCEPTION_ACCESS_VIOLATION,也就是說這個異常並不是讀寫非法記憶體造成的,那麼直接返回EXCEPTION_EXECUTE_HANDLER,這個時候會執行__exception塊中的程式碼,也就是結束程式,如果是由於訪問非法記憶體造成的,並且讀寫的範圍沒有超過4M那麼就提交一個物理頁面供程式使用,並且返回EXCEPTION_CONTINUE_EXECUTION,讓程式接著從剛才的位置執行也就是說再次執行寫入操作,這樣保證了程式需要多少就提交多少,節約了實體記憶體。

終止處理塊

終止處理塊是結構化異常處理特有的模組,它保證了當__try塊執行完成後總會執行終止處理塊中的程式碼。一般位於__finally塊中。只有當執行緒在__try中結束,也就是在__try塊中呼叫ExitProcess或者ExitThread。由於系統為了保證__try塊結束後總會呼叫__finally所以某些跳轉語句如:goto return break等等就會新增額外的機器碼以便能夠跳入到__try塊中,所以為了效率可以用__leave語句代替這些跳轉語句。另外需要注意的一點是一個__try只能跟一個__finally塊但是可以跟多個__except塊。同時__try塊後面要麼跟__except要麼跟__finally這兩個二選一,不能同時跟他們兩個。

丟擲異常

在SEH中丟擲異常需要使用函式:RaiseException,它的原型如下:

void WINAPI RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, const ULONG_PTR* lpArguments);

第一個是異常程式碼,第二個引數是異常標誌,第三個是異常引數個數,第四個是引數列表,這個函式主要是為了填充EXCEPTION_RECORD結構體並將這個節點新增到連結串列中,當發生異常時系統會查詢這個連結串列,下面是一個簡單的例子:

DWORD FilterException()
{
    wprintf(_T("1\n"));
    return EXCEPTION_EXECUTE_HANDLER;
}

int _tmain(int argc, TCHAR *argv[])
{
    __try
    {
        __try
        {
            RaiseException(1, 0, 0, NULL);
        }
        __finally
        {
            wprintf(_T("2\n"));
        }
    }
    __except(FilterException())
    {
        wprintf(_T("3\n"));
    }

    _tsystem(_T("PAUSE"));
    return 0;
}

上面的程式使用RaiseException丟擲一個異常,按照異常處理的流程,程式首先會試著執行FilterException,以便處理這個異常,所以首先會輸出1,然後根據返回值EXCEPTION_EXECUTE_HANDLER決定下一步會執行異常處理塊__except中的內容,這個時候也就表示最裡面的__try塊執行完了,在前面說過,不管遇到什麼情況,執行完__try塊,都會接著執行它對應的__finally塊,所以這個時候會首先執行__finally塊,最後執行外層的__except塊,最終程式輸出結果為1 2 3

win32下的向量化異常處理

為什麼向量化異常要強調是win32下的呢,因為64位windows不支援這個特性
理解這個特性還是回到之前說的作業系統處理異常的順序上面,首先會交給除錯程式,然後再由使用者程式處理,根據過濾表示式返回的值決定這個異常是否被處理,而這個向量化異常處理,就是將異常處理的程式碼新增到這個之前,它的程式碼會先於過濾表示式之前執行。
我們知道異常是由內層向外層一層一層的查詢,如果在內層已經處理完成,那麼外層是永遠沒有機會處理的,這種情況在我們使用第三方庫開發應用程式,而這個庫又不提供原始碼,並且當發生異常時這個庫只是簡單的將執行緒終止,而我們想處理這個異常,但是由於內部處理了,外層的try根本捕獲不到,這個時候就可以使用向量化異常處理了。這樣我們可以編寫異常處理程式碼先行處理並返回繼續執行,這樣庫中就沒有機會處理這個異常了。
使用這個機制通過AddVectoredExceptionHandler函式可以新增向量化異常處理過濾函式,而呼叫RemoveVectoredExceptionHandler可以移除一個已新增的向量化異常處理過濾函式。下面是一個簡單的例子:

int g_nVal = 0;
void Func(int nVal)
{
    __try
    {
        nVal /= g_nVal;
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        printf("正在執行Func中的__try __except塊\n");
        ExitProcess(0);
    }
}

LONG CALLBACK VH1(PEXCEPTION_POINTERS pExceptionInfo)
{
    printf("正在執行VH1()函式\n");
    return EXCEPTION_CONTINUE_SEARCH;
}

LONG CALLBACK VH2(PEXCEPTION_POINTERS pExceptionInfo)
{
    printf("正在執行VH2()函式\n");
    if (EXCEPTION_INT_DIVIDE_BY_ZERO == pExceptionInfo->ExceptionRecord->ExceptionCode)
    {
        g_nVal = 25;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

LONG CALLBACK VH3(PEXCEPTION_POINTERS pExceptionInfo)
{
    printf("正在執行VH3()函式\n");
    return EXCEPTION_CONTINUE_SEARCH;
}

LONG SEH1(EXCEPTION_POINTERS *pEP)
{
    //除零錯誤
    if (EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode)
    {
        g_nVal = 34;
        return EXCEPTION_EXECUTE_HANDLER;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

int _tmain(int argc, TCHAR *argv[])
{
    LPVOID lp1 = AddVectoredExceptionHandler(0, VH1);
    LPVOID lp2 = AddVectoredExceptionHandler(0, VH2);
    LPVOID lp3 = AddVectoredExceptionHandler(1, VH3);

    __try
    {
        Func(g_nVal);
        printf("Func()函式執行完成後g_nVal = %d\n", g_nVal);
    }
    __except(SEH1(GetExceptionInformation()))
    {
        printf("正在執行main()中的__try __except塊");
    }

    RemoveVectoredExceptionHandler(lp1);
    RemoveVectoredExceptionHandler(lp2);
    RemoveVectoredExceptionHandler(lp3);

    return 0;
}

上述的程式模擬了呼叫第三方庫的情況,比如我們呼叫了第三方庫Func進行某項操作,我們在外層進行了異常處理,但是由於在Func函式中有異常捕獲的程式碼,所以不管外層如何處理,總不能捕獲到異常,外層的異常處理程式碼總是不能執行,這個時候我們註冊了3個向量處理函式,由於VH1返回的是EXCEPTION_CONTINUE_SEARCH,這個時候會在繼續執行後面註冊的向量函式——VH2,VH2返回EXCEPTION_CONTINUE_SEARCH,會繼續執行VH3,VH3還是返回EXCEPTION_CONTINUE_SEARCH,那麼它會繼續執行庫函式內層的異常處理,內層的過濾表示式返回EXCEPTION_EXECUTE_HANDLER,這個時候會繼續執行異常處理塊中的內容,結束程式,如果我們將3個向量函式中的任何一個的返回值改為EXCEPTION_CONTINUE_EXECUTION,那麼庫中的異常處理塊中的內容將不會被執行。
函式AddVectoredExceptionHandler中填入的處理函式也就是上述程式碼中的VH1 VH2 VH3只能返回EXCEPTION_CONTINUE_EXECUTION和EXCEPTION_CONTINUE_SEARCH,對於其他的值作業系統不認。

將SEH轉化為C++異常

C++異常處理並不能處理所有型別的異常而將SEH和C++異常混用,可以達到使用C++異常處理處理所有異常的目的
要混用二者需要在專案屬性->C/C++->程式碼生成->啟動C++異常的選項中開啟SEH開關。
在混用時可以在SEH的過濾表示式的函式中使用C++異常,當然最好的方式是將SEH轉化為C++異常。
通過呼叫_set_se_translator這個函式指定一個規定格式的回撥函式指標就可以利用標準C++風格的關鍵字處理SEH了。下面是它們的定義:

_set_se_translator(_se_translator_function seTransFunction);
typedef void (*_se_translator_function)(unsigned int, struct _EXCEPTION_POINTERS* );

使用時,需要自定義實現_se_translator_function函式,在這個函式中通常可以通過throw一個C++異常的方式將捕獲的SEH以標準C++EH的方式丟擲
下面是一個使用的例子:

class SE_Exception
{
public:
    SE_Exception(){};
    SE_Exception(DWORD dwErrCode) : dwExceptionCode(dwErrCode){};
    ~SE_Exception(){};
private:
    DWORD dwExceptionCode;
};

void STF(unsigned int ui,  PEXCEPTION_POINTERS pEp)
{
    printf("執行STF函式\n");
    throw SE_Exception();
}

void Func(int i)
{
    int x = 0;
    int y = 5;
    x = y / i;
}

int _tmain(int argc, TCHAR *argv[])
{
    try
    {
        _set_se_translator(STF);
        Func(0);
    }
    catch(SE_Exception &e)
    {
        printf("main 函式中捕獲到異常 \n");
    }
    return 0;
}

程式首先呼叫_set_se_translator函式定義了一個回掉函式,當異常發生時,系統呼叫回掉函式,在函式中丟擲一個自定義的異常類,在主函式中使用C++的異常處理捕獲到了這個異常併成功輸出了一條資訊。

相關推薦

windows 異常處理中VEH、SEH、UEH、VCH 之間的關系

異常1.當異常交由用戶處理時,按照以下順序調用異常處理方式VEH-〉SEH-〉VCH 。2.當VEH表示處理了異常,就不會傳遞給SEH,但是會傳遞異常給VCH 。3.當VEH沒有處理了,就會傳遞給SEH。4.當SEH的所有異常處理函數沒能夠處理異常,會調用默認的SEH(就是UEH,只是方式屬於SEH)處理函數

C++及Windows異常處理(try,catch; __try,__finally; __try, __except)

C++及Windows異常處理(try,catch; __try,__finally; __try, __except) 題目: int* p = 0x00000000; // pointer to NULL puts( "hello "); __try{ puts( "

Windows異常處理

對異常處理的研究不夠多,這個也源於工作中專案框架一般比較完善的原因,總結一下Windows下C++開發中常用的異常處理方式: 1. 語言自帶異常邏輯 try catch,這個應該是最熟悉的了。C++支援的方式,跨平臺。不足的地方在於只能捕獲語言的異常,就是有有刻意在邏輯中throw出來的。

C++及Windows異常處理(try,catch; __try,__finally; __try, __except)——一道筆試題引起的探究

轉載自 http://www.blogbus.com/shijuanfeng-logs/178616871.html 題目: int* p = 0x00000000; // pointer to NULL put

windows 異常處理

為了程式的健壯性,windows 中提供了異常處理機制,稱為結構化異常,異常一般分為硬體異常和軟體異常,硬體異常一般是指在執行機器指令時發生的異常,比如試圖向一個擁有隻讀保護的頁面寫入內容,或者是硬體的除0錯誤等等,而軟體異常則是由程式設計師,呼叫RaiseEx

windows異常處理 __try __except

try-except用法   try except是windows 系統獨有的異常處理模型,windows的異常處理模式,稱為SEH( structured exception handling ),        SEH的異常處理模型主要由try-except語句來完成,與

Windows結構化異常處理淺析

null 崩潰 plc 處理程序 了解 got AC doc pdo 近期一直被一個問題所困擾,就是寫出來的程序老是出現無故崩潰,有的地方自己知道可能有問題,但是有的地方又根本沒辦法知道有什麽問題。更苦逼的事情是,我們的程序是需要7x24服務客戶,雖然不需要實時精準零差錯,

Windows結構化異常處理(SEH) - by Matt Pietrek

原文題目: A Crash Course on the Depths of Win32™ Structured Exception Handling 作者: Matt Pietrek   About Matt Pietrek Matt Pietrek (

Error:Execution failed for task toolchains\mips64el-linux-android-4.9\prebuilt\windows-x86_64.異常處理

今天接入Bmob後,工程出現了這個異常 Error:Execution failed for task ‘:app:transformNativeLibsWithStripDebugSymbolForDebug’. > A problem occurred

Windows異常處理優先順序

偵錯程式 VEH,頂端向量化異常處理,AddVectoredExceptionHandler SEH,結構化異常處理,__try{}__except{} UEF,頂級異常處理,SetUnhandledExceptionFilter VCH,底端向量化異常處

第八章——Windows異常處理-異常處理基本概念

前言: 中斷和異常的區別,中斷是由外部硬體裝置或非同步事件產生的。異常是由內部事件產生,可以分為故障,陷阱和終止三類。 由CPU引發的異常成為硬體異常,例如訪問一個無效的記憶體地址。由作業系統或應用程式引發的異常成為軟體異常。 我們也可以主動丟擲一個異常,通過RaiseExcept

通過兩道題目理解windows異常處理機制

關於windows異常處理機制最經典的文章應該是A Crash Course on the Depths of Win32™ Structured Exception Handling。強烈建議沒有讀過的讀者仔細閱讀這篇文章,這裡就不囉嗦了。 題目下載:htt

windows系統安裝軟體提示登錄檔資訊錯誤異常處理方法

案例一:   SQLServer安裝錯誤之------>無法開啟項 UNKNOWN\Components\DA42BC89BF25F5BD0AF18C3B9B1A1EE8\c1c4f01781cc94c4c8fb1542c0981a2a 案例二:   AppScan安裝報錯提示----->無

第八章——Windows異常處理-SEH的概念及基本知識

1.SEH相關結構     ①TIB         TIB是儲存執行緒基本資訊結構體,它位於TEB頭部,而TEB在FS:[0]處(0X7FFDE000)之前的筆記中提及過。具體的TIB結構如下:  &

windows程式崩潰對話方塊和異常處理

    經常碰到某些程式崩潰時彈出帶紅色叉叉的錯誤視窗或者是叫你選擇除錯或關閉的視窗,很礙眼。不過平時也沒去理它,點掉就好。     今天客戶反映我們的程式崩潰後就起不來了,其實我們為了方便無人化管理,做了一個守護程序。如果程式異常退出就會重啟那個程式,這在linux下沒

Windows 系統異常處理順序總結

首先要明白異常處理是分層的,有:1.核心異常處理 2.偵錯程式異常處理 3.程序VEH4.執行緒SEH 5.系統預設的異常處理函式 UnhandledExcetionFilter(). SetUnhandledExceptionFilter()來註冊新的Top Level

windows核心程式設計--SEH(結構異常處理

SEH 的工作原理。          Windows 程式設計中最重要的理念就是訊息傳遞,事件驅動。當GUI應用程式觸發一個訊息時,系統將把該訊息放入訊息佇列,然後去查詢並呼叫窗體的訊息處理函式(CALLBACK),傳遞的引數當然就是這個訊息。我們同樣可以把異常也當作是

漫談相容核心之二十四:Windows的結構化異常處理(一)

結構化異常處理(Structured Exception Handling),簡稱SEH,是Windows作業系統的一個重要組成部分。 在ReactOS核心的原始碼中,特別是在實現系統呼叫的程式碼中,讀者已經看到很多類似於這樣的程式碼:    if(MaximumSize

Windows核心程式設計》讀書筆記二十五章 未處理異常,向量化異常處理與C++異常

第二十五章  未處理異常,向量化異常處理與C++異常 本章內容 25.1 UnhandledExceptionFilter函式詳解 25.2 即時除錯 25.3 電子表格示例程式 25.4 向量化異常和繼續處理程式 25.5 C++異常與結構化異常的比較 25.6 異常與

Laravel 5.1 中的異常處理器和HTTP異常處理 abort()

錯誤日誌 exce ant upload 記錄 再次 .org splay don 原文 http://laravelacademy.org/post/1867.html 錯誤和異常是處理程序開發中不可回避的議題,在本地開發中我們往往希望能捕獲程序拋出的異常並將其顯示打印