文章的这一部分使用了一个例子,在 Windows 2000 系统下手工创建了 Notepad 的一个 minidump,然后在 Windows XP 系统下调试。
启动 Visual Studio .NET,单击“文件”菜单上的“打开解决方案”,在“文件类型”下拉列表框中选择“转储文件(*.dmp; *.mdmp)”(555~~~为什么我在 Visual Studio .NET 2003 中找不到这一项……不过还好,可以直接双击 .dmp 文件打开——译者注),找到 minidump 文件,然后点击“打开”创建一个缺省工程。
按 F5 在调试器中启动这个 dump,这一步将为你开始调试提供一些信息。调试器将创建一个假的进程;在输出窗口中显示了很多模块加载的消息。此时调试器仅仅是在重建崩溃时的进程状态。在显示了一条 EXE 不包含调试信息的警告消息之后,调试器停在了用户崩溃的地方,诸如一个非法访问什么的。这时候如果你查看调用栈窗口,你会发现缺少符号和很多有用的信息。
图 1:起初没有符号文件的堆栈窗口 |
 |
为了读取一个 minidump,你通常需要相关的二进制拷贝。为了找到正确的二进制文件,打开模块窗口。
图 2:起初没有二进制文件的模块窗口 |
 |
图 2 展示了 Notepad 的例子,并且说明了两个情况。首先,二进制所在的路径名前标上了一个星号,这表示这些是在用户机器上的模块路径,但是在本地却找不到对应的二进制文件。其次,在“Information”一列中全都写着“No matching binary found”。找到对应的二进制文件的关键是注意“Version”字段和文件名。在这个例子中,大多数系统文件的版本号都是 2195,也就是 Windows 2000。虽然无法从这些信息上立即得知确切的 service pack (SP) 或者 quality fix engineering (QFE),但这些信息可以从微软的 DLL 帮助数据库中查询到:http://support.microsoft.com/servicedesks/fileversion/dllinfo.asp。
现在,你需要找到一张 Windows 操作系统的 CD 或者已经安装了正确版本的机器,然后将所需要的文件复制到一个目录。通常情况下没有必要把进程中每个模块的二进制文件都找出来,但是找出那些在每个调用栈上的关键模块是很重要的。这通常包括操作系统的二进制文件(例如 Kernel32.dll)和你自己的二进制模块(在这个例子中就是 Notepad.exe)。
在你找到这些二进制文件、并把它们拷贝到一个本地目录之后,单击“调试”菜单中的“停止调试”命令。然后在解决方案资源管理器中,右键单击工程图标,在快捷菜单上单击“属性”,你将看到“调试”属性页。在“命令参数”中填入“MODPATH”,跟上一个等号,然后输入二进制文件所在的位置,如果有多个位置,可以用分号分隔。在这个例子中,它是:
MODPATH=m:sysbits
在设置好了路径之后,按 F5 重新装入 minidump,MODPATH 的值将通过命令行参数传递给调试器;在 Visual Studio .NET 的后续版本中应该会有更方便的方法设置这个参数,或许可以作为一个选项出现在属性对话框中。
尽管找到二进制文件并不太可能改善调用栈窗口的情况,但是它却能解决模块窗口中的问题,如图 3 所示:
图 3:找到二进制文件后的模块窗口 |
 |
它现在显示的不再是“No matching binary found”,而是“Cannot find or open a required DBG file”(我怎么觉得这个地方的英文语法应该用“nor”而不是“or”……呵呵,不管它了,反正微软程序中无伤大雅的语法错误已经不是第一次被发现了——译者注)和“No symbols loaded”。前一条消息出现在那些使用 DBG 文件存储调试信息的系统 DLL 上,后一条消息则出现在使用 PDB 文件的 DLL 上。找到对应的二进制文件并不能使你看到调用栈;你还需要找到它们对应的调试信息。
方法 A:坎坷之路
为了完整地分析一个 minidump,你需要找到所有的调试信息。但为了节省时间,你可以只找那些你需要的信息。本例中的调用栈列表包含了 User32.dll 和 Kernel32.dll,所以需要它们的调试信息。
对应的调试信息 |
操作系统 | 所需的文件 |
Windows NT 4 |
DBGs |
Windows 2000 |
DBGs, PDBs |
Windows XP |
PDBs |
一个找系统符号的好地方在 http://www.microsoft.com/ddk/debugging,你也可以在 Windows NT Server 和 Windows 2000 Server 操作系统的 Support CD 上找到系统符号。在本例中,它们被拷贝到了二进制代码所在的位置。实际情况中你可能会遇到非微软发布的二进制模块,这时候你就需要它们的 PDB 文件了。同样在本例中,Notepad 的 DBG 和 PDB 文件也被拷贝了出来,因为它是我们使用的样本应用程序。
在单击“调试”菜单上的“停止调试”命令后再按 F5 就会看到调用栈列表,如图 4 所示。你也许发现了,由于添加了新的二进制文件和调试信息,调用栈发生了变化。这就是我们要的结果;只有在具有调试信息的情况下才能准确地回溯一个调用栈,提供的信息越详细,你得到的堆栈就越精确,通常能够把那些原来没有显示出来的栈帧信息暴露出来。
在本例中并没有崩溃。在实际情况中,这些信息应该已经能够帮助你查找出大概 70% 的崩溃原因。另外,本例中的调用栈列表是使用微软随系统组件提供的、经过删减的符号文件所生成的,所以没有行号信息。如果你使用自己生成的、完整的 PDB 文件,你可以看到一个更详尽的调用栈。
图 4:找到符号和二进制文件后的调用栈窗口 |
 |
符号服务器
如果你需要处理大量 minidumps,进行大范围调试,那么存储和访问所有的二进制文件以及 PDB/DBG 文件将变得很困难。Windows NT 中包含了一种叫做符号服务器的技术,起初只是用来存储符号文件的,后来扩展到也支持二进制文件的查找。Windows NT 调试器是第一个支持它的工具,但实际上 Visual Studio .NET 也支持它,虽然没有文档提及(事实上,在 Visual Studio .NET 2003 的文档中和 MSDN 的 Knowledge Base (KB) 中都有讲到如何使用符号服务器,KB 中的 Q319037 和 Q311503 分别讲述了如何在 Visual Studio .NET 2002 和 Visual Studio 6.0 中使用符号服务器,甚至还包括了一个详细无比的、傻瓜式的教学视频。在 MSDN 中搜索关键字“symsrv.dll”可以找到这部分内容——译者注)。关于符号服务器,可以参考 http://www.microsoft.com/ddk/debugging/symbols.asp。
|
方法 B:康庄大道——使用符号服务器
首先,去 http://www.microsoft.com/ddk/debugging 下载调试工具。你需要安装 Symsrv.dll 文件,你可以将它拷贝到 devenv.exe 所在的目录下,或者放到你的 System32 目录下,以便 Visual Studio .NET 可以访问到它。在复制了 Symsrv.dll 文件之后,你就可以安全地卸载调试工具了。你还需要创建一个本地目录,在本例中,创建了一个本地目录 C:localstore。
在工程属性对话框中的“调试”属性页上,填写“符号路径”:
SRV*c:/localstore*http://msdl.microsoft.com/download/symbols
这个字符串会告诉调试器使用符号服务器来获取符号文件,并在本地创建一个符号服务器,用来存放符号文件。现在,当你在 minidump 工程中按下 F5 后,符号文件将从微软的网站上拷贝到本地服务器。在第一次下载后,之后的加载速度就会快很多,因为符号文件将从本地服务器中直接加载,不再需要通过 Web 下载了。
在调试微软之外的程序时,你应该将方法 A 和方法 B 结合起来。用方法 A 获得系统组件的符号文件(反了吧?好像应该用方法 B 吧……不过原文确实是“Use A to get the system components”——译者注),然后附上你自己的符号文件路径,用分号将它们隔开,例如:
c:dropuildmyapp;SRV*c:localstore*http://msdl.microsoft.com/download/symbols
由于符号服务器是 Visual Studio .NET 中的一个没有文档说明的特性,所以没有错误报告。如果表达式错误或者 Symsrv.dll 文件的位置不正确,符号文件就不能被加载,只能在模块窗口中显示“No symbols loaded”的错误信息。你也可以使用符号服务器存储和下载二进制文件,但是 MODPATH 表达式需要使用“symsrv*symsrv.dll*”而不是“SRV*”(MSDN 中对于“srv”的解释是:“This is shorthand for symsrv*symsrv.dll.”——译者注)。
注意:微软的符号服务器不包含二进制文件,但是你自己创建的符号服务器却可以。
符号服务器不仅仅是用来调试 minidumps 的,它提供了一种“在线”调试的方法。在使用符号服务器之前,别忘了正确地配置“调试”属性页上的“符号路径”选项。
|