天天看點

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香

25.1 UnhandledExceptionFilter函數詳解

25.1.1 BaseProcessStart僞代碼(Kernel32内部)

void BaseProcessStart(PVOID lpfnEntryPoint) //參數為線程函數的入口位址
{
     DWORD retValue;
     DWORD currentESP;
     DWORD exceptionCode;
     currentESP = ESP; 

     //lpfnEntryPoint被try/except封裝着,這是系統安裝的預設的異常處理程式,也是SEH鍊上最後一個異常處理程式
     __try
     {
         NtSetInformationThread(GetCurrentThread(),
                                    ThreadQuerySetWin32StartAddress,
                                    &lpfnEntryPoint,
                                    sizeof(lpfnEntryPoint));

         retValue = lpfnEntryPoint();
         ExitThread(retValue); //如果異常,線程從這裡退出!
     }
     __except (   //過濾器表達式代碼
                exceptionCode = GetExceptionInformation(),
                UnhandledExceptionFilter(GetExceptionInformation())) //出現異常會調用Unhandled...這個函數,該函數内部會調用
                                                                     //使用者通過SetUnhandledFilter設定的全局異常處理函數。                     
     {
         //如果UnhandledExceptionFilter傳回EXCEPTION_EXECUTE_HANDLER,則會控制流會執行到這裡
         ESP = currentESP;
if (!_BaseRunningInServerProcess) //普通程序,則退出程序
              ExitProcess(exceptionCode);
         else // 線程是作為服務來運作的,隻退出線程并不終止整個服務
              ExitThread(exceptionCode);
     }
}      

(1)如果異常過濾程式傳回EXCEPTION_CONTINUE_SEARCH時,系統會繼續向外層尋找異常過濾程式。但如果每個異常過濾程式都傳回EXCEPTION_CONTINUE_SEARCH時,會未到遇處理異常。

(2)調用SetUnhandledExceptionFilter安裝使用者提供的全局(頂層)異常過濾回調函數(為所有線程共享)。如果頂層異常回調函數傳回EXCEPTION_EXECUTE_HANDLER或EXCEPTION_CONTINUE_SEARCH則直接傳遞給UnhandledExceptionFilter函數,UnhandledExceptionFilter根據這個傳回值判斷是終止程序還是重新執行異常代碼。如果頂層異常回調函數傳回EXCEPTION_CONTINUE_SEARCH,則接下來的要發生的事情就比較複雜(可參考後面的《UnhandledExceptionFilter内部工作流程》)

(3)SetUnhandledExceptionFilter傳回值為上次安裝的異常過濾程式的位址。如果使用C/C++運作庫,則會預設安裝一個__CxxUnhandledExceptionFilter過濾程式。該函數首先檢查異常是不是C++異常,如果是則在結束時執行abort函數(該函數内部調用了UnhandledExceptionFilter函數,注意這可能會造成循環調用,因為UnhandledExceptionFilter内部調用了我們安裝的全局異常過濾函數_CxxUnhandledExceptionFilter,而這個函數的内部又調用UnhandledExceptionFilter,為了防止無限遞歸調用,_CxxUnhandledExceptionFilter在調用UnhandledExceptionFilter之前會調用SetUnhandledExceptionFilter(NULL))。如果不是C++異常則傳回EXCEPTION_CONTINUE_SEARCH。是以當我們調用SetUnhandled*函數,傳回的位址為_CxxUnhandledExceptionFilter的位址。

(4)注意,在我們的頂層異常過濾函數裡,在傳回EXCEPTION_CONTINUE_SEARCH前,不應調用之前的全局異常過濾函數(即我們通過SetUnhandledExceptionFilter的傳回值取得的那個函數)。因為如果這個函數是在某個動态庫裡,那它随時都可能被解除安裝了。

(5)如果SetUnhandledExceptionFilter(NULL),則取消我們設定的全局異常過濾函數。

【UnhandledExceptionFilter程式】示範設定頂層異常過濾函數

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香
#include <tchar.h>
#include <windows.h>
#include <locale.h>

LONG WINAPI MyUnhandledExceptionFilter(
struct _EXCEPTION_POINTERS *lpTopLevelExceptionFilter)
{
    _tprintf(_T("發生未處理異常\n"));
    _tsystem(_T("PAUSE"));
    return EXCEPTION_EXECUTE_HANDLER; //這樣傳回,程序将被終止。
}

int _tmain()
{
    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); //安裝使用者自定義的未處理異常

    _tsetlocale(LC_ALL, _T("chs"));
    __try{
        //SetErrorMode(SEM_NOGPFAULTERRORBOX);

        *(int*)0 = 5;//引發異常
    }
    __except (EXCEPTION_CONTINUE_SEARCH){ //這裡傳回EXCEPTION_CONTINUE_SEARCH,異常就會到達MyUnhandled*
        
    }
    
    _tsystem(_T("PAUSE"));//這行不會被執行!
    return 0;
}      

25.1.2 UnhandledExceptionFilter内部工作流程

  ①判斷是否因為對資源進行寫入操作引發的異常。如果是,将資源的隻讀屬性改為可寫入,并傳回EXCEPTION_CONTINUE_EXECUTION以允許失敗的指令再次執行。

  ②确定程序是否被調試。如果被調試,就傳回EXCEPTION_CONTINUE_SEARCH給調試器,通知調試器定位異常指令,并告知我們出了什麼樣的異常。

  ③調用我們設定的頂層異常過濾函數(如果存在的話)。如果頂層過濾函數傳回EXCEPT_EXECUTE_HANDLER或EXCEPTION_CONTINUE_EXECUTION,将直接傳遞給UnhandledExceptionFilter,由它将傳回值給系統。如果傳回EXCEPT_CONTINUE_SEARCH,則跳到第④步。

  ④再次将未處理異常報告給調試器

  ⑤終止程序:如果線程調用SetErrorMode并設定SEM_NOGPFAULTERRORBOX标志,那麼UnhandledExceptFilter會傳回EXCEPTION_EXECUTE_HANDLER,在未處理異常的情況下進行全局展開并執行未執行的finally塊,然後程序終止。

如果沒有調用SetErrorMode函數,UnhandledExceptionFilter會傳回EXCEPTION_CONTINUE_SEARCH。于是系統核心得到程式控制,它将通過ALPC(進階本地過程調用)機制将異常通知給WerSvc(Windows錯誤報告專用服務),然後ALPC先阻塞自己的線程,直到WerSvc執行完畢。

  ⑥UnhandledExceptionFilter與WER的互動

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香
第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香

當WerSvc接到通知時,會先調用CreateProcess來啟動WerFault.exe,然後 WerSvc會等待這個新程序的結束。而WerFault.exe會向我們建立上面的兩個對話框以報告錯誤的發生。當第1個對話框出現時,可以選擇“取消”來終止我們的應用程式,否則過一會兒,會彈出第2個對話框,如果我們選擇“關閉程式”,則WerFault.exe會調用TerminateProcess來結束我們的應用程式。如果選擇“調試”,WerFault.exe會建立一個子程序(調試器),讓他附着在出錯的程式上進行“即時調試”

25.2 即時調試

(1)預設調試器:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug子項下有一個名為Debugger的值,系統通過個值找到調試器。

(2)WerFault.exe會給這個調試器傳入兩個參數:要調試的程序ID和繼承過來的事件句柄(這個句柄由WerSvc服務建立用于通知被調試程序調試也結束)

(3)通過将調試器附着到被調試程序,可以檢視全局、局部和靜态變量的值,也可以設定斷點,檢查函數調用樹等調試工作。

【Spreadsheet程式】通過SEH向預訂的位址空間稀疏調撥存儲器

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香
/************************************************************************
Module: Spreadsheet.cpp
Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
************************************************************************/
#include "../../CommonFiles/CmnHdr.h"
#include "resource.h"
#include "VMArray.h"
#include <tchar.h>
#include <strsafe.h>

//////////////////////////////////////////////////////////////////////////
HWND g_hWnd; //全局的視窗句柄,SEH報告中會用到

const int g_nNumRows = 256;
const int g_nNumCols = 1024;

//聲明單個單元格内容的結構體,每個單元格大小為1024位元組
typedef struct{
    DWORD dwValue;
    BYTE bDummy[1020];
}CELL,*PCELL;

//聲明全個電子表格的資料
////SPREADSHEET類型為一個數組類型,元素類型為CELL及g_nNumRows行g_nNumCols列。
//判讀時,可去掉typedef來看。
typedef CELL SPREADSHEET[g_nNumRows][g_nNumCols];
typedef SPREADSHEET* PSPREADSHEET;

//////////////////////////////////////////////////////////////////////////
//一個電子表格是一個二維數組的CELLs
class CVMSpreadsheet :public CVMArray<CELL>{
public:
    CVMSpreadsheet() :CVMArray<CELL>(g_nNumRows*g_nNumCols){}

private:
    LONG OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
};

//////////////////////////////////////////////////////////////////////////
LONG CVMSpreadsheet::OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful){
    
    TCHAR sz[200];
    StringCchPrintf(sz, _countof(sz), TEXT("非法通路:試圖在0x%8X進行%s操作!"),pvAddressTouched,
                    bAttemptedRead ? TEXT("讀取") : TEXT("寫入"));

    SetDlgItemText(g_hWnd, IDC_LOG, sz);

    LONG lDispostion = EXCEPTION_EXECUTE_HANDLER;

    //隻有寫入操作發生異常時才會送出實體存儲器,讀取操作則不會
    if (!bAttemptedRead){
        //傳回基類的傳回值
        lDispostion = CVMArray<CELL>::OnAccessViolation(pvAddressTouched,
                                                        bAttemptedRead, pep, bRetryUntilSuccessful);
    }
    return (lDispostion);
}

//////////////////////////////////////////////////////////////////////////
//産生一個全局CVMSpreadsheet對象
static CVMSpreadsheet g_ssObject;

//建立一個全局指針,指向電子表格的區域
SPREADSHEET& g_ss = *(PSPREADSHEET)(PCELL)g_ssObject;

//////////////////////////////////////////////////////////////////////////
BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){

    chSETDLGICONS(hWnd, IDI_SPREADSHEET);

    g_hWnd = hWnd; //儲存句柄(For SEH錯誤報告)

    //設定對話框上面控件的預設值
    Edit_LimitText(GetDlgItem(hWnd, IDC_ROW), 3);
    Edit_LimitText(GetDlgItem(hWnd, IDC_COLUMN), 4);
    Edit_LimitText(GetDlgItem(hWnd, IDC_VALUE), 7);

    SetDlgItemInt(hWnd, IDC_ROW, 100, FALSE);
    SetDlgItemInt(hWnd, IDC_COLUMN, 100, FALSE);
    SetDlgItemInt(hWnd, IDC_VALUE, 12345, FALSE);

    return (TRUE);
}

//////////////////////////////////////////////////////////////////////////
void Dlg_OnCommand(HWND hWnd, int id, HWND hwndCtrl, UINT codeNotity){
    int nRow, nCol;
    
    switch (id)
    {
    case IDCANCEL:
        EndDialog(hWnd, id);
        break;

    case IDC_ROW:
        //使用者修改了行數,更新UI
        nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
        EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chINRANGE(0, nRow, g_nNumRows - 1));
        EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chINRANGE(0, nRow, g_nNumRows - 1));
        break;

    case IDC_COLUMN:
        //使用者修改了行數,更新UI
        nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
        EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chINRANGE(0, nCol, g_nNumCols - 1));
        EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chINRANGE(0, nCol, g_nNumCols - 1));
        break;

    case IDC_READCELL:
        //嘗試從使用者選擇的單元格中讀取一個資料
        SetDlgItemText(hWnd, IDC_LOG, TEXT("沒有發生異常!"));
        nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
        nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);
        __try{
            SetDlgItemInt(hWnd, IDC_VALUE, g_ss[nRow][nCol].dwValue, FALSE);
        }
        //ExceptionFilter傳回EXECUTION_CONTINUE_EXECUTE或EXCEPTION_EXECUTE_HANDLER
        //如果送出成功,傳回前者;失敗,傳回後者
        __except (g_ssObject.ExceptionFilter(GetExceptionInformation(),FALSE)){
            //單元格不支援存儲,裡面不含内容
            SetDlgItemText(hWnd, IDC_VALUE, TEXT(""));

        }
        break;

    case IDC_WRITECELL:
        //嘗試向使用者選擇的單元格中寫入資料
        SetDlgItemText(g_hWnd, IDC_LOG, TEXT("沒有發生異常!"));
        nRow = GetDlgItemInt(hWnd, IDC_ROW, NULL, FALSE);
        nCol = GetDlgItemInt(hWnd, IDC_COLUMN, NULL, FALSE);


        //假如單元格不支援存儲,将抛出非法記憶體通路,這時将導緻自動送出存儲器
        //這裡不設定try/except,則異常會讓全局異常(未處理)過濾函數捕獲
        g_ss[nRow][nCol].dwValue = GetDlgItemInt(hWnd, IDC_VALUE, NULL, FALSE);

        break;
    }
}

//////////////////////////////////////////////////////////////////////////
INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
    switch (uMsg)
    {
        chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);
        chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);
    }
    return (FALSE);
}

//////////////////////////////////////////////////////////////////////////
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd){
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_SPREADSHEET), NULL, Dlg_Proc);
    return (0);
}      

//VMArray.h

/************************************************************************
 Module: VMArray.h
 Notices:Copyright(c) 2008 Jeffrey Richter & Christophe Nasarre
 ************************************************************************/
#pragma  once
#include "../../CommonFiles/CmnHdr.h"
#include <tchar.h>


#ifndef _M_IX86
#error "The following code only works for x86!"
#endif

//////////////////////////////////////////////////////////////////////////
//注意:這個C++類是線程不安全的。不能在多線程下同時建立和銷毀該類的執行個體

//但是一旦建立,多線程可同時通路不同的CVMArray對象,也可以通過自己同步的
//方法在多線程下通路同一個CVMArray對象
//////////////////////////////////////////////////////////////////////////

template <class TYPE>
class CVMArray{
public:
    //為數組各元素預訂稀疏的位址空間
    CVMArray(DWORD dwreservElements);

    //釋放
    virtual ~CVMArray();

    //允許通路數組中的一個元素
    operator TYPE*(){ return (m_pArray); }
    operator const TYPE*()const { return (m_pArray); }

    //若送出失敗,可以被優雅的處理
    LONG ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful = FALSE);

protected:
    //虛函數,當非法通路記憶體時,可優雅的處理
    virtual LONG OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead,
                                   PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful);
private:
    static CVMArray* sm_pHead;  //第一個CVMArray對象
    CVMArray* m_pNext;          //下一個VCMArray對象
    TYPE* m_pArray;  //指向一個預訂的區域數組
    DWORD m_cbReserve; //預訂的數組空間的大小

private:
    //通路前一個未處理異常過濾函數
    static PTOP_LEVEL_EXCEPTION_FILTER sm_pfnUnhandledExceptionFilterPrev;

    //當這個類發生異常,調用我們自己的全局異常過濾函數(VS2005以後微軟讓
    // 對CRT (C 運作時庫)的一些與安全相關的代碼做了些改動,使得許多錯誤
    //都不能在SetUnhandledExceptionFilter 捕獲到。
    static LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pep);
    void DisableSetUnhandledExceptionFilter();//使SetUnhandledExceptionFilter函數失效

    //為了達到設定全局異常過濾函數的目的,用向量化
    //AddVectoredContinueHandler來達到設定全局異常過濾函數相同的功能
    //static LONG WINAPI LastVEHandler(PEXCEPTION_POINTERS pep);
    //static PVOID sm_pVEH;


};

//////////////////////////////////////////////////////////////////////////
//向量化異常過濾函數句柄
//template <class TYPE>
//PVOID CVMArray<TYPE>::sm_pVEH = NULL;

//CVMArray對象連結清單的頭
template <class TYPE>
CVMArray<TYPE>* CVMArray<TYPE>::sm_pHead = NULL;

//前一個全局異常過濾函數
template <class TYPE>
PTOP_LEVEL_EXCEPTION_FILTER CVMArray<TYPE>::sm_pfnUnhandledExceptionFilterPrev;


//////////////////////////////////////////////////////////////////////////
template <class TYPE>
CVMArray<TYPE>::CVMArray(DWORD dwreservElements){
    if (sm_pHead == NULL){
        //在建立第1個對象前,安裝我們的全局異常過濾函數
        sm_pfnUnhandledExceptionFilterPrev =
            SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
        DisableSetUnhandledExceptionFilter();//使SetUnhandledExceptionFilter失效

        //sm_pVEH = AddVectoredContinueHandler(0, LastVEHandler);
    }

    m_pNext = sm_pHead;  //下一次節點初始化為連結清單頭部
    sm_pHead = this;     //本對象為連結清單頭

    m_cbReserve = sizeof(TYPE)*dwreservElements;

    //預訂整個數組大小的一塊區域
    m_pArray = (TYPE*)VirtualAlloc(NULL, m_cbReserve, MEM_RESERVE | MEM_TOP_DOWN,
                                   PAGE_READWRITE);
    chASSERT(m_pArray != NULL);
}

//////////////////////////////////////////////////////////////////////////
template <class TYPE>
CVMArray<TYPE>::~CVMArray(){
    //釋放數組所占的空間
    VirtualFree(m_pArray, 0, MEM_RELEASE);

    //删除連結清單
    CVMArray* p = sm_pHead;
    if (p == this){
        sm_pHead = p->m_pNext; //删除連結清單頭
    }else{
        BOOL bFound = FALSE;

        //周遊鍊頭,并修複指針
        for (; !bFound && (p->m_pNext != NULL);p= p->m_pNext){
            if (p->m_pNext == this){
                p->m_pNext = p->m_pNext->m_pNext;
                bFound = TRUE;
                break;
            }
        }
        chASSERT(bFound);
    }

    //if (sm_pVEH != NULL)
    //    RemoveVectoredExceptionHandler(sm_pVEH);
}

//////////////////////////////////////////////////////////////////////////
//當非法通路時,預設的異常處理
template <class TYPE>
LONG CVMArray<TYPE>::OnAccessViolation(PVOID pvAddressTouched, BOOL bAttemptedRead, PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful)
{
    BOOL bCommittedStorage = FALSE; //假定送出失敗
    do{
        //送出存儲器
        bCommittedStorage = (NULL != VirtualAlloc(pvAddressTouched,
            sizeof(TYPE),MEM_COMMIT,PAGE_READWRITE));

        //假如無法送出而我們又試圖重試,提醒使用者釋放記憶體
        if (!bCommittedStorage && bRetryUntilSuccessful)
            MessageBox(NULL, TEXT("請關閉一些其他應用,然後按“确定”!"),
            TEXT("記憶體空間不足"),MB_ICONWARNING | MB_OK);

    } while (!bCommittedStorage && bRetryUntilSuccessful);

    //當送出存儲器,重新執行出錯代碼。否則執行異常處理程式
    return (bCommittedStorage 
                ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER);
}

//////////////////////////////////////////////////////////////////////////
//過濾函數被關聯到單一的CVMArray對象
template <class TYPE>
LONG CVMArray<TYPE>::ExceptionFilter(PEXCEPTION_POINTERS pep, BOOL bRetryUntilSuccessful /* = FALSE */){
    //預設,送出給其它過濾函數處理(這是一個安全的選擇)
    LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;

    //隻修改非法通路記憶體的異常
    if (pep->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
        return (lDispostion);

    //擷取試圖通路的位址,以及讀或寫異常
    //對于EXCEPTION_ACCESS_VIOLATION異常,ExceptionInformation[0]指出非法通路的類型
    //0表示線程試圖讀取不能通路的資料;1表示寫入不能通路的資料
    PVOID pvAddrTouched = (PVOID)pep->ExceptionRecord->ExceptionInformation[1];//非法通路的位址
    BOOL bAttempedRead = (pep->ExceptionRecord->ExceptionInformation[0] == 0); //非法通路的類型

    //如果試圖通路的位址在VMArray的預訂的位址空間内
    if ((m_pArray <=pvAddrTouched) && (pvAddrTouched<((PBYTE)m_pArray + m_cbReserve))){
        //通路這個數組,并嘗試解決問題
        lDispostion = OnAccessViolation(pvAddrTouched, bAttempedRead, pep, bRetryUntilSuccessful);
    }
    return (lDispostion);
}

//////////////////////////////////////////////////////////////////////////
//template <class TYPE>
//LONG  CVMArray<TYPE>::LastVEHandler(PEXCEPTION_POINTERS pep){
//    //預設為讓其他過濾器處理
//    LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;
//
//    MessageBox(NULL, TEXT("發生未處理異常"), TEXT("提示"), MB_OK);
//
//    //隻修改非法通路記憶體
//    if (pep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION){
//        //周遊所有連結清單節點
//        for (CVMArray* p = sm_pHead; p != NULL; p = p->m_pNext){
//            //詢問該節點是否可以修複錯誤
//            //注意:這個錯誤必須被修複,否程序會被終止
//            lDispostion = p->ExceptionFilter(pep, TRUE);
//
//            //如果修複了錯誤,就停止循環
//            if (lDispostion != EXCEPTION_CONTINUE_SEARCH)
//                break;
//        }
//    }
//    return (lDispostion);
//}

//新版本的CRT 實作在異常進行中強制删除所有應用程式先前設定的捕獲函數,如下所示:
///* Make sure any filter already in place is deleted. */
//SetUnhandledExceptionFilter(NULL);
//UnhandledExceptionFilter(&ExceptionPointers);
//解決方法是攔截CRT 調用SetUnhandledExceptionFilter 函數,使之無效
template <class TYPE>
void CVMArray<TYPE>::DisableSetUnhandledExceptionFilter()
{
    void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),
                                       "SetUnhandledExceptionFilter");
    if (addr)
    {
        unsigned char code[16];
        int size = 0;
        code[size++] = 0x33;
        code[size++] = 0xC0;
        code[size++] = 0xC2;
        code[size++] = 0x04;
        code[size++] = 0x00;

        DWORD dwOldFlag, dwTempFlag;
        VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
        WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
        VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
    }
}

//////////////////////////////////////////////////////////////////////////
//全局異常過濾函數,為所有CVMArray對象共用
//這個未處理異常很有必須,如果使用者忘記用try/except來處理此類對象發生的異常,可以在這裡
//進行最後的處理!
template <class TYPE>
LONG WINAPI  CVMArray<TYPE>::MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pep){
    //預設為讓其他過濾器處理
    LONG lDispostion = EXCEPTION_CONTINUE_SEARCH;

    //隻修改非法通路記憶體
    if (pep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION){
        //周遊所有連結清單節點
        for (CVMArray* p = sm_pHead; p != NULL;p=p->m_pNext){
            //詢問該節點是否可以修複錯誤
            //注意:這個錯誤必須被修複,否程序會被終止
            lDispostion = p->ExceptionFilter(pep, TRUE);

            //如果修複了錯誤,就停止循環
            if (lDispostion != EXCEPTION_CONTINUE_SEARCH)
                break;
        }    
    }

    //如果節點修複錯誤,試圖調用前一個異常處理來處理
    if (lDispostion == EXCEPTION_CONTINUE_SEARCH)
        lDispostion = sm_pfnUnhandledExceptionFilterPrev(pep);

    return (lDispostion);
}

///////////////////////////////////檔案結束///////////////////////////////      

//resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ 生成的包含檔案。
// 供 25_Spreadsheet.rc 使用
//
#define IDD_SPREADSHEET                 1
#define IDC_LOG                         101
#define IDI_SPREADSHEET                 102
#define IDI_ICON1                       102
#define IDC_ROW                         1001
#define IDC_COLUMN                      1002
#define IDC_COLUMN2                     1003
#define IDC_VALUE                       1003
#define IDC_READCELL                    1004
#define IDC_WRITECELL                   1005

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1001
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif      

//Spreadsheet.rc

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"

/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////////////
// 中文(簡體,中國) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE 
BEGIN
    "#include ""winres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Dialog
//

IDD_SPREADSHEET DIALOGEX 18, 18, 164, 165
STYLE DS_SETFONT | DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Spreadsheet"
FONT 10, "宋體", 400, 0, 0x86
BEGIN
    LTEXT           "單元格大小:\n行數:\n列數:\n總大小:",IDC_STATIC,4,4,56,36
    LTEXT           "1024 位元組\n256\n1024\n256 MB (268,435,456 位元組)",IDC_STATIC,49,4,104,36
    LTEXT           "行 (0-255):",IDC_STATIC,4,45,50,8
    EDITTEXT        IDC_ROW,60,41,40,14,ES_AUTOHSCROLL | ES_NUMBER
    LTEXT           "列(0-1023):",IDC_STATIC,4,65,54,8
    EDITTEXT        IDC_COLUMN,60,61,40,14,ES_AUTOHSCROLL | ES_NUMBER
    PUSHBUTTON      "&讀取單元格",IDC_READCELL,104,61,54,14
    LTEXT           "值:",IDC_STATIC,4,85,21,8
    EDITTEXT        IDC_VALUE,60,81,40,14,ES_AUTOHSCROLL | ES_NUMBER
    PUSHBUTTON      "寫入單元格",IDC_WRITECELL,104,81,54,14
    LTEXT           "記錄檔:",IDC_STATIC,4,100,48,8
    EDITTEXT        IDC_LOG,4,110,156,52,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY
END


/////////////////////////////////////////////////////////////////////////////
//
// DESIGNINFO
//

#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO
BEGIN
    IDD_SPREADSHEET, DIALOG
    BEGIN
    END
END
#endif    // APSTUDIO_INVOKED


/////////////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_SPREADSHEET         ICON                    "Spreadsheet.ico"
#endif    // 中文(簡體,中國) resources
/////////////////////////////////////////////////////////////////////////////



#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//


/////////////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED      

25.3 向量化異常和繼續處理程式

25.3.1向量化異常(vectored exception handing,VEH)——在SEH前被調用

  ①對于多層嵌套的SEH來說,外層的__try_except語句塊可能沒有機會處理被内層嵌套攔截的異常。對于一般軟體來說,這不是太大的問題,但是當内層嵌套的軟體是第三方的庫函數,并且内部以不友好的方式處理了異常,比如:異常退出程序了事,這對整個程式将造成很不利的影響。

  ②此時可以利用向量化異常處理,在正常的SEH之前以合适的方式攔截和處理異常。

  ③當異常發生時,系統在執行SEH過濾程式之前,會先依次調用VEH清單中的每個VEH異常處理函數。

(2)注冊VEH異常處理程式:AddVectoredExceptionHandler(bFirstInTheList,pfnHandler)

  ①參數bFirstInTheList為0表示pfnHandler被添加到清單尾端,非0在清單頭部。

  ②pfnHandler:異常處理函數,如果傳回EXCEPTION_CONTINUE_SEARCH,則重新執行導緻異常的指令,如果傳回EXCEPTION_CONTINUE_SEARCH表示讓VEH連結清單中的其他函數去處理異常,如果所有函數都傳回EXCEPTION_CONTINUE_SEARCH,SEH過濾函數就會被執行。

(3)删除VEH異常處理函數:RemoveVectoredExceptionHandler(pHandler),其中pHandler這個句柄為AddVectoredExceptionHandler的傳回值。

25.3.2 繼續處理程式:——用于實作程式的診斷和跟蹤

(1)安裝:PVOID AddVectoredContinueHandler(bFirstInTheList,pfnHandler);

  ①參數bFirstInTheList為0,表示安裝在繼續處理程式列頭的尾部,非0在頭部。

  ②通過該函數安裝的異常處理程式是在SetUnhandledExceptionFilter安裝的異常處理程式傳回EXCEPTION_CONTINUE_SEARCH之後才被調用。

  ③如果pfnHandler函數傳回EXCEPTION_CONTINUE_EXECUTION重新執行導緻異常的指令,EXCEPTION_CONTINUE_SEARCH讓系統執行它後面的異常處理程式。

(2)删除:RemoveVectoredContinueHandler(pHandler);

 【VectoredExceptionFilter】示範向量化異常過濾函數的調用

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香
#include <windows.h>
#include <tchar.h>
#include <locale.h>

int g_iVal = 0;

//VEH1異常過濾函數
LONG CALLBACK VEH1(struct _EXCEPTION_POINTERS* pEP){
    _tprintf(_T("VEH1\n"));
    return EXCEPTION_CONTINUE_SEARCH;
}

//VEH2異常過濾函數
LONG CALLBACK VEH2(struct _EXCEPTION_POINTERS* pEP){
    _tprintf(_T("VEH2\n"));
    //以下的注釋,可以取消以觀察不同的輸出結果
    //if ((EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode)){
    //    g_iVal = 25;
    //    return EXCEPTION_CONTINUE_EXECUTION;
    //}
    return EXCEPTION_CONTINUE_SEARCH;
}

//VEH3異常過濾函數
LONG CALLBACK VEH3(struct _EXCEPTION_POINTERS* pEP){
    _tprintf(_T("VEH3\n"));
    return EXCEPTION_CONTINUE_SEARCH;
}

//SEH異常過濾函數
LONG SEHFilter(PEXCEPTION_POINTERS pEP){
    _tprintf(_T("SEH\n"));
    if ((EXCEPTION_INT_DIVIDE_BY_ZERO == pEP->ExceptionRecord->ExceptionCode)){
        g_iVal = 34;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

void Fun1(int iVal){
    __try{
        _tprintf(_T("Fun1 g_iVal = %d iVal = %d\n"), g_iVal, iVal);
        iVal /= g_iVal; //這裡發生異常,VEH異常會先被調用!
        _tprintf(_T("Fun1 g_iVal = %d iVal = %d\n"), g_iVal, iVal);
    }
    __except (EXCEPTION_EXECUTE_HANDLER){
        _tprintf(_T("Func1 _except塊執行,程式将退出!\n"));
        _tsystem(_T("PAUSE"));
        ExitProcess(1);
    }
}

int _tmain(){
    _tsetlocale(LC_ALL, _T("chs"));

    PVOID pVEH1 = AddVectoredExceptionHandler(0, VEH1);//安裝到VEH連結清單尾部
    PVOID pVEH2 = AddVectoredExceptionHandler(0, VEH2);//安裝到VEH連結清單尾部
    PVOID pVEH3 = AddVectoredExceptionHandler(0, VEH3);//安裝到VEH連結清單尾部

    __try{
        Fun1(g_iVal);
    }
    __except (SEHFilter(GetExceptionInformation())){
        _tprintf(_T("main _except excuted!\n"));
    }

    RemoveVectoredExceptionHandler(pVEH1);
    RemoveVectoredExceptionHandler(pVEH2);
    RemoveVectoredExceptionHandler(pVEH3);
    _tsetlocale(LC_ALL, _T("chs"));

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

25.4 C++異常與結構化異常的比較

(1)架構的差别

//C++異常

void ChunkyFunky(){

     try{

         //try塊

         ...

         throw 5;

}

     catch (int x){

         //catch塊

         ...

     }

     ...

}

//SEH異常

void ChunkyFunky(){

     __try{

         //try塊

         //...

         RaiseException(Code = 0xE06D7363,//ASCII的“msc”

                          Flag = EXECEPTION_NONCONTINUABLE,

                          Args = 5);

     }

     __except ((ArgType == Integer)?

               EXCEPTION_EXECUTE_HANDLER:

               EXCEPTION_CONTINUE_SEARCH){

         //Catch塊

         ...

     }

     ...

}

(2)SEH和C++異常的比較

  ①SEH是作業系統提供的,它在任何語言中都可以使用,而C++異常隻有在編寫C++代碼時才可以使用。

  ②如果開發C++應用程式,應該使用C++異常,而不是SEH異常,因為C++異常是語言的一部分,編譯器會自動生成代碼來調用C++對象的析構函數,保證對象的釋放。

  ③C++異常也是利用作業系統的SEH來實作的,是以在建立一個C++try塊時,編譯器也會生成一個SEH的__try塊。C++的catch語句對應SEH異常過濾程式,catch塊中的代碼對應SEH __except塊中的代碼。C++的throw語句也是對RaiseException函數的調用。

  ④C++調用throw抛出異常時,都會自動帶EXCEPT_NONCONTINUEABLE标志。這意味着C++不能再次執行錯誤代碼。

  ⑤__except通過比較throw變量的資料類型與C++ catch語句中所用到的變量的資料類型,如果一緻,傳回EXCEPTION_EXECUTE_HANDLER,讓__except塊執行。如果不同,傳回EXCEPTION_CONTINUE_SEARCH繼續向搜尋外層的__try/__except。

25.5 異常與調試器

(1)首次機會通知和最後機會通知:

當某個線程抛出異常裡,作業系統會馬上通知調試器(如果調試器己經附着),這個通知被稱為“首次機會通知(First-Chance Notification)”調試器将響應這個通知,促使線程尋找異常過濾程式。如果所有的異常過濾程式都傳回EXCEPTION_CONTINUE_SEARCH,作業系統會給調試器一個“最後機會通知(Last-Chance Notification)”

(2)每個解決方案(.sln),可以從主菜單“調試”→“異常(x)…”找出“異常”對話框。每個異常代碼為32位,如果勾選“引發”表示每當被調試線程引發異常時,調試器都會收到首次機會通知,此時異常剛剛發生,線程還沒有得到機會執行異常過濾程式。調試器會彈出相應的對話框讓我們選擇操作,我們可以選擇調試,然後在代碼裡設定斷點,檢視變量的值或線程的函數調用棧,如果不勾選“引發”這項,則不會彈出對話框,但調試器收到通知時,會在輸出視窗中顯示一行文字,以表示它收到了這個異常通知,然後允許線程尋找異常過濾程式。 “使用者未處理的”表示異常過濾函數傳回EXCEPTION_CONTINUE_SEARCH,調試器收到最後機會通知,也會彈出相應的對話框讓使用者來選擇操作。

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香

(3)可以自定義軟體異常:隻需單擊“添加”按鈕,然後輸入異常名稱和異常代碼(不能與己有的重複)

第25章 SEH結構化異常處理_未處理異常及向量化異常 - 淺墨濃香