Release版本程序定位Crash位置

Carsh

一、崩溃地址+MAP文件+COD文件

1. 生成应用程序MAP文件

MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,它可以在任何地方、任何时候使用,不需要有额外的程序进行支持。而且,这是唯一能找出程序崩溃的地方的救星。

 

1.1 VC生成MAP文件

我们可以按下 Alt+F7 ,打开“Project Settings”选项页,选择 C/C++ 选项卡,并在最下面的 Project Options 里面输入:/Zd ,然后要选择 Link 选项卡,在最下面的 Project Options 里面输入: /mapinfo:lines 和 /map:PROJECT_NAME.map(或者勾选Generate mapfile) 。最后按下 F7 来编译生成 EXE 可执行文件和 MAP 文件。
     在此我先解释一下加入的参数的含义:

/Zd   表示在编译的时候生成行信息
/map[:filename]  表示生成 MAP 文件的路径和文件名
/mapinfo:lines   表示生成 MAP 文件时,加入行信息
/mapinfo:exports 表示生成 MAP 文件时,加入 exported functions (如果生成的是 DLL 文件,这个选项就要加上)

 

1.2 VS2005,2008,2010生成MAP,COD文件

MAP:工程属性->配置属性->链接器->调试->生成映射文件->是 (/MAP); 映射导出->是 (/MAPINFO:EXPORTS)

COD:工程属性->配置属性->C/C++->输出文件->汇编输出->FAcs

 

1.3 根据MAP文件定位出错位置函数

假如出错地址: 0x0040104a

MAP:仔细浏览 Rva+Base 这栏,你会发现第一个比崩溃地址 0x0040104a 大的函数地址是 0x00401070 ,所以在 0x00401070 这个地址之前的那个入口就是产生崩溃的函数。

 

1.4根据COD文件定位出错行

COD:根据找到的崩溃函数找到对应文件的COD,找到函数生成的机器码,类似XXX,COMDAT。

计算相对偏移:0x0040104a - 函数地址 = 0x21(举例),在函数的机器码中找到0x21对应的C++函数。

 

二、SetUnhandledExceptionFilter+Minidump适用于Release程序

在发生异常的地方生成minidump文件

1. Minidump

minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。

我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。

2. 生成minidump文件

2.1生成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。

2.2异常中断API:UnhandledExceptionFilter处理那些没被catch的异常。

使用如下;

long __stdcall callback(_EXCEPTION_POINTERS* excp)

{   

return   EXCEPTION_EXECUTE_HANDLER;

}

SetUnhandledExceptionFilter(callback);

整个进程设置一个 callback就可以拦住进程内任何线程抛出的漏网的异常。如果你用 debugger 调试 callback代码,会发现它从来不会被调到,因为异常已经被 debugger 拦下来了,于是就不是“漏网之鱼”,所以这个 callback不会被调到。

可以看到 callback的参数里也有 EXCEPTION_POINTERS,所以在这个 callback函数里我们可以调用 MiniDumpWriteDump 生成我们需要的 dump 文件。callback被调到的线程就是发生异常的线程。

 

做到这一步,进程内几乎所有的异常都会被我们捕获到并通过 mini dump 文件把现场保存下来以供事后分析。

为什么说是“几乎所有”呢,还是有一些特殊情况下的异常无法被捕获到:

当遇到 Heap Corrupt 异常时(两次 delete 同一块内存地址就会出现这种异常),windows有两种处理策略:杀掉进程或者不杀。在64位的 windows server 下,当堆发生错误时,系统会直接杀死进程,不会有全局展开,异常捕获函数什么都不活不到,这种异常代码是:0xc0000374,这时需要外挂 windbg 或 cdb 来帮助我们 dump。

如果 mini dump 的参数设的过于“求全”,既要保存堆栈信息又要保持堆信息,系统很可能没有足够的硬盘空间来保持 dump 文件,这样 mini dump 也会失败。

但无论如何,只要进程非正常退出,总会在系统事件中记上一笔,我们在 Event Viewer 里就可以看到。

 

3.调试minidump

调试dump文件首先需要pdb文件,因此需要设置VS

属性->配置属性->C/C++调试信息格式->程序数据库格式。

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

 

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


4. minidump + email

博主设置当前文章不允许评论。

没有更多推荐了,返回首页