后在公司前辈的指点下,我们想到了使用window自带的dumpfile来记录崩溃时刻的堆栈信息,这样配合log日志记录,能够快速的定位出问题点。大大提高了系统调试效率。
经过一段时间的调试,现在项目已相对稳定了。想记录下此方法,以待后续类似情况下使用。
1.//使所有版本都可以捕获到异常
2.void DisableSetUnhandledExceptionFilter()
3.{
4. void *addr = (void*)GetProcAddress(LoadLibrary(_T("kernel32.dll")), "SetUnhandledExceptionFilter");
5.6. if (addr)
7. {
8. unsigned char code[16];
9. int size = 0;
10. code[size++] = 0x33;
11. code[size++] = 0xC0;
12. code[size++] = 0xC2;
13. code[size++] = 0x04;
14. code[size++] = 0x00;
15.16. DWORD dwOldFlag, dwTempFlag;
17. VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
18. WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
19. VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
20. }
21.}
22.23.//程序未捕获的异常处理函数
24.LONG WINAPI ExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo)
25.{
26. ::AfxMessageBox("ExceptionFilter");
27.28. HANDLE hFile = ::CreateFile( _T("C:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
29. if( hFile != INVALID_HANDLE_VALUE)
30. {
31. MINIDUMP_EXCEPTION_INFORMATION einfo;
32. einfo.ThreadId = ::GetCurrentThreadId();
33. einfo.ExceptionPointers = ExceptionInfo;
34. einfo.ClientPointers = FALSE;
35.36. ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, &einfo, NULL, NULL);
37. ::CloseHandle(hFile);
38. }
39.40. return 0;
41.}
42.43.//把当前时刻的线程栈记录到DUMP文件中
44.int RecordCurStack()
45.{
46. HANDLE hFile = ::CreateFile( _T("C:\\dumpfile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
47. if( hFile != INVALID_HANDLE_VALUE)
48. {
49. ::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpWithFullMemory ,NULL, NULL, NULL);
50.51. ::CloseHandle(hFile);
52. return 1;
53. }
54.55. return 0;
56.}
57.58.59.bool bCreateDumpThrd = true;
60.//循环检测线程
61.//查看到有ADTV2_TEMP.TXT文件,则记录下当前时刻的堆栈
62.void CreateDumpThrd(void* pv)
63.{
64. HANDLE hFile;
65. string strPath = FileAssist::GetExePath() + "\\ADTV2_TEMP.TXT";
66. while(bCreateDumpThrd)
67. {
68. //每5秒检测一次
69. Sleep(5000);
70. hFile = CreateFileA(strPath.c_str(), // file to open
71. GENERIC_READ, // open for reading
72. FILE_SHARE_READ, // share for reading
73. NULL, // default security
74. OPEN_EXISTING, // existing file only
75. FILE_ATTRIBUTE_NORMAL, // normal file
76. NULL); // no attr. template
77.78. if (hFile != INVALID_HANDLE_VALUE)
79. {
80. //防止多次记录当前堆栈信息,删除文件
81. ::CloseHandle(hFile);
82. ::DeleteFile(strPath.c_str());
83. RecordCurStack();
84. }
85. }
86.}
然后在程序入口将异常处理接口声明即可。
1.//调试信息
2.::SetUnhandledExceptionFilter(ExceptionFilter); //设置异常处理函数
3.DisableSetUnhandledExceptionFilter(); //获取未处理的异常
这样,在程序异常时,就可以在C盘根目录下记录一个dumpfile.dmp的文件。这个文件会比较大,一般有100多M,其中信息比log形式的日志丰富很多,包括了异常时的堆栈调用关系以及各对象的值。,在VS中可以直接打开。如果保留了和当时编译软件一致的代码备份的话,可以直接使用VS的debug功能定位到问题代码行,否则,debug定位是到汇编代码行,看起来比较麻烦。
今天尝试集成了CrashRpt,感觉还不错,功能很完善,集成也很容易。http://code.google.com/p/crashrpt/
下载以后解压缩,虽然有现成的Dll,EXE,但是还是建议自己编译,因为这个CrashRpt需要和你程序共享一样的vc runtime dll,否则某些异常截获不到。
编译很简单,有现成的VC 工程,支持 2005, 2008,就是需要WTL支持下载 WTL 80, http://sourceforge.net/projects/wtl/但是有一个小问题,如果你用VC Express版本的话,那么是没有ATL的,如果你装的Windows Plarform SDK是2008的话,那么就彻底杯具了,也没有ATL(在这个开源的大时代,微软连ATL这破东西都不舍得开放),解决方法是安装 2003版本的SDK,如果你能找到的话,或者找别人copy一份ATL ,就在SDK\include\atl目录下。http://www.cppblog.com/Files/stevenyao/atl.7z 我自己打包了一份ATL,希望微软不要告我。。。
然后,http://crashrpt.sourceforge.net/docs/html/simple_example.html 到这里抄个例子
info.pszEmailTo = _T("myapp_support@hotmail.com"); //改成你自己的email随便写个什么程序,自己弄个crash出来,注意,需要把下列文件复制到你的程序的exe目录
CrashRpt.dll
CrashSender.exe
dbghelp.dll
crashrpt_lang.ini
好了,测试下吧,应该会提示程序崩溃,是否发送crash log的对话框。如果发送,那么会在email里收到一个zip文件,和一个zip的md5校验码。
这个zip文件里包含有crashdump.dmp 和 crashrpt.xml, xml只是些信息,dmp是Crash dump可以用vc直接打开,如果你有源代码和所有符号表,那么就能还原当时崩溃的现场。