1. 程式人生 > >C++程式崩潰生成dump

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, 00);
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;
}