自己动手实现Crash时候Dump.

来源:互联网 发布:minitab软件15 编辑:程序博客网 时间:2024/06/05 20:47

程序在运行时,难免会有一些异常情况发生,特别是在条件不容许去挂调试器的时候,如何快速的定位错误的方法就显得很重要。

日志一直都是一种很重要的定位错误的方法,出得好的日志可以方便程序员快速的定位问题所在。但日志有时也显不足:

  • 日志有时只能定位大体错误范围,却无法确认问题所在,比如程序抓到一个未知的异常。
  • 程序有时没有机会来出日志,或者能出日志的时候已经无法获得和错误相关的信息,比如程序崩溃的时候。

在日志明显不足的时候,把进程中相关数据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)
{


}

 

 

 

 

 

其实在使用GenerateMiniDump也可以不用传入LPEXCEPTION_POINTERS值,有它只是方便WinDbg使用命令.ecxr 直接显示出现问题的位置,比如在C++异常Catch中抓到一个未知的异常,这时虽然不能取得EXCEPTION_POINTERS指针,但是出现异常时的CONTEX指针已经在内存中了,只要手工找到它,同样可以知道出现异常时的环境。

使用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就可以看到异常发生时的寄存器值、堆栈等信息。

 

 

 

__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);
}

2、设置的UnhandledExceptionFilter传人的参数。

 

SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

LONG WINAPI MyUnhandledExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{

    ...
   
if(IsDebuggerPresent())
    {
       
return EXCEPTION_CONTINUE_SEARCH;
    }

   
return GenerateMiniDump(hFile,lpExceptionInfo);
}