标签 CrashRpt 下的文章

当在软件发布后收到大量错误报告时,在数百个报告中,可能只有几个不同的问题,而其他报告只是重复了有关这些问题的信息。在 Visual Studio 或 WinDbg 中手动打开这么多报表并分析它们的内容可能会浪费很多时间。

自动处理错误报告的最简单方法是安装 CrashFix web 应用程序。CrashFix 服务器允许轻松地接收、存储、组织和分析由您的 C++ 应用程序发送的崩溃报告。

- 阅读剩余部分 -

收到的崩溃报告的计数可能取决于软件对各种异常情况的健壮性和软件的流行程度。所以,如果你每天都收到数百个错误报告,这没什么不寻常的。如果有许多传入的错误报告,你可以在发布后的一段时间内监视和分析它们,并准备一个热修复发布。

错误报告的目的是帮助你确定问题的原因并尝试修复它。关键字是‘try’,因为在大多数情况下,你不能像修复正常错误那样修复崩溃,因为你通常不能在你的机器上重现错误。

通常情况下,你的软件会随着每个演进版本变得越来越稳定,而收到的错误报告也越来越少。

1.错误报告的结构

崩溃报告通常是包含几个文件的 ZIP 文件,例如:

  • 崩溃 minidump (crashdump.dmp);
  • 崩溃描述 XML (crashrpt.xml);
  • 可选的桌面截图;
  • 可选的屏幕捕捉视频文件;
  • 以及其他可选的特定于应用程序的文件,例如日志文件。

2.分析崩溃描述 XML

The crash description is an XML file that contains basic information about the crash. It complements the information contained in mindump file.

崩溃描述是一个 XML 文件,其中包含关于崩溃的基本信息。它补充了 mindump 文件中包含的信息。

下面是一个典型的崩溃描述文件。

<?xml version="1.0" encoding="UTF-8" ?>
<CrashRpt version="1207">
  <CrashGUID>374db652-d268-4b0b-94dc-c014150c8cfe</CrashGUID>
  <AppName>CrashRpt Tests</AppName>
  <AppVersion>1.2.7</AppVersion>
  <ImageName>D:\Projects\CrashRpt\src\bin\WTLDemo.exe</ImageName>
  <OperatingSystem>Windows 7 Ultimate Build 7100</OperatingSystem>
  <OSIs64Bit>1</OSIs64Bit>
  <GeoLocation>en-us</GeoLocation>
  <SystemTimeUTC>2010-05-15T08:21:49Z</SystemTimeUTC>
  <ExceptionType>11</ExceptionType>
  <GUIResourceCount>51</GUIResourceCount>
  <OpenHandleCount>71</OpenHandleCount>
  <MemoryUsageKbytes>8144</MemoryUsageKbytes>
  <CustomProps>
    <Prop name="VideoCard" value="nVidia GeForce 9800 GTX+"/>
  </CustomProps>
  <FileList>
    <FileItem name="crashdump.dmp" description="Crash Dump" />
    <FileItem name="crashrpt.xml" description="Crash Log" />
    <FileItem name="dummy.ini" description="Dummy INI File" />
    <FileItem name="dummy.log" description="Dummy Log File" />
    <FileItem name="screenshot0.png" description="Desktop Screenshot" />
  </FileList>
</CrashRpt>

根元素是 CrashRpt,它有一个名为 version 的属性,表示生成错误报告的 CrashRpt 库的版本。例如,“1103”的值表示版本 1.1.3,“1201”的值表示版本1.2.1。

元素 CrashGUID 是错误报告的唯一ID。例如,如果计划在数据库中存储崩溃报告,则可以将其用作主键。

元素 AppName 是作为 CR_INSTALL_INFO::pszAppName 结构成员传递给 crInstall() 的应用程序的名称。

元素 AppVersion 是作为 CR_INSTALL_INFO::pszAppVersion 结构成员传递给 crInstall() 的应用程序的版本。

元素 ImageName 是软件可执行映像的路径。

元素 OperatingSystem 是终端用户操作系统的友好名称,包括名称、构建号和服务包。这是很有用的信息,因为许多错误都是针对操作系统的。

元素 OSIs64Bit 是用户操作系统的位数,用户的操作系统是 64 位(等于1)或 32 位(等于0)。

元素 GeoLocation 表示用户的地理位置。

元素SystemTimeUTC 以 UTC 格式表示发生崩溃的时间。此值可用于按创建时间排序崩溃报告。

元素 UserEmail 是错误报告发送者的电子邮件地址。

元素 ProblemDescription 是用户提供的问题描述。

元素 FileList 文件列表包含错误报告中包含的文件列表。

元素 ExceptionType 是一个整数,表示错误的类型:

  • 0 SEH exception.
  • 1 C++ terminate() call.
  • 2 C++ unexpected() call.
  • 3 C++ pure virtual function call.
  • 4 C++ new operator fault.
  • 5 Buffer overrun error.
  • 6 Invalid parameter exception.
  • 7 C++ SIGABRT signal (abort).
  • 8 C++ SIGFPE signal (floating point exception).
  • 9 C++ SIGILL signal (illegal instruction).
  • 10 C++ SIGINT signal.
  • 11 C++ SIGSEGV signal (invalid storage access)
  • 12 C++ SIGTERM signal (termination request).

异常类型可用于将错误报告划分类别。

3.分析崩溃 Minidump

崩溃 minidump 文件(DMP文件)包含了应用程序崩溃时的各种状态信息。从技术上讲,崩溃 minidump 文件可能包含:

  • 一般系统信息(操作系统版本、CPU计数等);
  • 进程内存块(包括每个执行线程的全局变量、调用堆栈和局部变量的值);
  • 加载和卸载模块的列表(包括它们的版本和时间戳)。

Minidump 的大小通常是几十个千字节(几十KB),但是 Minidump 的实际内容取决于传递给 CR_INSTALL_INFO::uMiniDumpType 字段的 Minidump 类型。

要分析 CrashRpt 库生成的崩溃 minidump 文件,通常需要在 Visual Studio 或 WinDbg 中打开这些文件。

下面讲述在Visual Studio中打开 Minidump 的简单说明:

解压错误报告存档 ZIP 文件。它包含 crashdump.dmp 文件和其他几个文件。解压出 crashdump.dmp 文件后,你可以在 Visual Studio 中打开它(双击该文件即可打开)。如果你安装了多个版本的 Visual Studio ,则右键单击文件名,并在上下文菜单中选择 Open With,然后选择要使用的 Visual Studio 版本。

打开后将出现一个新的 Visual Studio 窗口,显示 crashdump.dmp 上的一般信息。

在 Visual Studio 窗口中,单击 Debug with Native Only 以加载 minidump 数据。加载数据后,你应该能够看到包含异常信息的对话框,比如异常地址、模块名称、异常代码及其文本描述(参见下图)。按“中断”按钮继续。

在 Output 窗口中,你应该能够看到 minidump 加载过程的日志。如果没有看到“输出”窗口,请打开“菜单”视图并单击“输出”菜单项。

由于调试符号似乎已经为应用程序的主要模块成功加载,在代码窗口(参见下图)中,你应该能够看到源代码中发生异常的位置。在 crEmulateCrash() 函数内的第829行,我们可以看到文件 crashrt .cpp 中发生了崩溃,原因是将一个空指针变量赋值为 0。如果崩溃的原因很清楚,你甚至可以在适当的地方编辑源代码来修复问题。

通过将鼠标移动到变量名上,可以看到局部变量的值。该值(如果已知)显示在工具提示窗口中。并不是所有变量的值都可以恢复,这取决于你使用的最小类型和其他因素,比如代码优化。

为了更好地理解崩溃的原因,我们想知道什么 c++类或函数引起的崩溃。我们可以在调用堆栈窗口的帮助下做到这一点(参见下图)。如果调用堆栈窗口被隐藏,打开菜单Debug->窗口并选择“调用堆栈”菜单项。

堆栈跟踪的每一行(也称为堆栈帧)包含代码所属模块的名称、符号(函数或类)的名称、符号代码开头的偏移量、源文件和行号。向下移动堆栈,我们可以看到 crEmulateCrash()CMainDlg::DoCrash() 类方法调用,而 CMainDlg::OnOK() 方法在单击按钮时调用 crEmulateCrash()

通常,程序有几个执行线程。你可以使用 threads 选项卡在线程之间切换,并浏览每个线程的堆栈。我们可以看到应用程序中有两个线程:主线程和工作线程。这个异常发生在主线程中,第二个线程没有崩溃。

最后,当你完成了对 minidump 程序数据的分析后,关闭 Visual Studio 窗口。

4.常见问题

Visual Studio 会自动定位所有二进制文件、PDB文件和源文件。但通常通常情况下是, Visual Studio 无法找到这些文件,或者当这些文件的时间戳不匹配,或文件被更改时,读取 minidumps 文件可能会有一些问题。

为了找到匹配的二进制文件、PDB文件和源代码文件,Visual Studio 使用了在编译和链接时嵌入到 PDB 文件中的绝对路径。因此,当你不删除/移动/修改用于构建解决方案的文件时,Visual Studio 可以自动定位它们。但是,如果你删除/移动或修改这些文件,Visual Studio 将无法找到它们。

你可以通过手动指定符号搜索路径来解决这个问题。在 minidump 窗口中,单击“设置号路径”并输入 PDB 文件所在目录的路径,参见下图:

设置完成后重新加 minidump。你可以看到,堆栈跟踪现在已正确恢复。但是仍然有一个问题——没有显示正确的源代码文件。在“调用堆栈”窗口中,双击最顶层的堆栈框架。一个标题为“Find Source: crashrpt.cpp”的对话框将会出现。在此对话框中,浏览到保存源代码的文件夹并选择 crashrt .cpp 文件。现在正确的源文件应该显示在 Visual Studio 源代码窗口中。

这时,你应该能够正确地读出 minidump 。如果问题仍然存在,请确认是不是出了以下问题:

  • 确保编译后保存了正确匹配的二进制文件和/或 PDB 文件。确保保存了在编译/链接过程中生成的完全相同的二进制文件和 PDB 文件。如果重新生成(或部分重新生成)解决方案,则应该再次保存这些文件。
  • 如果程序的某些模块的符号已经加载,而你仍然无法读取堆栈跟踪,那么就没有为发生崩溃的模块生成调试符号。检查你是否设置了“程序数据库(/Zi)”(Program Database (/Zi) )编译器标记,并为应用程序的所有模块“生成调试信息(/DEBUG)”(Generate Debug Info (/DEBUG) )链接器标记。具体参考本系列文章第二篇。
  • 如果堆栈跟踪没有你预期的那么精确,原因可能是代码优化。建议你关闭省略帧指针(FPO)优化。具体参考本系列文章第二篇。