为什么Minidumps不能提供良好的调用堆栈?

时间:2009-10-12 04:55:46

标签: debugging callstack minidump dbghelp

多年来我在许多游戏项目中都使用过minidumps,他们似乎有大约50%的机会拥有一个有效的调用堆栈。我该怎么做才能让他们有更好的调用堆栈?

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

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

我使用的代码看起来像this sample

5 个答案:

答案 0 :(得分:25)

为提高转储中调用堆栈的准确性,您可以做的一件事是使用Visual Studio以外的调试器 - 特别是使用WinDbg或其他使用" Windows Debugger"在dbgeng.dll中找到的调试引擎(与#34; Visual Studio Debugger" Visual Studio使用的调试引擎相反)。

根据我们的经验,WinDbg在从Visual Studio生成无法使用或非常不准确的调用堆栈的相同转储中生成良好的调用堆栈方面100%可靠。据我所知,如果未处理的异常是崩溃的根源,WinDbg会自动执行tricky process of reconstructing/recovering the exception callstack,但Visual Studio不会(或不能?)。两个调试器使用different heuristics for interpreting stacks

WinDbg起初可能令人生畏,所以这里是我的快速指南,介绍如何让它更容易,甚至避免直接使用它。

提供好的问候语的纯粹凡人指南

这些是从"最快/最简单的" to"最慢/最神秘的解释"。

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

    这是一个鲜为人知的工具,可以自动对常见问题进行大量分析,而且非常简单,可以给非程序员甚至客户。它速度快,几乎万无一失,已经成为我的第四个去往"用于快速分析传入故障转储的工具。

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


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

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

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

  2. 中间选项:安装WinDbg作为Visual Studio的备用调试引擎

    • 安装Visual Studio(如果尚未安装)。这需要在下一步之前完成。
    • 安装Windows Driver Kit (WDK)
    • 启动Visual Studio,(这部分很重要!)使用新的"文件 - >打开 - > Crash Dump ......"打开转储的选项。这将使用Windows调试器调试故障转储(如果您在Visual Studio上拖放转储,或使用标准"文件 - >打开 - >文件...&#34,则 ;打开转储的选项,它将使用旧的Visual Studio调试引擎调试它...所以小心使用正确的选项)。
    • 您现在应该能够看到正确的调用堆栈并使用Visual Studio GUI进行导航,尽管有些工作方式不同(监视窗口需要使用不熟悉的WinDbg语法,线程ID不同等)。 注意:Visual Studio UI可能非常缓慢,特别是如果涉及许多线程并且“线程”#或者'并行堆栈'窗户是敞开的。
  3. 硬核选项:直接使用WinDbg

    • 启动WinDbg.exe
    • 将转储拖放到WinDbg窗口
    • 键入!analyze -v,然后按Enter键。经过一段时间后,WinDbg会吐出一个崩溃调用堆栈,并且还会估计出问题的根源。如果您正在分析死锁,可以尝试!analyze -v -hang,WinDbg会经常向您展示所涉及的依赖关系链。


    此时您可能拥有所需的所有信息!但是,如果您想在Visual Studio调试器中检查进程状态,则可以执行以下附加步骤:

    • 在Visual Studio中打开故障转储
    • 右键单击callstack窗口并选择"转到Disassembly"
    • 将WinDbg输出callstack顶行的十六进制地址粘贴到" Address"反汇编窗口栏,然后按Enter键。您现在正处于崩溃的位置,查看反汇编的代码。
    • 在反汇编窗口中单击鼠标右键,然后选择"转到源代码"转到该位置的源代码。现在,您正在查看崩溃站点的源代码。
  4. 注意:以上所有内容都要求配置正确的符号服务器路径,否则您将无法解析调用堆栈中的符号。我建议设置_NT_SYMBOL_PATH environment variable,以便Visual Studio,WinDbg和DebugDiag自动使用它。

答案 1 :(得分:6)

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

每次有人检查新的Perforce更改列表时,我们会存储每个二进制文件中的每个.PDB,这样当从办公室内的任何人或零售的任何客户收回转储时,我们都会将.PDB对应于版本的。他们正在运行的游戏。设置符号服务器和路径后,我所要做的就是双击.mdmp,每次都可以使用。

或者你有一个似乎只有一个功能的调用堆栈?比如,0x8538cf00在堆栈中没有任何其他东西?如果是这样,那么你的崩溃实际上是堆栈本身被破坏了。如果后面的返回地址被覆盖,调试器自然无法解析它们。

有时你也会发现实际发出minidump的线程不是引发崩溃的异常的线程。在Threads窗口中查看其中一个其他线程是否包含违规代码。

如果您正在调试“Release”构建 - 也就是说,在打开所有优化标志的情况下进行编译 - 您将不得不忍受调试器在查找局部变量和其他一些数据时遇到的问题。这是因为启用优化意味着允许编译器将数据保存在寄存器上,折叠计算,并且通常会执行各种阻止数据实际写入堆栈的事情。如果这是您的问题,那么您需要打开反汇编窗口并手动追踪数据,或重建调试二进制文件并重现问题,以便查看它。

答案 2 :(得分:4)

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

答案 3 :(得分:1)

记录minidump的代码不太可能相关。 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但仍有问题,那么堆栈行走失败可能是您遇到的崩溃所特有的。如果缓冲区溢出在崩溃之前使堆栈失效,则调用堆栈将不可逆转地损坏。你的问题关于你所看到的哪些失败给出明确诊断的信息太少。我发现两个调试器的堆栈显示相当可靠,但我也常常理解为什么它们有时会失败,当发生这种情况时,我仍然可以提取我需要的信息。

答案 4 :(得分:0)

我不使用minidumps,而是通过“hand”将堆栈转储到日志文件中 (见www.ddj.com/cpp/185300443How to Log Stack Frames with Windows x64)。

我遇到类似你的行为:有时会有一个有效的调用堆栈,有时候没有。在少数情况下,堆栈可能确实已损坏。在所有情况的1/3中,根本不会调用已安装的Exception处理程序!我猜它在某种程度上是windows结构化异常处理的问题。