XCrashReport:异常处理和崩溃报告——第三部分






4.96/5 (54投票s)
为您的应用程序添加基本的异常处理和崩溃报告
引言
在第 1 部分中,我讨论了调试已投入生产或已交付给客户的程序的难题。在开发人员的工作站上,程序可能运行良好,但在客户的系统上,却会出现随机崩溃。要解决这个问题,您需要知道程序在何处崩溃,在第 1 部分中,我讨论了一种确定这一点的方法。在第 2 部分中,我描述了一种简单的源代码插桩方法,可以方便地分析堆栈转储。但这种技术也有其自身的问题,在本文中,我将描述我改进它的尝试,以及我如何找到完美的解决方案。
Deus Ex Minidump
在我写完第 2 部分之后,我真的认为堆栈跟踪就只能到此为止了,除非诉诸于更大的异常处理程序占位符或其他可能在 Win9x 系统上无法工作的技术。我了解到一种新的 Visual Studio .Net 技术,称为 minidumps(小型转储),据说可以直接加载到 .Net 调试器中并查看调用堆栈,但我找不到关于如何做到的信息。我没有 Visual Studio .Net,所以似乎不值得去研究——即使我能生成 minidump,我又该如何查看它呢?此外,还有一个问题是 Win9x 系统是否能够创建 minidump。
然后发生了两件事。我发现dbghelp.dll——其中包含MiniDumpWriteDump()
API——是可以重新分发的。第二,我了解到一种无需使用 Visual Studio .Net 即可读取 minidump 的方法。
准备工作
MiniDumpWriteDump()
API 尽管功能强大,但使用起来相当简单。以下是 MSDN 的说明:
MiniDumpWriteDump 函数将用户模式的 minidump 信息写入指定的文件。
BOOL MiniDumpWriteDump(
DWORD ProcessId,
HANDLE hFile,
MINIDUMP_TYPE DumpType,
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam
参数
hProcess
ProcessId
hFile
DumpType
ExceptionParam
UserStreamParam
CallbackParam
返回值
如果函数成功,则返回值为 TRUE;否则,返回值为 FALSE。
备注
MiniDumpCallback 函数从 MiniDumpWriteDump 接收扩展的 minidump 信息。它还提供了一种方法,允许调用者确定写入 minidump 文件的信息的粒度,因为回调函数可以过滤默认信息。
MiniDumpWriteDump 可能不会为调用线程生成有效的堆栈跟踪。为了解决这个问题,您必须在调用 MiniDumpWriteDump 之前捕获调用线程的状态,并将其用作 ExceptionParam 参数。一种方法是在 __try/__except 块内强制引发异常,并使用 GetExceptionInformation 提供的 EXCEPTION_POINTERS 信息。或者,您可以从一个新的工作线程调用该函数,并将此工作线程从转储中过滤掉。
要求
客户端:包含在 Windows XP 中。
服务器:包含在 Windows Server 2003 中。
可再发行:在 Windows 2000、Windows NT 4.0 和 Windows Me/98/95 上需要 dbghelp.dll。
头文件:在 DbgHelp.h 中声明。
库:使用 Dbghelp.lib。
一个棘手的参数是 DumpType
。在 Win9x 系统上,我唯一能够使其正常工作的 DumpType
值是 MiniDumpNormal
。
理论付诸实践
为了能够调用MiniDumpWriteDump()
,我们需要打开一个文件进行写入,并将它的句柄传递给 MiniDumpWriteDump()
。在经过重新修改的 ExceptionHandler.cpp 模块中,这是我们调用 MiniDumpWriteDump()
的方式://///////////////////////////////////////////////////////////////////////////// // DumpMiniDump static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo) { if (excpInfo == NULL) { // Generate exception to get proper context in dump __try { OutputDebugString(_T("raising exception\r\n")); RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL); } __except(DumpMiniDump(hFile, GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) { } } else { OutputDebugString(_T("writing minidump\r\n")); MINIDUMP_EXCEPTION_INFORMATION eInfo; eInfo.ThreadId = GetCurrentThreadId(); eInfo.ExceptionPointers = excpInfo; eInfo.ClientPointers = FALSE; // note: MiniDumpWithIndirectlyReferencedMemory does not work on Win98 MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, excpInfo ? &eInfo : NULL, NULL, NULL); } }
这个新的 ExceptionHandler.cpp 被整合到了 XCrashReportTest 演示程序中。当程序运行时,您可以选择不同类型的崩溃。
当您单击其中一个按钮时,程序会崩溃并生成一个名为 CRASH.DMP 的 minidump 文件。还记得我曾说过我了解到一种无需使用 Visual Studio .Net 即可读取 minidump 的方法吗?方法是:使用 WinDbg,可以从 Microsoft Debugging Tools 下载。WinDbg 要求 pdb 文件与 exe 文件和 CRASH.DMP 文件位于同一目录中。启动 WinDbg 后,转到 File | Open Crash Dump 并选择 CRASH.DMP 文件。然后按 ALT+1 切换到命令窗口,键入 .ecxr 并按 Enter。您将看到
在下面的窗口中,您将看到 OnCrash
调用了 Crash1
,Crash1
调用了 Crash2
,Crash2
调用了 CrashTestFunction
,并且崩溃发生在 crashtest.cpp 的第 33 行。
摘要
现在我们有了一种在不修改源代码的情况下获取调用堆栈和查找崩溃位置的方法。下载和学习使用 WinDbg 需要几分钟时间——好吧,如果您的网速很慢,可能需要更多时间。但这只需要一次,而且似乎是值得的。总而言之:可以通过添加几个文件、更改三个构建设置并重新编译,将异常处理和 minidumps 添加到任何现有的 MFC 应用程序中。 您只需要做的额外工作是在每次发布新版本时保存 pdb 文件。我本以为这将是关于崩溃报告的最后一篇文章,但随后我遇到了一个绝妙的东西,以至于我知道我必须将其融入我的异常处理和崩溃报告方法中。我将在第 4 部分中告诉您所有关于它的信息。
修订历史
版本 1.1 - 2003 年 10 月 19 日
- 首次公开发布
用法
本软件已进入公共领域。您可以随意使用它,但不得销售此源代码。如果您修改或扩展它,请考虑将新代码发布到此处供大家共享。本软件按“原样”提供,不附带任何明示或暗示的保证。对于本软件可能造成的任何损害或业务损失,本人概不承担任何责任。