vc++程序崩溃后不生成dump文件

来源:互联网 发布:河南化工网软件 编辑:程序博客网 时间:2024/06/09 16:43

    这几天给自己的程序通过SetUnhandleExceptionFilte加dump机制。实测时发现不是所有的崩溃,都能生成dump文件:比如assert(false);语句就没有生成dump文件。上网查了一下发现跟我有相同困惑的人还不少,比如这篇

"

很多软件通过设置自己的异常捕获函数,捕获未处理的异常,生成报告或者日志(例如生成mini-dump文件),达到Release版本下追踪Bug的目的。但是,到了VS2005(即VC8),Microsoft对CRT(C运行时库)的一些与安全相关的代码做了些改动,典型的,例如增加了对缓冲溢出的检查。新CRT版本在出现错误时强制把异常抛给默认的调试器(如果没有配置的话,默认是Dr.Watson),而不再通知应用程序设置的异常捕获函数,这种行为主要在以下三种情况出现。

(1)       调用abort函数,并且设置了_CALL_REPORTFAULT选项(这个选项在Release版本是默认设置的)。

(2)       启用了运行时安全检查选项,并且在软件运行时检查出安全性错误,例如出现缓存溢出。(安全检查选项 /GS 默认也是打开的)

(3)       遇到_invalid_parameter错误,而应用程序又没有主动调用

_set_invalid_parameter_handler设置错误捕获函数。

所以结论是,使用VS2005(VC8)编译的程序,许多错误都不能在SetUnhandledExceptionFilter捕获到。这是CRT相对于前面版本的一个比较大的改变,但是很遗憾,Microsoft却没有在相应的文档明确指出。

"

这篇文章的作者其实已经写的很好了。这里顺带提一下,他为了解决SetUnhandledExceptionFilter再次被调用,通过inline hook的方式使得SetUnhandledExceptionFilter什么都不做直接返回。我在此处引用作者代码并略作注解

void DisableSetUnhandledExceptionFilter(){    void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")),                                                         "SetUnhandledExceptionFilter");    if (addr)    {              unsigned char code[16];              int size = 0;
              //下面两个字节是xor eax,eax的Opcode
              code[size++] = 0x33;              code[size++] = 0xC0;
              //下面三个字节是ret 0x0004的Opcode              code[size++] = 0xC2;              code[size++] = 0x04;              code[size++] = 0x00;                DWORD dwOldFlag, dwTempFlag;              VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);              WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);              VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);       }}

    当然,如果在这篇文章的基础上再深挖一点,会明白因为vs crt库自作主张的通过CxxUnhandledExceptionFilter调用了SetUnhandledExceptionFilter覆盖了前面自定义的UEH,会导致c++异常和win异常走了两条路:

下面内容节选自widows用户态程序高效排错第二章:

案例分析:华生医生(Dr. Watson)在什么情况下不能记录Dump文件

问题描述

客户声称用VC开发的程序偶尔会崩溃。为了获取详细信息,客户激活了Dr. Watson,以便程序崩溃的时候可以自动获取dump文件。但是问题再次发生后,Dr. Watson并没有记录dump文件。

背景知识

dump文件包含的是内存镜像信息。在Windows系统上,dump文件分为内核dump和用户态dump两种。前者一般用来分析内核相关的问题,比如驱动程序;后者一般用来分析用户程序的问题。如果不作说明,本书后面所指的dump都表示用户态dump。用户态的dump又分成mini dump和full dump。前者尺寸小,只记录一些常用信息;后者则是把目标进程用户态的所有内容都记录下来。Windows提供了MiniDumpWriteDump API可供程序调用来生成mini dump。通过调试器和相关工具,可以抓取目标程序的full dump。拿到dump后,可以通过调试器检查dump中的内容,比如call stack,memory,exception等等。关于dump和调试器的更详细信息,后面会有更多介绍。跟Dr. Watson相关的文档是:

Description of the Dr. Watson for Windows (Drwtsn32.exe) Tool

http://support.microsoft.com/?id=308538

Specifying the Debugger for Unhandled User Mode Exceptions

http://support.microsoft.com/?id=121434

INFO: Choosing the Debugger That the System Will Spawn

http://support.microsoft.com/?id=103861

也就是说,通过设定注册表中的AeDebug项,可以在程序崩溃后,选择调试器进行调试。选择Dr. Watson就可以直接生成dump文件。

问题分析

回到这个问题,客户并没有获取到dump文件,可能性有两个:

1.         Dr. Watson工作不正常。

2.         客户的程序根本没有崩溃,不过是正常退出而已。

为了测试第1点,提供了如下的代码给客户测试:

int *p=0;

*p=0;

测试上面的代码,Dr. Watson成功地获取了dump文件。也就是说,Dr. Watson工作是正常的。那看来客户声称的崩溃可能并不是unhandled exception导致的。说不定在非预料情况下调用了ExitProcess,被客户误认为是崩溃。所以,抓取信息不应该局限于unhandled exception,而应该检查进程退出的原因。

程序在Windbg调试器中退出的时候,系统会触发调试器的进程退出消息,可以在这个时候抓取dump来分析进程退出的原因。

如果让客户每次都先启动Windbg,然后用Windbg启动程序,操作起来很复杂。最好有一个自动的方法。Windows提供了让指定程序调试器启动的选项。设定注册表后,当设定的进程启动的时候,系统先启动指定的调试器,然后把目标进程的地址和命令行作为参数传递给调试器,调试器再启动目标进程调试。这个选项在无法手动从调试器中启动程序的时候特别有用,比如调试先于用户登录而启动Windows Service程序,就必须使用这个方法:

How to debug Windows services

http://support.microsoft.com/?kbid=824344

有趣的是,好多恶意程序也通过这个方法来达到加载进程的目的。很多人把这个方法叫做IFEO 劫持(Image File Execution Option Hacking)。

在Windbg目录下,有一个叫做adplus.vbs的脚本可以方便地调用Windbg来获取dump文件。所以这里可以借用这个脚本:

How to use ADPlus to troubleshoot "hangs" and "crashes"

http://support.microsoft.com/kb/286350/EN-US/

脚本的详细说明可以参考adplus /?的帮助。

新的做法

结合上面的信息,具体做法是:

1.         在客户机器的Image File Execution Options注册表下面创建跟问题程序同名的键。

2.         在这个键的下面创建Debugger字符串类型子键。

3.         设定Debugger= C:/Debuggers/autodump.bat。

4.         编辑C:/Debuggers/autodump.bat文件的内容为如下:

cscript.exe C:/Debuggers/adplus.vbs -crash -o C:/dumps -quiet -sc %1

通过上面的设置,当程序启动的时候,系统自动运行cscript.exe来执行adplus.vbs脚本。Adplus.vbs脚本的-sc参数指定需要启动的目标进程路径(路径作为参数又系统传入,bat文件中的%1代表这个参数),-crash参数表示监视进程退出,-o参数指定dump文件路径,-quiet参数取消额外的提示。可以用notepad.exe作为小白鼠做一个实验,看看关闭notepad.exe的时候,是否有dump产生。

根据上面的设定,问题再次发生后,C:/dumps目录生成了两个dump文件。文件名分别是:

PID-0__Spawned0__1st_chance_Process_Shut_Down__full_178C_DateTime_0928.dmp

PID-0__Spawned0__2nd_chance_CPlusPlusEH__full_178C_2006-06-21_DateTime_0928.dmp

注意看第二个的名字,这个名字表示发生2nd chance的C++ exception!打开这个dump后找到了对应的call stack,发现的确是客户忘记了catch潜在的C++异常。修改代码添加对应的catch后,问题解决。

问题解决了,可是为什么华生医生(Dr. Watson)抓不到dump呢

当然疑问并没有随着问题的解决而结束。既然是unhandled exception导致的crash,为什么Dr. Watson抓不到呢?首先创建两个不同的程序来测试Dr. Watson的行为:

int _tmain(int argc, _TCHAR* argv[])

{

  throw 1; 

  return 0;

}

int _tmain(int argc, _TCHAR* argv[])

{

  int *p=0;

  *p=0;

  return 0;

}

果然,对于第一个程序,Dr. Watson并没有保存dump文件。对于第二个,Dr. Watson工作正常。看来的确跟异常类型相关。

仔细回忆一下。当AeDebug下的Auto设定为0的时候,系统会弹出前面提到的红色框框。对于上面这两个程序,框框的内容是不一样的。

在我这里,看到的对话框分别是(对话框出现的时候用Ctrl+C保存的信息):

---------------------------

Microsoft Visual C++ Debug Library

---------------------------

Debug Error!

Program: d:/xiongli/today/exceptioninject/debug/exceptioninject.exe

This application has requested the Runtime to terminate it in an unusual way.

Please contact the application's support team for more information.

(Press Retry to debug the application)

---------------------------

Abort   Retry   Ignore  

---------------------------

---------------------------

exceptioninject.exe - Application Error

---------------------------

The instruction at "0x00411908" referenced memory at "0x00000000". The memory could not be "written".

Click on OK to terminate the program

Click on CANCEL to debug the program

---------------------------

OK   Cancel  

---------------------------

两者行为完全不一样!如果做更多的测试,会发现对话框的细节还跟编译模式release/debug 相关。

程序可以通过SetUnhandledExceptionFilter函数来修改unhanded exception的默认处理函数。这里,C++运行库在初始化CRT(C Runtime)的时候,传入了CRT的处理函数 (msvcrt!CxxUnhandledExceptionFilter)。如果发生unhandled exception,该函数会判断异常的号码,如果是C++异常,就会弹出第一个对话框,否则就交给系统默认的处理函数(kernel32!UnhandledExceptionFilter)处理。第一种情况的call stack 如下:

USER32!MessageBoxA

MSVCR80D!__crtMessageBoxA

MSVCR80D!__crtMessageWindowA

MSVCR80D!_VCrtDbgReportA

MSVCR80D!_CrtDbgReportV

MSVCR80D!_CrtDbgReport

MSVCR80D!_NMSG_WRITE

MSVCR80D!abort

MSVCR80D!terminate

MSVCR80D!__CxxUnhandledExceptionFilter

kernel32!UnhandledExceptionFilter

MSVCR80D!_XcptFilter

第二种情况CRT交给系统处理。Callstack如下:

ntdll!KiFastSystemCallRet

ntdll!ZwRaiseHardError+0xc

kernel32!UnhandledExceptionFilter+0x4b4

release_crash!_XcptFilter+0x2e

release_crash!mainCRTStartup+0x1aa

release_crash!_except_handler3+0x61

ntdll!ExecuteHandler2+0x26

ntdll!ExecuteHandler+0x24

ntdll!KiUserExceptionDispatcher+0xe

release_crash!main+0x28

release_crash!mainCRTStartup+0x170

kernel32!BaseProcessStart+0x23

详细的信息可以参考:

SetUnhandledExceptionFilter

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/setunhandledexceptionfilter.asp

UnhandledExceptionFilter

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/unhandledexceptionfilter.asp

上面观察到的信息能解释Dr. Watson的行为吗?看起来似乎有关系。为了进一步确认这个问题,可以通过下面的测试,使用Windbg代替Dr. Watson,看看是否可以获取dump。如果仅仅换一个调试器就可以获取dump,那说明问题是跟调试器相关,跟程序抛出的异常无关。具体做法是:

1.         运行drwtsn32.exe –i注册Dr. Watson。

2.         打开AeDebug注册表,找到Debugger项,里面应该是drwtsn32 -p %ld -e %ld -g。

3.         修改Debugger为: C:/debuggers/windbg.exe -p %ld -e %ld -c ".dump /mfh C:/myfile.dmp ;q"。

当unhanded exception发生后,系统会启动windbg.exe作为调试器加载到目标进程。但是windbg.exe不会自动获取dump,所以需要用-c参数来指定初始命令。命令之间可以用分开分割。这里的.dump /mfh C:/myfile.dmp命令就是用来生成dump文件的。接下来的q命令是让windbg.exe在dump生成完毕后自动退出。用这个方法,对于unhandled C++ exception,windbg.exe是可以获取dump文件的。所以我认为Dr. Watson这个工具在获取dump的时候是有缺陷的。研究的发现在:

    http://eparg.spaces.msn.com/blog/cns!59BFC22C0E7E1A76!1213.entry

0 0
原创粉丝点击