Windows下生成dump文件方法

【转载请注明出处】:http://blog.csdn.net/longlong530

一. 背景

       没人能保证自己的软件在各种未知环境运行下,会木有任何问题。那么如果程序崩溃了怎么办?看日志?日志不全又怎么办?日志能帮你定位的多细致呢?如果能有种方法记录程序最后工作的状态,比如堆栈调用情况等,那么我们就可以获悉“它是如何挂掉的~”


二. 调研

我们对程序bug引起的程序崩溃的五种定位方法进行了调研,并最终选择方案5为我所在项目使用的程序崩溃定位方案。

方案1: 崩溃地址 + MAP文件
     利用程序崩溃时产生的map文件来定位。这种方案只能对VC7以前的版本开发的程序使用    


方案2: 崩溃地址 + MAP文件 + COD文件
     这个方案主要是为了解决方案1的缺陷。由于VC8以后的版本都不再支持MAP文件中产生代码行信息,所以增加了COD文件的方法来定位问题。


方案3: 崩溃地址 + PDB文件 + CrashFinder

说明:前三种方案,其实只需要用户告知崩溃地址,然后在本地查找crash地址就可以了,但是定位crash的过程非常不方便,如果crash的情况比
      较多,前三种方案都不合适。而且,前三种方案均不能生成堆栈调用信息,对于debug的作用有限。
      
方案4:SetUnhandledExceptionFilter + StackWalker
       此方法需要pdb文件才能够正确生成堆栈调用的函数行号及代码行号,因此只适合本地release版的调试。
       
方案5:SetUnhandledExceptionFilter + Minidump
       该方法是我们使用的捕获dump文件的工具,所以这里对其重点介绍一下。


三. 代码共享

核心代码如下:

#include <stdio.h>
#include <time.h>
#include <windows.h>
#include <DbgHelp.h>

#pragma comment(lib, "DbgHelp.lib") 

LONG WINAPI TopLevelFilter( struct _EXCEPTION_POINTERS *pExceptionInfo )
{
	// 返回EXCEPTION_CONTINUE_SEARCH,让程序停止运行   
	LONG ret = EXCEPTION_CONTINUE_SEARCH;

	time_t  nowtime;
	time(&nowtime);
	struct tm *pTime = localtime(&nowtime);
	char szFile[128] = {0};
	
	// 设置core文件生成目录和文件名   
	sprintf(szFile, "c:\\%4d.%02d.%02d_%02d.%02d.%02d.dmp", pTime->tm_year+1900, pTime->tm_mon+1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min, pTime->tm_sec);


	HANDLE hFile = ::CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	if (hFile != INVALID_HANDLE_VALUE)
	{
		MINIDUMP_EXCEPTION_INFORMATION ExInfo;

		ExInfo.ThreadId = ::GetCurrentThreadId();
		ExInfo.ExceptionPointers = pExceptionInfo;
		ExInfo.ClientPointers = NULL;

		// write the dump
		BOOL bOK = MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ExInfo, NULL, NULL );
		ret = EXCEPTION_EXECUTE_HANDLER;
		::CloseHandle(hFile);
	}
	
	return ret;
}

代码移植方法:

1  将下面的头文件拷贝到你的代码里
#include <time.h>
#include <windows.h>
#include <DbgHelp.h>

2  引入gdbhelp的静态库
#pragma comment(lib, "DbgHelp.lib") 

3  将TopLevelFilter函数拷贝到你的代码里

4  在mian函数里,加入下面的代码
::SetUnhandledExceptionFilter(TopLevelFilter);

5  修改工程属性->配置属性->常规->项目默认值, 将字符集配置修改为使用多字节字符集


6  编译运行即可。 


四. 代码解释


a)SetUnhandledExceptionFilter

SetUnhandledExceptionFilter 函数声明如下:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
      __in          LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
    );

SetUnhandleExceptionFilter允许我们设置一个自己的函数作为全局SEH过滤函数,当程序crash前会调用我们的函数进行处理。我们可以利用的
是 _EXCEPTION_POINTERS 结构类型的变量ExceptionInfo,它包含了对异常的描述以及发生异常的线程状态,过滤函数可以通过返回不同的值来
让系统继续运行或退出应用程序。


b) Minidump

Minidump:minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在 系统属性 -> 
高级 -> 启动和故障恢复 -> 设置 -> 写入调试信息 中选择“小内存转储(64 KB)”的话,当系统意外停止时都会在C:\Windows\Minidump\路

径下生成一个.dmp后缀的文件,这个文件就是minidump文件,只不过这个是内核态的minidump。生成minidump文件的API函数:MiniDumpWriteDump,该函数需要dbghelp.lib支持。

BOOL WINAPI MiniDumpWriteDump(
      __in          HANDLE hProcess,
      __in          DWORD ProcessId,
      __in          HANDLE hFile,
      __in          MINIDUMP_TYPE DumpType,
      __in          PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
      __in          PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
      __in          PMINIDUMP_CALLBACK_INFORMATION CallbackParam
    );

   
调试dump文件首先需要pdb文件,因此我们build程序时需要设置 Debug Infomation Format 为 “Program Database(/Zi)”。其次,我们
还要确保所用的dump文件与源代码、exe、pdb文件版本是一致的,这要求我们必须维护好程序版本信息。调试minidump最方便的环境就是VS,
我们只要将.dmp、.exe、.pdb文件放在一个路径下,保证源代码文件的路径与编译时的路径一致就可以了,剩下的就是VS帮我们完成。双击.dmp
文件或者在文件打开工程中选择“dump files”,加载dump文件,然后按F5运行就能直接恢复crash时的现场了,你可以定位crash的代码,可以
查看调用堆栈,可以查看线程和模块信息.


五. 注意事项

对于release版的程序来说,很多代码是经过编译器优化过的,因此定位的时候可能会有所偏差,大家可以考虑设置选项去掉代码

优化。使用Minidump的详细方法可参见:http://vicchina.51.net/research/other/seh/minidumps/intro.htm


【转载请注明出处】:http://blog.csdn.net/longlong530

Windows下生成dump文件方法

上一篇:Windows下Yii创建应用及出错处理


下一篇:Windows下配置Apache集成PHP