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程式】示範設定頂層異常過濾函數

#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的互動
當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向預訂的位址空間稀疏調撥存儲器
/************************************************************************
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】示範向量化異常過濾函數的調用
#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,調試器收到最後機會通知,也會彈出相應的對話框讓使用者來選擇操作。
(3)可以自定義軟體異常:隻需單擊“添加”按鈕,然後輸入異常名稱和異常代碼(不能與己有的重複)