传统的log日志方法,在崩溃的情况下,并不能比较准确的表示出问题位置,这使得软件调试进程缓慢。
而其实当异常没有处理的时候,系统就会调用SetUnhandledExceptionFilter所设置异常处理函数.
这时候你就可以获取处理异常的机会.
1.SetUnhandledExceptionFilter
Windows平台下的C++程序异常通常可分为两种:结构化异常(Structured Exception,可以理解为与操作系统相关的异常)和C++异常。对于结构化异常处理(SEH),可以找到很多资料,在此不细说。对于crash错 误,一般由未被正常捕获的异常引起,Windows操作系统提供了一个API函数可以在程序crash之前有机会处理这些异常,就是 SetUnhandleExceptionFilt
er函数。(C++也有一个类似函数set_terminate可以处理未被捕获的C++异常。)
SetUnhandleExceptionFilter函数声明如下:
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(
__in LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
其中 LPTOP_LEVEL_EXCEPTION_FILTER 定义如下:
typedef LONG (WINAPI *PTOP_LEVEL_EXCEPTION_FILTER)(
__in struct _EXCEPTION_POINTERS *ExceptionInfo
);
typedef PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER;
简单来说,SetUnhandleExceptionFilter允许我们设置一个自己的函数作为全局SEH过滤函数,当程序crash前会调用我们的函 数进行处理。我们可以利用的是 _EXCEPTION_POINTERS 结构类型的变量ExceptionInfo,它包含了对异常的描述以及发生异常的线程状态,过滤函数可以通过返回不同的值来让系统继续运行或退出应用程 序。
关于 SetUnhandleExceptionFilter 函数的具体用法和示例请参考MSDN。
简单示例:
#include <windows.h>
long __stdcall callback(_EXCEPTION_POINTERS* excp)
{
MessageBox(0,"Error","error",MB_OK);
return EXCEPTION_EXECUTE_HANDLER;
}
int main(int argc,char* argv[])
{
/*只需在程序开始之前调用一下这一句就可以*/
SetUnhandledExceptionFilter(callback);
_asm int 3
return 0;
}
2. Minudump
1、Minidump概念
minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在 系统属性 -> 高级 -> 启动和故障恢复 -> 设置 -> 写入调试信息 中选择“小内存转储(64 KB)”的话,当系统意外停止时都会在C:\Windows\Minidump\路径下生成一个.dmp后缀的文件,这个文件就是minidump文件, 只不过这个是内核态的minidump。
我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。
2、生成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
);
在我们的异常处理函数中加入以下代码:
HANDLE hFile = ::CreateFile( _T("E:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if( hFile != INVALID_HANDLE_VALUE)
{
MINIDUMP_EXCEPTION_INFORMATION einfo;
einfo.ThreadId = ::GetCurrentThreadId();
einfo.ExceptionPointers = pExInfo;
einfo.ClientPointers = FALSE;
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, &einfo, NULL, NULL);
::CloseHandle(hFile);
}
其中,pExInfo变量为异常处理函数PEXCEPTION_POINTERS类型的参数。具体请参考MSDN。
3、调试minidump
调试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的工具还有WinDbg等,大家可以查阅相关资料。
上一篇我们已经给出了方案,能够非常方便的通过dump文件对crash错误进行调试和定位;从整个流程上看还差最后一步,即怎样拿到crash时 产生的dump文件。如果可以让用户把文件发送过来自然不错,但对于类似免费共享软件等在互联网上发布的程序呢?我们的用户是不确定的,而且用户量有可能 非常大,即使我们能想办法联系到用户,总不能挨个去收集crash信息吧。
我们需要一种方案,能够提供crash信息汇报功能。
3. google breakpad
google breakpad是一个开源的跨平台crash report系统,光从开源和跨平台这两个特点上来看,它就足以称的上是一个完善而有效的工具了。其实,breakpad在整个crash report层次上给出了一个系统级的解决方案,也就是说它几乎能适应各种软件、各种平台的应用要求。
breakpad的整体思路跟上面介绍的方案是相似的,只不过最后提交dump文件的方式更加完善。大家有兴趣可以去它的官方网址查阅相关资料:http://code.google.com/p/google-breakpad/。
本文主要参考了文章:
http://vicchina.51.net/research/other/seh/minidumps/intro.htm。
http://anfernee.blog.51cto.com/4828774/994536
http://bigasp.com/archives/450