20

多年来,我在许多游戏项目中使用了 minidump,它们似乎有大约 50% 的机会拥有有效的调用堆栈。我能做些什么来让他们有更好的调用堆栈?

我尝试将最新的 dbghelp.dll 放在 exe 目录中。这似乎对一些人有所帮助。

Visual Studio 2008 或 2010 更好吗?(我仍在使用 VS 2005)。

我使用的代码看起来像这个示例

4

5 回答 5

30

要提高转储中调用堆栈的准确性,您可以做的一件事是使用除 Visual Studio 之外的调试器 - 具体来说,使用 WinDbg 或其他使用 dbgeng.dll 中的“Windows 调试器”调试引擎的工具(相反到 Visual Studio 使用的“Visual Studio Debugger”调试引擎)。

根据我们的经验,WinDbg 在从 Visual Studio 生成不可用或非常不准确的调用堆栈的相同转储中生成良好的调用堆栈方面是 100% 可靠的。据我所知,如果未处理的异常是崩溃的根源,WinDbg 会自动执行重建/恢复异常调用堆栈的棘手过程,但 Visual Studio 不会(或不能?)。两个调试器使用不同的启发式方法来解释堆栈

WinDbg 一开始可能会让人望而生畏,所以这里是我的快速指南,教你如何使它更容易,甚至避免直接使用它。

提取良好调用堆栈的凡人指南

这些按从“最快/最容易”到“最慢/最难解释”的顺序排列。

  1. 最简单的选择:使用Microsoft 的 DbgDiag

这是一个鲜为人知的工具,可以自动对常见问题进行大量分析,并且非常简单,可以提供给非程序员甚至客户。它快速且几乎万无一失,并且已成为我快速分析传入故障转储的“首选”工具。

  • 启动“DebugDiag 分析”应用程序
  • 选中主页上的“CrashHangAnalysis”复选框
  • 将转储拖放到主页上的“数据文件”窗格中
  • 点击“开始分析”


几秒钟到几分钟后,它会输出一个漂亮的 .mhtml 文件,其中包含对问题的分析、所有相关线程的信息、完整的调用堆栈等。所有超链接且易于使用。

DebugDiag 甚至可以自动执行一些在 WinDbg 中可能但很痛苦的更复杂的分析(例如跟踪应用程序中的 350 个线程中的哪个线程导致了死锁)。

注意:出于安全原因,Chrome 不会下载或打开 .mhtml 文件,因此您必须在 Internet Explorer 或 Microsoft Edge 中打开它才能使用。这很烦人,我已经向 DebugDiag 团队 (dbgdiag@microsoft.com) 提出了将格式更改为纯 HTML 的请求

  1. 中间选项:安装 WinDbg 作为 Visual Studio 的备用调试引擎
  • 如果尚未安装 Visual Studio,请安装它。这需要在下一步之前完成。
  • 安装Windows 驱动程序工具包 (WDK)
  • 启动 Visual Studio,并且(这部分很重要!)使用新的“文件 -> 打开 -> 崩溃转储...”选项打开转储。这将使用 Windows 调试器调试故障转储(如果您改为在 Visual Studio 上拖放转储或使用标准的“文件 -> 打开 -> 文件...”选项打开转储,它将对其进行调试使用旧的 Visual Studio 调试引擎...所以要小心使用正确的选项)。
  • 您现在应该能够看到正确的调用堆栈并使用 Visual Studio GUI 进行导航,尽管有些事情的工作方式不同(监视窗口需要使用不熟悉的 WinDbg 语法,线程 ID 不同等)。注意:Visual Studio UI 可能非常缓慢,尤其是在涉及多个线程并且“线程”或“并行堆栈”窗口打开时。
  1. 硬核选项:直接使用WinDbg
  • 启动 WinDbg.exe
  • 将转储拖放到 WinDbg 窗口中
  • 键入!analyze -v并按 Enter。一段时间后,WinDbg 会吐出一个崩溃调用堆栈,以及它对问题根源的估计。如果您正在分析死锁,您可以尝试一下!analyze -v -hang,WinDbg 通常会向您显示所涉及的依赖链。


在这一点上,您可能拥有您需要的所有信息!但是,如果您想在 Visual Studio 调试器中检查进程状态,您可以采取以下附加步骤:

  • 在 Visual Studio 中打开故障转储
  • 右键单击调用堆栈窗口并选择“转到反汇编”
  • 将 WinDbg 输出调用堆栈顶行的十六进制地址粘贴到反汇编窗口的“地址”栏中,然后按 Enter。您现在位于崩溃的位置,查看反汇编代码。
  • 在反汇编窗口中右键单击并选择“转到源代码”以转到该位置的源代码。现在您正在查看崩溃站点的源代码。

注意:以上所有都需要配置正确的符号服务器路径,否则您将无法解析调用堆栈中的符号。我建议设置_NT_SYMBOL_PATH 环境变量,以便 Visual Studio、WinDbg 和 DebugDiag 自动使用它。

于 2015-10-28T14:50:17.437 回答
6

您的调用堆栈中缺少什么?您是否有一堆地址无法解析为有效的函数名称(即 0x8732ae00 而不是 CFoo:Bar())?如果是这样,那么您需要将 .PDB 放在调试器可以找到它们的位置,或者设置符号服务器并在“模块”窗格的右键单击上下文菜单中设置“符号路径”。

每次有人签入新的 Perforce 更改列表时,我们都会存储每个二进制文件中的每个 .PDB,这样当办公室内的任何人或零售店的任何客户返回转储时,我们就会拥有与他们之前的游戏版本相对应的 .PDB跑步。设置符号服务器和路径后,我所要做的就是双击 .mdmp,它每次都能正常工作。

或者您是否有一个似乎只有一个函数的调用堆栈?就像,0x8538cf00 在堆栈中没有其他任何东西?如果是这样,那么您的崩溃实际上是堆栈本身被损坏。如果回链中的返回地址已经被覆盖,调试器自然无法解析它们。

有时您还会发现实际发出 minidump 的线程并不是引发导致崩溃的异常的线程。查看“线程”窗口以查看其他线程之一是否包含有问题的代码。

如果您正在调试“发布”版本——也就是说,编译时打开了所有优化标志——您将不得不接受调试器将无法找到局部变量和其他一些数据的事实。这是因为启用优化意味着允许编译器将数据保存在寄存器中、折叠计算,并且通常会执行各种阻止数据实际写入堆栈的操作。如果这是您的问题,那么您需要打开反汇编窗口并手动跟踪数据,或者重建调试二进制文件并在您可以查看的地方重现问题。

于 2009-10-12T05:01:21.437 回答
4

如果需要堆栈转储,请关闭帧指针优化。帧指针用于显式定义堆栈帧。没有它们,调试器必须推断出每一帧的位置。

于 2009-10-12T08:48:48.157 回答
1

记录小型转储的代码不太可能相关。minidump 记录的主要内容是模块信息(用于获取符号)和所有线程堆栈的全部内容。除了那些基本信息(总是被记录下来的)之外,其他的都不重要了。

获得好的符号(包括 PE 文件)对于堆栈遍历至关重要。更多细节可以在这里找到:https ://randomascii.wordpress.com/2013/03/09/symbols-the-microsoft-way/

我发现 Visual Studio 在显示调用堆栈方面通常很可靠。它会自动显示异常记录中的相关调用堆栈,并且使更改线程变得容易,以便您可以看到所有线程的调用堆栈。它有时确实会尝试“隐藏”它认为可能会让您感到困惑的细节——这是好是坏取决于您的技能水平。

Windbg 默认显示记录崩溃转储的代码的调用堆栈,而不是崩溃调用堆栈。Windbg 要求您使用“.ecxr”或“!analyze -v”来查看崩溃堆栈。我觉得这很烦人。Windbg 还需要更多配置才能有用。

这两个调试器确实有不同的堆栈行走启发式。例如,如果您调用或返回地址零,则需要这些启发式方法,因为该地址没有展开信息。对于失败指令在正常代码中的“干净”崩溃,这些启发式方法不太重要。

在过去的十年中,堆栈行走几乎肯定有所改善。VS 2015 社区版功能非常强大,而且是免费的,你不妨试试。

如果你使用windbg,那么你可以尝试一些实验:

!vc7fpo - toggles some of the windbg heuristics.
!stackdbg d, 7, f - turns on windbg stack walk
k1 - walks one level of the stack, spitting diagnostics as controlled by !stackdbg
dds esp - dumps the raw contents of the stack, doing a symbol lookup on each pointer

如果您升级到 VS 2015 并且仍然遇到问题,那么堆栈遍历失败很可能是您所看到的崩溃所特有的。如果缓冲区溢出在崩溃之前超出堆栈,那么调用堆栈将被不可挽回地损坏。您的问题关于您所看到的故障的信息太少,无法给出明确的诊断。我发现这两个调试器的堆栈显示相当可靠,但我通常也理解为什么它们有时会失败,当这种情况发生时,我仍然可以提取我需要的信息。

于 2015-11-02T23:44:30.587 回答
0

我不使用小型转储,而是通过“手动”将堆栈转储到日志文件中(请参阅www.ddj.com/cpp/185300443如何使用 Windows x64 记录堆栈帧)。

我遇到了像你一样的类似行为:有时有一个有效的调用堆栈,有时没有。在少数情况下,堆栈可能真的被破坏了。在所有情况下,可能有 1/3 根本不调用已安装的异常处理程序!我猜想它在某种程度上是Windows结构化异常处理的问题。

于 2009-10-12T08:41:50.213 回答