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

使用 Minidump 和 Visual Studio .NET 进行应用程序的 Post-Mortem 调试

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (98投票s)

2002 年 3 月 7 日

16分钟阅读

viewsIcon

1364294

downloadIcon

7414

本文介绍了 minidump 的工作原理、如何让应用程序在崩溃时创建它们,以及如何使用 Visual Studio .NET 重新读取它们。

摘要:如果您的应用程序在客户站点崩溃,您现在可以使用 minidump 和 Microsoft® Visual Studio® .NET 调试器对其进行事后调试。本文介绍了 minidump 的工作原理、如何让应用程序在崩溃时创建它们,以及如何使用 Visual Studio .NET 重新读取它们。Minidumps 是 Microsoft 错误报告程序提高 Windows 操作系统和 Visual Studio .NET 等应用程序可靠性的关键。本文还介绍了如何使用 Microsoft 符号服务器自动查找系统组件的符号。本文假设您熟悉 Win32 和 C++ 编程。

目录

什么是 Minidump?

Minidump 是一个包含已崩溃应用程序最重要部分的文件。它在用户计算机上写入,然后客户可以将其提交给开发人员。开发人员可以加载该转储文件以帮助确定崩溃原因并开发修复程序。

自 Windows NT 早期以来,Dr. Watson 程序一直能够生成带有 .dmp 扩展名的崩溃转储文件。然而,由于两个问题,它们不如预期的有用

  1. 它们非常大。应用程序的转储文件包含整个进程空间的每个字节,因此像记事本这样简单的东西崩溃,文件大小会达到几兆字节,而像 Word 这样的应用程序崩溃,文件大小可能高达数百兆字节。这些文件太大了,无法通过电子邮件或 FTP 发送。
  2. 它们的内容不一定有用。Dr. Watson 实际上是一个即时 (JIT) 调试器,调试器很难获取已加载模块的完整路径。像 Visual Studio 调试器这样的完整调试器会执行一系列步骤来获取路径,但 Dr. Watson 没有。这通常会导致模块名称无用,例如 MOD0000 等。

Minidumps 的设计旨在通过多种方式解决这些问题

  • 不保存整个进程空间,只保存某些部分。没有必要保存 Kernel32.dll 这样的模块副本;如果包含了版本号,很容易从 Windows CD 获取副本。应用程序的实际内存堆默认不保存在 minidump 中;对于出乎意料的高比例崩溃,调试它并不需要堆。但是,如果需要,您可以保存堆。
  • Minidump 保存代码致力于获取模块的准确完整信息,包括它们的名称、路径、版本信息和内部时间戳。
  • Minidump 保存代码还获取线程列表、它们的上下文(即寄存器集)以及它们堆栈后面的内存。
  • 整个文件都被压缩,进一步减小了尺寸。Windows XP 上记事本的 minidump 大约是 6K,比同一进程以前的崩溃转储文件小近 300 倍。

注意:内核模式 minidump(Windows XP 在计算机停止响应后生成)也存在,但本文讨论的是更常见的用户模式 minidump。

创建 Minidump

有三种方法可以创建 minidump

  • 在您自己的应用程序中添加代码,以便在应用程序发生未处理的异常时写入 minidump。
  • 在 Visual Studio .NET 的集成开发环境中,在调试应用程序时,单击“调试”菜单上的“保存转储”。
  • 什么也不做。

第一种方法将在接下来的几节中更详细地讨论。

第二种方法仅在已配置调试器的工作站上可用,这可能只在组织内部(例如,与其他开发人员或测试人员)有用。如果您使用 Visual Studio .NET 调试崩溃,然后可以单击“调试”菜单上的“另存为转储”。您可以选择保存为“Minidump”或“Minidump With Heap”。您不需要配置任何符号或 PDB 即可保存转储文件;但是,稍后读取它时会需要它们。

第三种方法仅在 Windows XP 上可用,如果应用程序发生未处理的异常且未设置 JIT 调试器,Windows XP 会自动创建 minidump。此外,minidump 会直接提交给 Microsoft,因此您将无法确定原因。

构建问题

要配置您的应用程序以在它停止响应时创建转储文件,您必须将您的构建配置为生成完整的调试信息,特别是对于零售构建。生成 PDB 后,您还必须归档发送给客户的每个二进制文件及其匹配的 PDB;稍后您将需要 PDB 来调试客户提交的任何 minidump。

另外,请确保您正确设置了二进制文件的版本信息。您发送的每个组件的每个发行版都应具有不同的版本,以便您将其与 minidump 匹配。版本字段有助于您匹配二进制文件。但是,调试器本身不使用版本信息;它根据 PE 文件头中的内部时间戳匹配二进制文件。

在输出方面,为发布构建生成调试信息影响很小。会生成一个 PDB 文件,占用构建机上的一些空间,并且二进制文件会大几百字节,以便在 PE 文件内的调试目录中记录 PDB 文件名。您不应向客户提供 PDB;这可能会使客户更容易逆向工程您的应用程序。

使用 MiniDumpWriteDump 写入 Minidump

保存 minidump 的关键 API 是 MiniDumpWriteDump,它从 Dbghelp.dll 导出,该 DLL 是 Platform SDK 附带的可重新分发 DLL。确保您使用的是 Windows XP 版本 5.1.2600;早期 beta 和 release candidate 版本在此 API 上存在问题,而 5.0.x 版本来自 Windows 2000 且不导出该函数。如果您使用的是早于 5.0 的版本,那么它来自 Systems Debugger 包(包括 WinDbg 等)并且不可重新分发。

要调用该 API,您需要通过 SetUnhandledExceptionFilter API 设置一个未处理的异常过滤器来捕获崩溃。这允许在应用程序中发生未处理的异常时几乎随时调用过滤器函数。在某些未处理的异常(如双重堆栈故障)中,操作系统会立即终止应用程序,而不调用过滤器或 JIT 调试器。

在您的过滤器函数中,您必须加载 Dbghelp.dll。这不像调用 LoadLibrary 那么简单;与 Windows 2000 一样,您将访问 System32 目录中的那个,它没有正确的导出。附加文件中的示例代码尝试从与 EXE 相同的位置加载。将正确版本的 Dbghelp.dll 安装到与 EXE 相同的目录中;如果这不起作用,则代码将回退到普通的 LoadLibrary,这只有在应用程序在 Windows XP 上运行时才有效。

加载 DLL 后,它会检查命名的导出;如果正确,它将创建一个具有适当名称的文件,例如保存到 Temp 目录并使用应用程序名称和 .dmp 扩展名。然后将此句柄传递给 API 本身,以及一些附加信息,如进程 ID 和转储文件类型。示例使用了 MiniDumpNormal。您可能希望或-入 MiniDumpWithDataSegs 标志值,这相当于 Visual Studio 调试器中的 **Minidump With Heap** 选项。这确实会导致转储文件大小显著增大。

一旦创建了 .dmp 文件,应用程序就会通知用户其存储位置。然后用户可以通过电子邮件或 FTP 将文件发送给您进行调查。

要使用提供的示例代码,请添加 mdump.h 文件,并在全局范围内声明一个 MiniDumper 对象。它的构造函数接受一个参数,该参数应为 minidump 文件的基本名称。将 mdump.cpp 添加到您的项目中。您需要在与 EXE 相同的目录中拥有正确的 Dbghelp.dll 文件才能成功运行。

您不能使用调试器调试写入 minidump 的代码(在示例代码中,是 Minidumper::TopLevelFilter)。如果调试器附加到进程,则永远不会调用未处理的异常过滤器。如果遇到问题,您需要使用 MessageBox 调试。

使用 Visual Studio .NET 读取 Minidump

本节使用 Windows 2000 中手动创建的记事本 minidump 并在 Windows XP 中进行调试的示例。

启动 Visual Studio .NET,然后在“文件”菜单上,单击“打开解决方案”。将“文件类型”下拉菜单更改为“转储文件 (*.dmp; *.mdmp)”,导航到 minidump,然后通过单击“打开”创建默认项目。

要使用调试器启动转储,请按 F5。这将为您提供入门信息。调试器会创建一个假的进程;在“输出”窗口中,会显示各种模块加载消息。调试器仅重新创建了已崩溃进程的状态。在显示“EXE 包含调试信息”警告后,调试器会因用户的崩溃而停止,例如访问冲突。如果您随后检查“调用堆栈”窗口,您会注意到缺少符号和有用信息。

图 1. 无符号的初始堆栈

要读取 minidump,您通常需要相关二进制文件的副本。要查找正确的二进制文件,请打开“模块”窗口。

图 2. 无二进制文件的初始模块

图 2 显示了记事本示例,并演示了两件事。首先,二进制文件的路径用星号标记,表示用户工作站上的路径,但您的计算机上无法在该位置找到二进制文件。其次,“信息”字段显示“找不到匹配的二进制文件”。查找匹配二进制文件的关键是查看“版本”字段和文件名。在此示例中,大多数系统文件的版本是 2195,这表示 Windows 2000。但是,它没有立即显示确切的服务包 (SP) 或质量工程修复 (QFE)。有关更多信息,请参阅 此链接 处的 DLL 帮助数据库。

此时,您需要找到一个 Windows 操作系统 CD 或具有正确版本的用户工作站,并将正确版本复制到某个目录中。通常不需要找到进程中的每个二进制文件,但找到每个相关调用堆栈中出现的每个二进制文件很重要。这通常会同时涉及操作系统二进制文件(如 Kernel32.dll)和您的应用程序二进制文件(在此示例中为 Notepad.exe)。

找到二进制文件并将其复制到本地目录后,在“调试”菜单上,单击“停止调试”。在“解决方案资源管理器”中,右键单击项目图标,然后在快捷菜单中选择“属性”。这将带您进入“调试”页面。在“命令行参数”中填入 MODPATH,后跟一个等号,再后跟一个分号分隔的要查找二进制文件的位置列表。在此示例中,它是

MODPATH=m:\sysbits

设置路径后,按 F5 重新加载 minidump。MODPATH 重用命令行参数以将值传递给调试器;Visual Studio .NET 的未来版本可能有一种更好的设置方法,也许是“属性”对话框中的一个选项。

虽然查找二进制文件不太可能改善调用堆栈,但它应该可以解决“模块”窗口中的一些问题,如图 3 所示。

图 3. 包含二进制文件的模块

它不再显示“找不到匹配的二进制文件”,而是显示“找不到或无法打开必需的 DBG 文件”或“未加载符号”的组合。第一个消息会出现在使用 DBG 作为其调试信息的系统 DLL 上。第二个消息会出现在使用 PDB 的 DLL 上。获取匹配的二进制文件不一定会改善您的调用堆栈;您还需要匹配它们的调试信息。

方法 A:手动获取符号

要完全分析 minidump,您应该找到所有内容的调试信息,但为了节省时间,您可以只找到您需要的信息。示例堆栈列出了 User32.dllKernel32.dll,因此它们需要匹配的调试信息。

匹配的调试信息

操作系统 所需文件
Windows NT 4 DBG
Windows 2000 DBG、PDB
Windows XP PDB

查找系统符号的最佳地点是 此处。您也可以在随 Windows NT Server 和 Windows 2000 Server 操作系统附带的支持光盘上找到系统符号。在此示例中,它们被复制到了二进制文件的位置。在实际情况下,您会看到非 Microsoft 的二进制文件,因此您需要它们的 PDB。在此示例中,还复制了记事本的 DBG 和 PDB,因为它是使用的示例应用程序。

在“调试”菜单上单击“停止调试”后,按 F5 将列出调用堆栈,如图 4 所示。您可能会发现,当您添加新的二进制文件和调试信息时,您的调用堆栈会发生变化。这是可以预期的;调用堆栈只能在具有调试信息的情况下准确遍历,因此随着您添加更多信息,您的堆栈会变得更准确,这通常会暴露原始堆栈中不存在的额外帧。

在此示例中,没有发生崩溃。在实际情况下,您将拥有足够的信息来确定大约 70% 的用户崩溃的原因。此外,此堆栈是使用 Microsoft 为系统组件发布的剥离符号创建的,因此没有行号信息。对于您自己的具有完整 PDB 的二进制文件,您将获得一个更丰富的堆栈。

图 4. 带有符号和二进制文件的调用堆栈

符号服务器

如果您需要处理大量 minidump 并执行通用调试,存储和访问所有二进制文件和 PDB/DBG 文件可能会很困难。Windows NT 开发了一项称为符号服务器的技术,最初是为了存储符号而设想的,但后来扩展到支持查找二进制文件。Windows NT 调试器是第一个支持此功能的工具,但 Visual Studio .NET 也将其作为一项未公开的功能支持。有关符号服务器的更多信息,请参阅 此处

您还可以从 Microsoft 符号服务器检索符号。这些符号将在本地为您缓存和索引。

方法 B:轻松获取符号:使用符号服务器

首先,请访问 此链接 并下载调试工具。您需要 Symsrv.dll,它需要位于路径中。您可以将其复制到 devenv.exe 旁边,或复制到您的 System32 目录中,以便 Visual Studio .NET 可以访问它。复制 Symsrv.dll 后,您可以安全地卸载调试工具。您还需要一个本地目录。在此示例中,请创建一个本地目录,例如 C:\localstore

在“项目属性”对话框中,将“调试”页面上的“符号路径”设置为

SRV*c:/localstore*http://msdl.microsoft.com/download/symbols

string 告诉调试器使用符号服务器获取符号并创建一个本地符号服务器,符号将被复制到其中。现在,当您在 minidump 上按 F5 时,符号将从 Microsoft 网站检索,复制到本地存储,然后由调试器使用。第一次执行此操作后,您的性能将有所提高,因为它们将从本地存储而不是从 Web 检索。

调试非 Microsoft 应用程序时,您应该结合使用方法 AB。使用 A 获取系统组件,并通过分号分隔来添加您的二进制文件和符号的路径,例如

c:\drop\build\myapp;SRV*c:\localstore*http://msdl.microsoft.com/download/symbols

由于符号服务器是 Visual Studio .NET 的一项未公开功能,因此没有错误报告。如果语法不正确或 Symsrv.dll 不在路径中,符号将加载失败,并显示错误“未加载符号”。您还可以使用符号服务器存储和检索二进制文件,但 MODPATH 语法需要使用 symsrv*symsrv.dll* 而不是 SRV*

注意:Microsoft 符号服务器不包含二进制文件,但您可以创建的任何符号服务器都可以。

符号服务器不仅对 minidump 有效,也适用于“实时”调试。要做到这一点,您需要正确设置“调试”页面上的“符号路径”。

Microsoft 如何使用 Minidumps

一年多以来,Microsoft 一直在使用 minidumps 来提高其应用程序的质量。Microsoft Internet Explorer 5.5 和 Microsoft Office XP 是首批随新版 Dr. Watson 一起发布的版本,该实用程序可以在应用程序停止响应时捕获,创建 minidump,并询问用户是否要将信息提交给 Microsoft。

如果用户单击“发送错误报告”,则 Dr. Watson 会创建一个 minidump 并将其提交到 Microsoft 网站上的服务器。该实用程序在单独的进程中执行用户提示和 minidump 保存,该进程从崩溃的进程派生。这减少了对已崩溃应用程序的负载,增加了从用户那里获取有效 minidump 的机会。

进一步改进

在服务器端,minidumps 被分析到相似的崩溃区域,基于崩溃发生的位置和组件。这使产品团队能够统计应用程序崩溃的频率以及特定崩溃的发生频率。团队还会收到实际的崩溃 minidumps 以供进一步调查。在某些完全理解的崩溃情况下,用户会自动被引导到一个包含已知解决方法信息的网页,或者包含纠正问题的修复程序。自 Internet Explorer 5.5 和 Office XP 发布以来,许多其他产品团队使用类似的技术来收集崩溃信息。它也是 Windows XP 的标准组成部分。

本文讨论的示例为理解 minidumps 提供了基础。示例未讨论从用户那里检索转储文件。最简单的情况下,可以通过电子邮件提示用户发送 minidump。此外,可能会出现潜在的隐私问题,因为用户数据可能存在于堆栈上,并且在包含完整堆的 minidump 中保证存在。应向用户明确说明这一点。Microsoft 的数据收集策略(其中还包含有关 minidumps 具体内容的更多信息)位于 此链接

此外,从发生未处理异常的 Windows 服务创建 minidumps 会带来额外的挑战,这与桌面访问(例如,如果没有人使用控制台,则无法提示他们)和安全上下文有关。

结论

Minidumps 是一项新技术,用于在用户工作站上对崩溃进行事后调试。可以轻松地向现有应用程序添加代码,以便在发生未处理的异常时自动创建它们。Visual Studio .NET 可以轻松地重新读取它们以重现崩溃并允许开发人员调试问题。符号服务器可用于大大简化查找系统符号以辅助此分析。

许可证

本文没有明确的许可证附加,但可能包含文章文本或下载文件本身的使用条款。如有疑问,请通过下面的讨论板联系作者。作者可能使用的许可证列表可在 此处 找到。

© . All rights reserved.