2009-10-12 26 views
11

多年来,我在很多游戏项目中都使用过小型转储,他们似乎拥有有效调用堆栈的几率为50%。我能做些什么来让他们拥有更好的调用堆栈?为什么Minidump不能提供良好的调用堆栈?

我试过把最新的dbghelp.dll放在exe目录下。这似乎有助于一些。

Visual Studio 2008或2010是否更好? (我仍然在VS 2005)。

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

回答

6

你的callstack中缺少什么?你有一堆地址不能解析为有效的函数名称(即0x8732ae00而不是CFoo:Bar())吗?如果是这样,那么你需要的是将你的.PDBs放到你的调试器可以找到它们的位置,或者设置一个symbol server并在Modules面板的右键上下文菜单中设置“Symbol Paths”。

每次有人检入新的Perforce更改列表时,我们都会从每个二进制文件中存储每个.PDB,以便当办公室内部的任何人或零售店的任何客户发出转储时,我们将.PDB对应于他们正在运行的游戏。通过设置符号服务器和路径,我所要做的只是双击.mdmp,并且每次都有效。

或者你有一个看起来只有一个函数的调用堆栈?像,0x8538cf00在堆栈中没有其他任何东西?如果是这样,那么你的崩溃实际上是堆栈本身被损坏。如果回链中的返回地址被覆盖,调试器自然不能解决它们。

有时你也会发现实际发出minidump的线程并不是抛出导致崩溃的异常的线程。在Threads窗口中查看是否有其他线程中存在有问题的代码。

如果您正在调试“发布”版本 - 也就是说,编译时打开了所有优化标记 - 您将不得不忍受调试器无法找到局部变量和其他一些事实数据。这是因为启用优化意味着允许编译器将数据保存在寄存器中,崩溃计算,并且通常会做各种事情,以防止数据实际上被写入堆栈。如果这是您的问题,那么您需要打开反汇编窗口并手动追踪数据,或者重新构建调试二进制文件,然后重现问题,以便查看它。

+0

0x8732ae00是不太可能的地址,它在内核空间(使用x86-32的2GB设置)。 0x7_______地址更常见,因为Windows DLL拥抱2GB边界。这减少了所需的重定位数量。如果您没有看到它们的符号,请使用_Microsoft_ Symbol Server。 – MSalters 2009-10-12 08:46:22

+1

我只是随机抽取地址(在这种情况下,这是特定机顶盒控制台喜欢重新定位用户模式DLL的地方)。 – Crashworks 2009-10-12 08:52:07

0

我不使用小型转储,而是倾倒德栈以“手”为日志文件 (见www.ddj.com/cpp/185300443How to Log Stack Frames with Windows x64)。

我遇到了类似你的行为:有时候有一个有效的调用栈,有时候没有。在少数情况下,堆栈可能会被损坏。在大概1/3的情况下,安装的异常处理程序根本不会被调用!我猜想它以某种方式是Windows结构异常处理的问题。

4

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

+0

这是一个好主意。但是,使用PDB和原始DLL,MSVC的调试器可以用FPO来处理堆栈帧,但它的工作当然会变得更加困难。我知道这一点,因为我们使用FPO进行编译,并且始终从小型转储中获取堆栈。 – Crashworks 2009-10-12 09:00:43

+0

如果“崩溃”是由于手动INT 3断点造成的,那当然很容易。问题是,大多数崩溃不会发生在完全错误的指令上。 CPU绊倒了一段时间,直到触发故障。与此同时,执行的代码不能按预期工作,并且可能会损坏程序状态。这可能包括执行你不打算执行的指令(尤其是nastiness:通过被误解的vtable间接跳转)。 vtablecourse – MSalters 2009-10-12 09:47:23

+1

是的,没有框架指针肯定会让人们更难以通过手工堆叠钓鱼。即使程序通过跳过一个疯狂的vfunc指针而死亡,通常你可以找出它来自哪里,因为CALL操作系统会将IP压入堆栈,但找到它然后找出所有当地人已经去过的地方可能会变成一项艰巨的练习一次倒退一次。如果你发现自己在这条小溪,windbg有一个有用的'dps'命令,它搜索内存中可能已知的符号和函数地址;这可以帮助你寻找旧的EIP。 – Crashworks 2009-10-12 10:43:49

19

为了提高在转储中找到的调用堆栈的准确性,您可以做的一件事是使用Visual Studio以外的调试器 - 具体来说,使用WinDbg或其他使用dbgeng中的“Windows调试器”调试引擎的工具。 (而不是Visual Studio使用的“Visual Studio Debugger”调试引擎)。

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

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

一个凡人指南要提取良好调用堆栈

这些都是从“最快/最简单的”到“最慢/最神秘的解释”命令。

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

    这是一个鲜为人知的工具,它可以自动很多常见问题的分析,这是很简单的给非程序员甚至客户。它快速且几乎万无一失,并且已经成为我快速分析传入崩溃转储的“转到”工具。

    • 推出的“DebugDiag资料分析”应用
    • 选择主页上的“CrashHangAnalysis”复选框主页
    • 拖动和删除您转储到“数据文件”窗格点击“开始分析”


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

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

    注意:出于安全原因,Chrome浏览器不会下载或打开.mhtml文件,因此您必须在Internet Explorer或Microsoft Edge中打开以使其可用。这是烦人,我已经提交给DebugDiag资料球队([email protected])的请求的格式更改为纯HTML

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

    • 如果尚未安装Visual Studio,请安装它。这需要在下一步之前完成。
    • 安装Windows Driver Kit (WDK)
    • 启动Visual Studio和(这一部分是非常重要的!)使用新的“文件 - >打开 - >崩溃转储...”选项打开转储。这将使用Windows调试器调试故障转储(,如果您将该转储拖放到Visual Studio或使用标准的“文件 - >打开 - >文件...”选项来打开转储,它将调试它使用旧的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中的崩溃转储
    • 在调用堆栈窗口中右键单击并选择“转到拆卸”
    • 将WinDbg输出callstack首行的十六进制地址粘贴到反汇编窗口的“地址”栏中,然后按Enter键。您现在处于崩溃位置,查看反汇编的代码。
    • 右键单击反汇编窗口并选择“转到源代码”以转到该位置的源代码。现在您正在查看崩溃网站的源代码。

注:上述所有要求具有配置正确的符号服务器的路径,否则你将不能够解决在调用堆栈的符号。我建议设置_NT_SYMBOL_PATH environment variable,以便它可以自动提供给Visual Studio,WinDbg和DebugDiag。

+0

另一个注意事项:为了在发布模式崩溃转储中获得更多令人愉快的调试体验,还有一个特殊的编译器标志,它向PDB注入附加信息,允许您正确地遍历优化代码并在调用中看到内联函数堆栈(和剖析器痕迹!)。这在VS2010中作为无证标记“/ d2Zi +”提供,然后在VS2013 Update 3中更改为官方标记“/ Zo”。有关更多信息,请参阅 http://randomascii.wordpress.com/2013/09/11/调试优化-codenew-在视觉工作室-2012 / – 2016-08-12 12:31:39

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 Community Edition非常强大且免费,所以你不妨尝试一下吧。青梅实验:

!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 

如果升级到2015年VS仍然有问题,那么很可能是堆栈行走故障是特定于您所看到的崩溃。如果缓冲区溢出在崩溃之前使堆栈发生敲打,那么调用堆栈将被不可挽回地损坏。你的问题对你提供明确诊断失败的信息太少。我发现这两个调试器的堆栈显示都相当可靠,但我通常也会明白为什么它们有时会失败,当发生这种情况时,我仍然可以提取我需要的信息。