C++程式崩潰生成dump
程式在執行時,難免會有一些異常情況發生,特別是在條件不容許去掛偵錯程式的時候,如何快速的定位錯誤的方法就顯得很重要。
日誌一直都是一種很重要的定位錯誤的方法,出得好的日誌可以方便程式設計師快速的定位問題所在。但日誌有時也顯不足:
- 日誌有時只能定位大體錯誤範圍,卻無法確認問題所在,比如程式抓到一個未知的異常。
- 程式有時沒有機會來出日誌,或者能出日誌的時候已經無法獲得和錯誤相關的資訊,比如程式崩潰的時候。
在日誌明顯不足的時候,把程序中相關資料DUMP下來分析就是一個比較實用方便的方法。很多應用都會提供這類功能,以便在程式出現問題時可以把相關的資料發給開發者,方便開發者分析問題。類似Office這樣的應用都會有這個功能,當應用崩潰時會彈出對話方塊,提示是否傳送錯誤相關的資料。
如何在自己程式中也新增類似的功能呢?其實這方面的程式碼很多的,在網上可以搜到很多,也有許多都是弄好的,只要你在自己程式中加幾行程式碼就可以實現。我以前開發的程式就參用過BugTrap來實現這種功能。在CodeProject中有篇文章“Catch All Bugs with BugTrap!”,可以下載它的程式碼。
也可以使用WheatyExceptionReport,它的程式碼定義了一個WheatyExceptionReport型別的全域性變數g_WheatyExceptionReport。在WheatyExceptionReport的建構函式中,程式碼呼叫SetUnhandledExceptionFilter,並且設定異常處理器為WheatyExceptionReport::WheatyUnhandledExceptionFilter。在網上可以搜到程式碼,只有把它加到工程中就可以使用了(針對非託管C++程式碼)
其實要實現這個功能並不是很難,自己也可以呼叫MS DbgHelp.dll中MiniDumpWriteDump來實現。自己實現的好處在於不用增加許多用不到的程式碼。
MiniDumpWriteDump可以匯出程式內部的記憶體、堆疊、控制代碼、執行緒、模組等程式執行相關的資訊,該函式的原型如下(具體細節參考 DbgHelp.h ):
BOOL WINAPI
MiniDumpWriteDump(
IN HANDLE hProcess,
IN DWORD ProcessId,
IN HANDLE hFile,
IN MINIDUMP_TYPE DumpType,
IN CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, OPTIONAL
IN CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, OPTIONAL
IN CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam OPTIONAL
);
使用這個函式我們可以自己寫個比較簡單的函式,在出現問題時候的Dump程序資料,再用WinDbg來分析問題所在。
1/******************************************************************
2
Routine Description:
34
Arguments:
56
Return:
78
Remark:
910
*******************************************************************/11int GenerateMiniDump(HANDLE
hFile, PEXCEPTION_POINTERS pExceptionPointers)
12
{
13
BOOL bOwnDumpFile = FALSE;
14
HANDLE hDumpFile = hFile;
15
MINIDUMP_EXCEPTION_INFORMATION ExpParam;
1617
typedef BOOL (WINAPI * MiniDumpWriteDumpT)(
18
HANDLE,
19
DWORD ,
20
HANDLE ,
21
MINIDUMP_TYPE ,
22
PMINIDUMP_EXCEPTION_INFORMATION ,
23
PMINIDUMP_USER_STREAM_INFORMATION ,
24
PMINIDUMP_CALLBACK_INFORMATION
25
);
2627
MiniDumpWriteDumpT pfnMiniDumpWriteDump = NULL;
28
HMODULE hDbgHelp = LoadLibrary(_T("DbgHelp.dll"));
29if (hDbgHelp)
30
pfnMiniDumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp,"MiniDumpWriteDump");
3132if (pfnMiniDumpWriteDump)
33
{
34if (hDumpFile==NULL || hDumpFile==INVALID_HANDLE_VALUE)
35
{
36
TCHAR szPath[MAX_PATH] = {0};
37
TCHAR szFileName[MAX_PATH] = {0};
38
TCHAR* szAppName = L"XXXXXXXXXX";
39
TCHAR* szVersion = L"v2.0";
40
TCHAR dwBufferSize = MAX_PATH;
41
SYSTEMTIME stLocalTime;
4243
GetLocalTime( &stLocalTime );
44
GetTempPath( dwBufferSize, szPath );
4546
StringCchPrintf( szFileName, MAX_PATH, L"%s%s",
szPath, szAppName );
47
CreateDirectory( szFileName, NULL );
4849
StringCchPrintf( szFileName, MAX_PATH, L"%s%s//%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
50
szPath, szAppName, szVersion,
51
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
52
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
53
GetCurrentProcessId(), GetCurrentThreadId());
54
hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE,
55
FILE_SHARE_WRITE|FILE_SHARE_READ, 0,
CREATE_ALWAYS, 0, 0);
5657
bOwnDumpFile = TRUE;
58
}
5960if (hDumpFile!=INVALID_HANDLE_VALUE)
61
{
62
ExpParam.ThreadId = GetCurrentThreadId();
63
ExpParam.ExceptionPointers = pExceptionPointers;
64
ExpParam.ClientPointers = FALSE;
6566
pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
67
hDumpFile, MiniDumpWithDataSegs, (pExceptionPointers ?&ExpParam
: NULL), NULL, NULL);
6869if (bOwnDumpFile)
70
CloseHandle(hDumpFile);
71
}
72
}
7374if (hDbgHelp!=NULL)
75
FreeLibrary(hDbgHelp);
7677return EXCEPTION_EXECUTE_HANDLER;
78
}
引數PEXCEPTION_POINTERS pExceptionPointers可以通過兩種方式取得:
1、結構化異常的__except內通過GetExceptionInformation()取得。
__try
{
...
}
__except(GenerateMiniDump(hFile, GetExceptionInformation()),EXCEPTION_CONTINUE_EXECUTION)
{
}
其實在使用GenerateMiniDump也可以不用傳入LPEXCEPTION_POINTERS值,有它只是方便WinDbg使用命令.ecxr 直接顯示出現問題的位置,比如在C++異常Catch中抓到一個未知的異常,這時雖然不能取得EXCEPTION_POINTERS指標,但是出現異常時的CONTEX指標已經在記憶體中了,只要手工找到它,同樣可以知道出現異常時的環境。
使用WinDbg找異常CONTEXT位置的方法有很多,我平時使用的使用過WinDbg的搜尋記憶體命令來找,檢視WinNt.h中的x86 CONTEXT定義:
1typedef struct _CONTEXT {23//4// The flags values within this flag control the contents of
5// a CONTEXT record.
6//7// If the context record is used as an input parameter, then
8// for each portion of the context record controlled by a flag
9// whose value is set, it is assumed that that portion of the
10// context record contains valid context. If the context record
11// is being used to modify a threads context, then only that
12// portion of the threads context will be modified.
13//14// If the context record is used as an IN OUT parameter to capture
15// the context of a thread, then only those portions of the thread's
16// context corresponding to set flags will be returned.
17//18// The context record is never used as an OUT only parameter.
19//
2021 DWORD ContextFlags;
2223//24// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
25// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
26// included in CONTEXT_FULL.
27//
2829 DWORD Dr0;
30 DWORD Dr1;
31 DWORD Dr2;
32 DWORD Dr3;
33 DWORD Dr6;
34 DWORD Dr7;
3536//37// This section is specified/returned if the
38// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
39//
4041 FLOATING_SAVE_AREA FloatSave;
4243//44// This section is specified/returned if the
45// ContextFlags word contians the flag CONTEXT_SEGMENTS.
46//
4748 DWORD SegGs;
49 DWORD SegFs;
50 DWORD SegEs;
51 DWORD SegDs;
5253//54// This section is specified/returned if the
55// ContextFlags word contians the flag CONTEXT_INTEGER.
56//
5758 DWORD Edi;
59 DWORD Esi;
60 DWORD Ebx;
61 DWORD Edx;
62 DWORD Ecx;
63 DWORD Eax;
6465//66// This section is specified/returned if the
67// ContextFlags word contians the flag CONTEXT_CONTROL.
68//
6970 DWORD Ebp;
71 DWORD Eip;
72 DWORD SegCs; // MUST BE SANITIZED73 DWORD EFlags; // MUST BE SANITIZED74 DWORD Esp;
75 DWORD SegSs;
7677//78// This section is specified/returned if the ContextFlags word
79// contains the flag CONTEXT_EXTENDED_REGISTERS.
80// The format and contexts are processor specific
81//
8283 BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
8485} CONTEXT;
86878889typedef CONTEXT *PCONTEXT;
其中ContextFlags的值一般都是CONTEXT_ALL,這個巨集的值為0x1003f(如果是其它值也可以算出值),通過在WinDbg中搜索0x1003f就能快速找到異常CONTEXT指標值,如下:
s -d 0 L?FFFFFFFF 0x1003f,從搜尋出的結果中可以比較方便的找到所要的異常CONTEXT指標值,如果找到後使用命令.cxr就可以看到異常發生時的暫存器值、堆疊等資訊。
__try{
...
}
__except(GenerateMiniDump(hFile, GetExceptionInformation()),EXCEPTION_CONTINUE_EXECUTION)
{
}
2、設定的UnhandledExceptionFilter傳人的引數。
SetUnhandledExceptionFilter(UnhandledExceptionFilter);
LONG WINAPI UnhandledExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
if(IsDebuggerPresent())
{
return EXCEPTION_CONTINUE_SEARCH;
}
return GenerateMiniDump(hFile,lpExceptionInfo);
} ============================================================================ 實際程式碼如下:
#include <DbgHelp.h>
//生產DUMP檔案
int GenerateMiniDump(HANDLE hFile, PEXCEPTION_POINTERS pExceptionPointers, PWCHAR pwAppName)
{
BOOL bOwnDumpFile = FALSE;
HANDLE hDumpFile = hFile;
MINIDUMP_EXCEPTION_INFORMATION ExpParam;
typedef BOOL(WINAPI * MiniDumpWriteDumpT)(
HANDLE,
DWORD,
HANDLE,
MINIDUMP_TYPE,
PMINIDUMP_EXCEPTION_INFORMATION,
PMINIDUMP_USER_STREAM_INFORMATION,
PMINIDUMP_CALLBACK_INFORMATION
);
MiniDumpWriteDumpT pfnMiniDumpWriteDump = NULL;
HMODULE hDbgHelp = LoadLibrary(L"DbgHelp.dll");
if (hDbgHelp)
pfnMiniDumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
if (pfnMiniDumpWriteDump)
{
if (hDumpFile == NULL || hDumpFile == INVALID_HANDLE_VALUE)
{
TCHAR szPath[MAX_PATH] = { 0 };
TCHAR szFileName[MAX_PATH] = { 0 };
TCHAR* szAppName = pwAppName;
TCHAR* szVersion = L"v1.0";
TCHAR dwBufferSize = MAX_PATH;
SYSTEMTIME stLocalTime;
GetLocalTime(&stLocalTime);
GetTempPath(dwBufferSize, szPath);
StringCchPrintf(szFileName, MAX_PATH, L"%s%s", szPath, szAppName);
CreateDirectory(szFileName, NULL);
StringCchPrintf(szFileName, MAX_PATH, L"%s%s//%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp",
szPath, szAppName, szVersion,
stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond,
GetCurrentProcessId(), GetCurrentThreadId());
hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
bOwnDumpFile = TRUE;
OutputDebugString(szFileName);
}
if (hDumpFile != INVALID_HANDLE_VALUE)
{
ExpParam.ThreadId = GetCurrentThreadId();
ExpParam.ExceptionPointers = pExceptionPointers;
ExpParam.ClientPointers = FALSE;
pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
hDumpFile, MiniDumpWithDataSegs, (pExceptionPointers ? &ExpParam : NULL), NULL, NULL);
if (bOwnDumpFile)
CloseHandle(hDumpFile);
}
}
if (hDbgHelp != NULL)
FreeLibrary(hDbgHelp);
return EXCEPTION_EXECUTE_HANDLER;
}
LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
if (IsDebuggerPresent())
{
return EXCEPTION_CONTINUE_SEARCH;
}
return GenerateMiniDump(NULL, lpExceptionInfo, L"test");
}
//程式入口函式
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
//加入崩潰dump檔案功能
SetUnhandledExceptionFilter(ExceptionFilter);
return 0;
}