白話windows之四 異常處理機制(VEH、SEH、TopLevelEH...)


原文地址:http://bbs.pediy.com/showthread.php?t=173853

今天是我侄子的4歲生日,特此更新一篇,祝我小侄子在幼兒園多泡MM

我們都知道SEH異常處理機制,那VEH、TopLevelEH呢?他們執行的先后順序是怎樣的呢?當這些機制都不使用的情況下,會發生什么情況呢?異常處理器是怎么工作的?如果你對此感興趣,那我們就一起來扒開異常處理機制的面紗吧

術語:
SEH: 結構化異常處理
VEH: 向量化異常處理
TopLevelEH:頂層異常處理

EXCEPTION_EXECUTE_HANDLER :該異常被處理。從異常處下一條指令繼續執行
EXCEPTION_CONTINUE_SEARCH:不能處理該異常,讓別人處理它吧
EXCEPTION_CONTINUE_EXECUTION:該異常被忽略。從異常處處繼續執行

//調試器返回值:
DBG_CONTINUE : 等同於EXCEPTION_CONTINUE_EXECUTION
DBG_EXCEPTION_NOT_HANDLED :等同於EXCEPTION_CONTINUE_SEARCH


想想對我這等語文是體育老師教出來的童鞋來說,想把這個主題將透徹,還真是有點難度的~,我們還是按異常處理器執行順序來吧,廢話不多說,開始。。。

異常處理器其實包含 內核異常處理R3異常處理內核異常處理比較簡單,我也對它沒興趣,所以這里就把它給忽略了。我們只講R3程序產生異常時,異常處理器是怎么工作的。

異常處理器處理順序流程
1. 交給調試器(進程必須被調試)
2. 執行VEH
3. 執行SEH
4. TopLevelEH(進程被調試時不會被執行)
5. 交給調試器(上面的異常處理都說處理不了,就再次交給調試器)
6. 調用異常端口通知csrss.exe


大致分上面幾步把,下面咱就詳細討論一下各個步驟都干了哪些細活

1. 第一次交給調試器
如果該出現異常的程序正在被調試,則該異常首先交給調試器處理(通過DebugPort)。
調試器拿到這個異常后,需要判斷是否要處理該異常,如果處理該異常返回DBG_CONTINUE,否則返回DBG_EXCEPTION_NOT_HANDLED

代碼:
    while(!bExit) 
    {
        DWORD dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
        DEBUG_EVENT debugEvent;
        WaitForDebugEvent(&debugEvent, INFINITE);
        switch ( debugEvent.dwDebugEventCode )
        {
        case EXCEPTION_DEBUG_EVENT:
            {
                EXCEPTION_DEBUG_INFO* pExcpInfo = &debugEvent.u.Exception;
                if ( MessageBox(0,_T("處理該異常?"), _T("我是調試器"),MB_YESNO)==IDYES )
                {
                    dwContinueStatus = DBG_CONTINUE;
                    //...
                }
            }
            break;
            //...
        }
        ContinueDebugEvent(debugEvent.dwProcessId,  debugEvent.dwThreadId, dwContinueStatus); 
    }

2. 執行VEH
這里就不講Veh的概念了,有興趣的去Google一下。
如果沒有被調試,或者調試器返回DBG_EXCEPTION_NOT_HANDLED,則就會檢查是否存在VEH。如果存在VEH,則把異常交給他們處理。
VEH是個鏈表,可以存在多個Veh。每個VEH按順序被調用
一個VEH可以返回連個值:EXCEPTION_CONTINUE_SEARCHEXCEPTION_CONTINUE_EXECUTION。返回EXCEPTION_EXECUTE_HANDLER無效的,等同於EXCEPTION_CONTINUE_SEARCH。
當一個Veh返回EXCEPTION_CONTINUE_SEARCH,則把異常交給下一個VEH處理。
如果返回EXCEPTION_CONTINUE_EXECUTION,認為已經被處理,退出異常處理器在異常指令處繼續執行
從執行順序來看,VEH是在SEH之前執行的,並且不依賴某一線程,本進程中任何線程出現異常都可以被VEH處理,所以在有些時候是很有用處的。
怎么添加一個VEH呢?

代碼:
LONG NTAPI FirstVectExcepHandler( PEXCEPTION_POINTERS pExcepInfo )
{
    if( ... )
    {
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}
//參數1=1表示插入Veh鏈的頭部,=0表示插入到VEH鏈的尾部
AddVectoredExceptionHandler( 1, &FirstVectExcepHandler );

3. 執行SEH
SEH應該是大家都比較熟悉的了。當所有的VEH都不處理該異常,該異常就會讓SEH處理。
SEH是基於線程棧的異常處理機制,所以它只能處理自己線程的異常。
先看一個示例代碼:

代碼:
LONG FirstSEHer( PEXCEPTION_POINTERS pExcepInfo )
{
    TCHAR* pTitle = _T("第一個SEH處理器");
    _tprintf( _T("[EH.Exe] [SEH][1] in \n") );
    LONG nRet = ShowSelectMessageBox(pTitle);
    _tprintf( _T("[EH.Exe] [SEH][1] out \n") );
    return nRet;
}
LONG SecondSEHer( PEXCEPTION_POINTERS pExcepInfo )
{
    TCHAR* pTitle = _T("第二個SEH處理器");
    _tprintf( _T("[EH.Exe] [SEH][2] in \n") );
    LONG nRet = ShowSelectMessageBox(pTitle);
    _tprintf( _T("[EH.Exe] [SEH][2] out \n") );;
    return nRet;
}
void ExcepFunction()
{
  __try
  {
    __try
    {
      __try
      {

        _tprintf( _T("[EH.Exe] *[CALL] int 3\n") );
        __asm int 3;
      }
      __finally
      {
        printf( "[EH.Exe] *[SEH][0] finally call...\n" );
      }
    }
    __except( FirstSEHer(GetExceptionInformation()) )
    {
      _tprintf( _T("[EH.Exe] [SEH][1] 被俺處理了~(只有返回EXCEPTION_EXECUTE_HANDLER才會走到這里)\n"));
    }
  }
  __except( SecondSEHer(GetExceptionInformation()) )
  {
    _tprintf( _T("[EH.Exe] [SEH][2] 被俺處理了(只有返回EXCEPTION_EXECUTE_HANDLER才會走到這里)\n"));
  }
}

ExcepFunction函數有三個SEH,但是有兩個Headler。當__asm int 3;被執行時就會被SEH捕獲。捕獲后,首先交給FirstSEHer處理,如果FirstSEHer返回EXCEPTION_CONTINUE_SEARCH則才會交給SecondSEHer處理。
FirstSEHer可以返回三個值:EXCEPTION_CONTINUE_SEARCHEXCEPTION_EXECUTE_HANDLEREXCEPTION_CONTINUE_EXECUTION
當返回EXCEPTION_CONTINUE_SEARCH,執行上一層SEH,這里執行SecondSEHer
返回EXCEPTION_EXECUTE_HANDLER時則表示異常被處理,會先把內部的__finally塊執行完,再跳到自身的__except塊中執行。
返回EXCEPTION_CONTINUE_EXECUTION時表示該異常被忽略,會再次執行__asm int 3處指令。如果該條匯編不被修正成其他指令(如nop),則會再次產生一個異常。

另外,如果想在 try catch的C++異常中捕獲系統異常,必須讓C++支持SEH異常處理。設置方法: Vc-〉項目屬性-->配置屬性-->c/C++-->代碼生成-->啟用C++異常,選中"是,但有SEH異常(/EHa)"。

4. TopLevelEH
頂層異常處理,這個其實是利用SEH實現的。在最頂層的SEH中,可以注冊一個頂層異常處理器。雖然他是基於SEH實現的,但是它可以處理所有線程拋出的異常。
當SEH都處理不了該異常,在最頂層的SEH中就會檢查是否注冊了頂層異常處理,如果注冊了,則執行頂層異常處理。
注意:如果該進程正在調試狀態,頂層異常處理會被忽略,不會被執行
頂層異常處理函數也可以返回三個值:EXCEPTION_CONTINUE_SEARCHEXCEPTION_EXECUTE_HANDLEREXCEPTION_CONTINUE_EXECUTION
返回EXCEPTION_CONTINUE_EXECUTION時,和SEH一樣。
返回EXCEPTION_EXECUTE_HANDLER時,則直接殺死該進程
返回EXCEPTION_CONTINUE_SEARCH時,會查注冊表,檢查是否存在實時調試器。注冊表路徑:KLM\software\microsoft\windows nt\currentvsrsion\aedebug。如果Auto==1,Debugger!=NULL則根據Debugger中指示的參數啟動實時調試器,讓調試器處理該異常(如果不存在頂層異常且進程沒被調試,也會檢查並啟動實時調試器)
注冊方法:

代碼:
LONG NTAPI TopLevelExcepFilter( PEXCEPTION_POINTERS pExcepInfo )
{
    TCHAR* pTitle = _T("*頂級* 異常處理器");
    _tprintf( _T("[EH.Exe] [TOP] in \n") );
    LONG nRet = ShowSelectMessageBox(pTitle);
    _tprintf( _T("[EH.Exe] [TOP] out \n") );;
    return nRet;
}
//注冊
SetUnhandledExceptionFilter( &TopLevelExcepFilter );

頂層異常處理通常用來生成程序Dump文件。供開發人員分析。

5. 再次交給調試器
如果上述的異常處理機制都沒有處理該異常,則調試器會再次接收該異常。
調試器這個時候返回DBG_CONTINUE,則和第一次相同。
返回DBG_EXCEPTION_NOT_HANDLED,則直接殺死該進程

6. 調用異常端口通知csrss.exe
當上面提到的都沒有處理該異常,則調用ExceptionPort通知csrss.exe。csrss.exe的做法是會彈出一個對話框:
名稱:  sd.jpg
查看次數: 0
文件大小:  33.7 KB
這個時候還有一次修復異常並讓程序繼續運行的機會,就是點擊“調試”按鈕。其他按鈕都很導致異常進程被終止。

終於寫完了,為了完成該文,特意寫了個小軟件。里面包含一個異常產生程序(ExcepHandler)和一個簡單調試器(MyDbg)源碼在此:
TestException.rar.
(調試器參考了超然兄的代碼,特此感謝)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM