转自https://blog.csdn.net/lixiangminghate/article/details/50413924
这几天给自己的程序通过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
————————————————
版权声明:本文为CSDN博主「Yuri800」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lixiangminghate/article/details/50413924