65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (75投票s)

2003 年 10 月 20 日

CPOL

11分钟阅读

viewsIcon

691494

downloadIcon

4689

为您的应用程序添加基本的异常处理和崩溃报告

更新第 5 部分现已发布,支持 VS2008 和 64 位。

引言

第 3 部分中,我说过我以为那是关于异常处理和崩溃报告的最后一篇文章。但我忽略了一个重要问题:你如何让你的客户向你发送崩溃转储和错误日志?这真的让我很担心,直到我看到了 Mike Carruth 在这里提出的绝妙解决方案。他的想法是用一个提示用户通过电子邮件将崩溃文件发送给软件供应商的对话框,来替换标准的崩溃对话框,标准对话框看起来像这样:

screenshot

这个对话框的绝妙之处在于,它允许客户详细查看将要发送的内容,并选择要发送的文件。

圣杯的终点?

好的,现在你可能会想:“太好了!我们可以直接使用 Carruth 的代码。” 但是,我对他实现的代码有一些问题。
问题 #1:它使用了 WTL。这倒不是什么大问题,但它又增加了一项需要配置和处理的依赖,而且我非常不愿意去修改我维护的一些旧 MFC 应用程序。

问题 #2:替换的崩溃对话框运行在与崩溃应用程序相同的进程中。这是一个很大的担忧,因为你确实希望在异常处理程序中做尽可能少的事情,而 GUI 对话框已经越过了这条界线。

问题 #3:它使用了 zlib.dll 来压缩崩溃文件。这意味着需要在安装中添加一个额外文件,而且一想到要处理这个就让我头疼。

问题 #4:实现中包含了一些对我来说不是必需的额外功能。例如,使用 Microsoft 的 MSXML 将崩溃信息输出到 XML。虽然我认为这是 XML 的一个非常有趣的用法,但我希望异常处理程序(及其引入的代码)保持非常小的占用空间。

准备工作

我的第一个要求是将崩溃对话框作为单独的应用程序运行,以避免对刚刚崩溃的应用程序造成任何问题;我的第二个要求是仅使用 MFC。

作为单独的应用程序运行没有任何问题。由于异常处理程序已经完成了创建崩溃文件的所有工作,新的崩溃对话框应用程序只需要找到这些文件,压缩它们,然后发送电子邮件。为了让崩溃对话框应用程序(我以后称它为 XCrashReport)尽可能独立,异常处理程序的最后一步是在命令行中调用 XCrashReport 并传入崩溃应用程序的名称。默认情况下,我决定崩溃应用程序、崩溃文件以及 XCrashReport 必须位于同一目录中。这个设计决策可以轻松更改,但到目前为止,它似乎很好地满足了我的需求。

接下来是 MFC 的部分。我知道会有两个对话框——主窗口,然后是一个对话框,显示正在发送的文件详细信息,并允许用户选择要发送的文件。对于后者,我认为我的XListCtrl 会非常完美,可以使用复选框来实现选择。我还可以使用我的新XHyperLinkXColorStatic 控件。我还可以使用我的XZip 代码来压缩崩溃文件。

我想感谢 Grant McDorman 对 Mike Carruth 代码的改进,你可以在这里找到。特别是,McDorman 添加了将注册表的一部分转储到文本文件的功能,然后可以将该文件添加到错误报告中。

理论付诸实践

在再次修改了 ExceptionHandler.cpp 代码以启动 XCrashReport.exe 后,如果新的崩溃报告对话框与崩溃的应用程序位于同一目录中,它将被显示。

screenshot

用户随后可以单击“单击此处”链接,在发送错误报告之前查看其内容。这显示了正在显示的 ERRORLOG.TXT 文件。

screenshot

这显示了正在显示的 CRASH.DMP 文件。

screenshot

这显示了正在显示的 REGISTRY.TXT 文件。

screenshot

摘要

现在,这是一个完整的解决方案,可以捕获应用程序异常,生成详细的错误报告,并提示用户将报告发送给你。感谢 Bruce Dawson、Mike Carruth 和 Grant McDorman 的工作,XCrashReport 提供了最小的占用空间以及用户友好的崩溃对话框。

实现说明

处理异常

ExceptionHandler.cpp 文件中 RecordExceptionInfo() 函数的末尾,有一些代码可以检测应用程序是否在调试器下运行,并据此处理异常。
  • 如果在调试器下运行,处理程序返回 EXCEPTION_CONTINUE_SEARCH。这告诉 Win32 该处理程序实际上并未处理该异常,因此事情将按正常情况进行,调试器将捕获该异常。
  • 如果不在调试器下运行,处理程序会尝试运行 XCrashReport.exe。如果成功,处理程序将返回 EXCEPTION_EXECUTE_HANDLER,这将抑制标准的崩溃对话框。如果不成功,处理程序将返回 EXCEPTION_CONTINUE_SEARCH,这再次允许按正常情况进行(将弹出标准的崩溃对话框)。

可自定义文件

有一些文件你可能希望为自己的应用程序和公司进行自定义。
  • CrashFileNames.h - 此文件包含错误输出文件的名称。
    #define XCRASHREPORT_MINI_DUMP_FILE     _T("CRASH.DMP")
    #define XCRASHREPORT_ERROR_LOG_FILE     _T("ERRORLOG.TXT")
    #define XCRASHREPORT_REGISTRY_DUMP_FILE _T("REGISTRY.TXT")
    #define XCRASHREPORT_CRASH_REPORT_APP   _T("XCrashReport.exe")
  • EmailDefines.h - 此文件包含电子邮件标头的字符串。
    #define XCRASHREPORT_SEND_TO_NAME      _T("Software Support")
    #define XCRASHREPORT_SEND_TO_ADDRESS   _T("support@softwarevendor.com")
  • IniDefines.h - 此文件包含 XCrashReport.ini 文件的宏定义。
    #define INI_FILE_NAME      _T("XCrashReport.ini")
    #define INI_FILE_SECTION   _T("FilesToAdd")
    #define INI_FILE_TEMPLATE  _T("File%03d")
    #define INI_REG_SECTION    _T("RegistryToAdd")
    #define INI_REG_TEMPLATE   _T("Registry%03d")
    #define MAX_INI_ITEMS      999
  • RegistryDefines.h - 此文件包含一个宏定义,用于指定是否转储任何注册表部分,以及默认的注册表部分。
    #define XCRASHREPORT_DUMP_REGISTRY
    #define XCRASHREPORT_REGISTRY_KEY \
                         _T("HKCU\\Software\\CodeProject\\XCrashReportTest")
  • XCrashReport.ini - 此 ini 文件由 XCrashReport.exe 读取,其中包含要转储到文件的注册表部分以及要包含在错误报告 zip 文件中的文件。在 [FilesToAdd] 部分中包含注册表文件不是必需的。
    [RegistryToAdd]
    ;Registry001=HKCU\Software\CodeProject\XCrashReportTest,Main reg key
    ;Registry002=HKCU\Software\CodeProject\XCrashReportTest\Program,new reg key
    
    [FilesToAdd]
    ;File001=CRASH.DMP,Crash Dump,DMP File
    ;File002=ERRORLOG.TXT,Crash log,Text Document

在调试模式下使用 XCrashReport

为了简化 XCrashReport 的开发,在调试构建中,XCrashReport 将模拟在命令行中接收字符串 "XCrashReportTest.exe"。

如何使用

  1. 设置你的发布版本以生成调试符号 (pdb)
  2. 将这些文件包含在你的项目中。
  3. 重新编译整个项目。

    • 在你的 VC++ 项目中,转到 **项目 | 设置**。确保在左侧的 **设置对象** 组合框中选择了 **发布** 配置。转到 **C/C++** 选项卡,选择 **常规** 类别,然后在 **调试信息** 组合框中选择 **程序数据库**。这会告诉编译器生成调试信息。

      screenshot

    • 转到 **链接** 选项卡并勾选 **生成调试信息**。这会告诉链接器将调试信息汇总到 .pdb 文件中。链接器还会将 .pdb 文件的名称放入可执行文件中,以便调试器找到它。
    • 在同一个 **链接** 选项卡上,在 **项目选项** 列表的末尾输入 /OPT:REF。这会告诉链接器删除从未引用的函数和/或数据。这通常是发布版本的默认设置,但当你告诉链接器生成调试信息时,它会被关闭。未能指定 /OPT:REF 会导致你的可执行文件和 DLL 增加 10-20% 的大小。

      screenshot


    • ExceptionAttacher.cpp
    • ExceptionHandler.cpp - 应在 **C/C++** 选项卡的 **预编译头** 设置中设置为 **不使用预编译头**。
    • ExceptionHandler.h
    • GetWinVer.cpp
    • GetWinVer.h
    • MiniVersion.cpp
    • MiniVersion.h
    • CrashFileNames.h


    将 exe 和 pdb 文件妥善保管。不要将 pdb 文件分发给客户 - 这既不必要,也可能对想要逆向工程你程序的人有帮助。

关于 dbghelp.dll 的说明

WinDbg 的相同下载也包含了最新的 dbghelp.dll。你可以在这里下载。在此下载中,redist.txt 文件指出 dbghelp.dll 版本 6.2.13.1 是可再发行的。

下载还包括最新的 dbghelp.libdbghelp.h

已知限制

  • XCrashReport.exe 必须与应用程序的 exe 位于同一目录中。
  • Unicode 实现不完整或未经测试。
  • 用户评论编辑框中可以输入的文本量限制为 64 KB 字符。
  • 在 **错误报告内容** 对话框中显示的文件的数量限制为 64 KB。
  • 为了通过电子邮件发送错误报告,必须在系统中安装默认的电子邮件客户端。这已经过 Outlook、Outlook Express 和 Eudora 的测试。如果没有安装默认的电子邮件客户端,将要求用户通过电子邮件发送 zip 文件,但不会尝试配置 MAPI 或创建电子邮件会话。

常见问题解答

  1. 我可以在非 MFC 应用程序中使用 ExceptionHandler.cpp 吗?
    是的!它被设计为可以与任何 Win32 程序编译。请参阅第 1 部分以获取示例。
  2. 如何为错误报告添加其他文件?
    如何为错误报告添加其他注册表部分?
    使用可选的 XCrashReport.ini 文件(包含了一个示例 XCrashReport.ini 文件)。当你在 XCrashReport.ini 文件中指定文件时,默认文件(CRASH.DMPERRORLOG.TXT)不会被包含——如果你想包含它们,则必须在 XCrashReport.ini 文件中添加它们。另外,当你指定 XCrashReport.ini 文件中的注册表部分时,默认的注册表部分(由 XCRASHREPORT_REGISTRY_KEY 定义)不会被包含——如果你想包含它,则必须在 XCrashReport.ini 文件中添加它。在目标系统上,XCrashReport.ini 文件必须放置在 exe 目录中。

    这是一个示例 XCrashReport.ini 文件。

    ;
    ; sample XCrashReport.ini file
    ;
    [FilesToAdd]
    File001=CRASH.DMP,Crash Dump,DMP File
    File002=ERRORLOG.TXT,Crash log,Text Document
    File003=BozoSoft.db,Database File,DB File
    
    [RegistryToAdd]
    Registry001=HKCU\Software\BozoSoft\BozoApp1,Main reg key
    Registry002=HKCU\Software\BozoSoft\BozoApp2\Program,another reg key
    文件大小没有限制,尽管在内部,**错误报告内容** 对话框只会显示文件的前 64 KB。
  3. 当我尝试在 MFC 项目中包含 ExceptionHandler.cpp 时,出现编译器错误 ExceptionHandler.cpp(823) : fatal error C1010: unexpected end of file while looking for precompiled header directive。如何解决?
    在使用 ExceptionHandler 的项目中,如果该项目使用预编译头,你必须将 **ExceptionHandler.cpp** 的 **C/C++ 预编译头** 设置更改为 **不使用预编译头**。请确保为 **所有配置** 进行此操作。

    XCrashReport screenshot

  4. 我不需要注册表转储。可以排除它们吗?
    是的。在 RegistryDefines.h 文件中注释掉以下行:
    #define XCRASHREPORT_DUMP_REGISTRY
    此文件包含在 XCrashReport.exe 项目中。
  5. 我不需要小型转储。可以排除它们吗?
    是的。注释掉 ExceptionHandler.cpp 文件顶部的以下行:
    #define XCRASHREPORT_WRITE_MINIDUMP
    此文件是你包含在你的应用程序项目中的文件之一。
  6. 我想重命名创建的默认错误文件。如何操作?
    你可以编辑 CrashFileNames.h 文件。
    // CrashFileNames.h  Version 1.0
    //
    // Author:  Hans Dietrich
    //          hdietrich2@hotmail.com
    //
    // This software is released into the public domain.
    // You are free to use it in any way you like, except
    // that you may not sell this source code.
    //
    // This software is provided "as is" with no expressed
    // or implied warranty.  I accept no liability for any
    // damage or loss of business that this software may cause.
    //
    /////////////////////////////////////////////////////////////////////
    
    #ifndef CRASHFILENAMES_H
    #define CRASHFILENAMES_H
    
    #define XCRASHREPORT_MINI_DUMP_FILE     _T("CRASH.DMP")
    #define XCRASHREPORT_ERROR_LOG_FILE     _T("ERRORLOG.TXT")
    #define XCRASHREPORT_REGISTRY_DUMP_FILE _T("REGISTRY.TXT")
    #define XCRASHREPORT_CRASH_REPORT_APP   _T("XCrashReport.exe")
    
    #endif //CRASHFILENAMES_H
    此文件是你包含在你的应用程序项目中的文件之一,也用于 XCrashReport.exe。两者都需要重新编译。
  7. 如何更改要转储的注册表部分?
    你可以使用 XCrashReport.ini 文件(参见上文),或者你可以编辑 RegistryDefines.h 文件。
    // RegistryDefines.h  Version 1.0
    //
    // Author:  Hans Dietrich
    //          hdietrich2@hotmail.com
    //
    // This software is released into the public domain.
    // You are free to use it in any way you like, except
    // that you may not sell this source code.
    //
    // This software is provided "as is" with no expressed
    // or implied warranty.  I accept no liability for any
    // damage or loss of business that this software may cause.
    //
    //////////////////////////////////////////////////////////////////////
    
    #ifndef REGISTRYDEFINES_H
    #define REGISTRYDEFINES_H
    
    #define XCRASHREPORT_DUMP_REGISTRY
    #define XCRASHREPORT_REGISTRY_KEY \
                         _T("HKCU\\Software\\CodeProject\\XCrashReportTest")
    
    #endif //REGISTRYDEFINES_H
    
    
    此文件用于 XCrashReport.exe
  8. 我们可以在我们的(共享软件/商业)应用程序中使用 XCrashReport 吗?
    是的,你可以免费使用 XCrashReport,无需支付任何许可费用。如果在你的“关于”对话框或启动屏幕中承认我的版权将很好,但这取决于你。

链接

这是第一部分到第四部分中提到的所有链接的累积列表。
Bruce Dawson 的文章“Release mode debugging with VC++” http://www.cygnus-software.com/papers/release_debugging.html
Bruce Dawson 在 Game Developer Magazine 上的原始异常处理程序代码 ftp://ftp.gdmag.com/pub/src/jan99.zip
Mike Carruth 的文章“Add Crash Reporting to Your Applications with the CrashRpt Library” https://codeproject.org.cn/debug/crash_report.asp
Grant McDorman 对 Mike Carruth 代码的改进 http://www3.sympatico.ca/grant.mcdorman/
XListCtrl - 具有子项格式化的自定义绘制列表控件 https://codeproject.org.cn/listctrl/xlistctrl.asp
XColorStatic - 一个彩色静态控件 https://codeproject.org.cn/useritems/XColorStatic.asp
XHyperLink - 又一个超链接控件 https://codeproject.org.cn/useritems/XHyperLink.asp
XZip 和 XUnzip - 在你的应用程序中添加 zip 和/或 unzip 功能,无需额外的 .lib 或 .dll https://codeproject.org.cn/cpp/xzipunzip.asp
Microsoft 调试工具,包括 WinDbg 和 dbghelp.dll http://www.microsoft.com/whdc/ddk/debugging
MiniDumpWriteDump API http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/base/minidumpwritedump.asp
第一部分 https://codeproject.org.cn/useritems/XCrashReportPt1.asp
第二部分 https://codeproject.org.cn/useritems/XCrashReportPt2.asp
第三部分 https://codeproject.org.cn/useritems/XCrashReportPt3.asp

修订历史

版本 1.1 - 2003 年 10 月 19 日

  • 首次公开发布

用法

本软件已进入公有领域。你可以随意使用它,但不得出售此源代码。如果你修改或扩展它,请考虑将新代码发布到这里供大家分享。本软件按“原样”提供,不含任何明示或暗示的保证。对于本软件可能造成的任何损害或业务损失,本人概不负责。

© . All rights reserved.