【转载请注明出处】: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