使用API自己实现Crash时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:
3
4 Arguments:
5
6 Return:
7
8 Remark:
9
10 *******************************************************************/
11 int GenerateMiniDump(HANDLE hFile, PEXCEPTION_POINTERS pExceptionPointers)
12 {
13 BOOL bOwnDumpFile = FALSE;
14 HANDLE hDumpFile = hFile;
15 MINIDUMP_EXCEPTION_INFORMATION ExpParam;
16
17 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 );
26
27 MiniDumpWriteDumpT pfnMiniDumpWriteDump = NULL;
28 HMODULE hDbgHelp = LoadLibrary(_T("DbgHelp.dll"));
29 if (hDbgHelp)
30 pfnMiniDumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");
31
32 if (pfnMiniDumpWriteDump)
33 {
34 if (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;
42
43 GetLocalTime( &stLocalTime );
44 GetTempPath( dwBufferSize, szPath );
45
46 StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );
47 CreateDirectory( szFileName, NULL );
48
49 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);
56
57 bOwnDumpFile = TRUE;
58 }
59
60 if (hDumpFile!=INVALID_HANDLE_VALUE)
61 {
62 ExpParam.ThreadId = GetCurrentThreadId();
63 ExpParam.ExceptionPointers = pExceptionPointers;
64 ExpParam.ClientPointers = FALSE;
65
66 pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
67 hDumpFile, MiniDumpWithDataSegs, (pExceptionPointers ? &ExpParam : NULL), NULL, NULL);
68
69 if (bOwnDumpFile)
70 CloseHandle(hDumpFile);
71 }
72 }
73
74 if (hDbgHelp!=NULL)
75 FreeLibrary(hDbgHelp);
76
77 return EXCEPTION_EXECUTE_HANDLER;
78 }
参数PEXCEPTION_POINTERS pExceptionPointers可以通过两种方式取得:
1、结构化异常的__except内通过GetExceptionInformation()取得。
__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);
}
其实在使用GenerateMiniDump也可以不用传入LPEXCEPTION_POINTERS值,有它只是方便WinDbg使用命令
使用WinDbg找异常CONTEXT位置的方法有很多,我平时使用的使用过WinDbg的搜索内存命令来找,查看WinNt.h中的x86 CONTEXT定义:
1 typedef struct _CONTEXT {
2
3 //
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 //
20
21 DWORD ContextFlags;
22
23 //
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 //
28
29 DWORD Dr0;
30 DWORD Dr1;
31 DWORD Dr2;
32 DWORD Dr3;
33 DWORD Dr6;
34 DWORD Dr7;
35
36 //
37 // This section is specified/returned if the
38 // ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
39 //
40
41 FLOATING_SAVE_AREA FloatSave;
42
43 //
44 // This section is specified/returned if the
45 // ContextFlags word contians the flag CONTEXT_SEGMENTS.
46 //
47
48 DWORD SegGs;
49 DWORD SegFs;
50 DWORD SegEs;
51 DWORD SegDs;
52
53 //
54 // This section is specified/returned if the
55 // ContextFlags word contians the flag CONTEXT_INTEGER.
56 //
57
58 DWORD Edi;
59 DWORD Esi;
60 DWORD Ebx;
61 DWORD Edx;
62 DWORD Ecx;
63 DWORD Eax;
64
65 //
66 // This section is specified/returned if the
67 // ContextFlags word contians the flag CONTEXT_CONTROL.
68 //
69
70 DWORD Ebp;
71 DWORD Eip;
72 DWORD SegCs; // MUST BE SANITIZED
73 DWORD EFlags; // MUST BE SANITIZED
74 DWORD Esp;
75 DWORD SegSs;
76
77 //
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 //
82
83 BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
84
85 } CONTEXT;
86
87
88
89 typedef CONTEXT *PCONTEXT;
其中ContextFlags的值一般都是CONTEXT_ALL,这个宏的值为0x1003f(如果是其它值也可以算出值),通过在WinDbg中搜索0x1003f就能快速找到异常CONTEXT指针值,如下:
s -d 0 L?FFFFFFFF 0x1003f,从搜索出的结果中可以比较方便的找到所要的异常CONTEXT指针值,如果找到后使用命令.cxr就可以看到异常发生时的寄存器值、堆栈等信息。