使用符号进行调试

本文提供了如何在调试过程中更好地使用符号的高级概述。 本文介绍如何使用 Microsoft 符号服务器,以及如何设置和使用你自己的专用符号服务器。 这些最佳做法有助于提高你调试问题的效率和能力,即使与问题相关的所有符号和可执行文件都不在你的计算机上也是如此。

符号

许多不同类型的符号都可用于调试。 它们包括 CodeView 符号、COFF、DBG、SYM、PDB,甚至包括从二进制文件导出表生成的导出符号。 本白皮书只讨论了 VS.NET 和 PDB 格式符号,因为它们是最新的首选格式。 默认情况下,它们是为使用 Visual Studio 编译的项目生成的。

为发布可执行文件生成 PDB 文件不会影响任何优化,也不会影响已生成文件的大小。 通常,唯一区别在于路径,PDB 文件的文件名嵌入在可执行文件中。 出于此原因,应始终生成 PDB 文件,即使你不想将它们与可执行文件一起发送。

如果使用 /Zi/ZI(生成 PDB 信息)编译器开关以及 /DEBUG(生成调试信息)链接器开关生成项目,则会生成 PDB 文件。 编译器生成的 PDB 文件将合并写入到单个 PDB 文件,该文件与可执行文件位于同一目录中。

默认情况下,PDB 文件包含以下信息:

  • 公共符号(通常是所有函数、静态变量和全局变量)
  • 负责可执行文件中代码段的对象文件列表
  • 帧指针优化信息 (FPO)
  • 局部变量和数据结构的名称和类型信息
  • 源文件和行号信息

如果你担心有人使用 PDB 文件信息来帮助对可执行文件进行逆向工程,则也可以使用 /PDBSTRIPPED:filename 链接器选项生成剥离的 PDB 文件。 如果你有要从中剥离私人信息的现有 PDB 文件,则可以使用名为 pdbcopy 的工具,它是 Windows 调试工具的一部分。

默认情况下,剥离的 PDB 文件包含以下信息:

  • 公共符号(通常只有非静态函数和全局变量)
  • 负责可执行文件中代码段的对象文件列表
  • 帧指针优化信息 (FPO)

这是允许进行可靠调试所需的最少信息。 最少的信息也让人们难以获得有关原始源代码的任何其他信息。 由于生成了剥离的 PDB 文件和常规 PDB 文件,因此你可以为可能需要有限调试能力但希望保持完整 PDB 机密的用户提供剥离版本。 请注意,/PDBSTRIPPED 会生成第二个较小的 PDB 文件,因此在生成要广泛分发的版本时,请确保使用正确的 PDB。 对于一个典型项目,常规 PDB 的大小可能为几兆字节,但 PDB 的剥离版本可能只有几百 KB。

使用符号进行调试

调试崩溃的应用程序时,调试程序会尝试显示导致崩溃的堆栈上的函数。 如果没有 PDB 文件,则调试程序将无法解析函数名称、参数或存储在堆栈上的任何局部变量。 如果调试 32 位可执行文件,则有时你甚至无法在没有符号的情况下获得可靠的堆栈跟踪。 有时,可以查看堆栈上的原始值,并找出哪些值可能是返回地址,但这些值很容易与函数引用或数据混淆。

如果当前堆栈上的函数是使用省略帧指针 (/Oy) 优化编译的,并且符号不存在,则调试程序无法可靠地确定调用当前函数的函数。 这是因为,如果没有 PDB 所包含的帧指针优化 (FPO) 信息,则调试程序就无法依靠帧指针寄存器 (EBP) 来指向保存的前一帧指针和父函数的返回地址。 相反,它会进行猜测。 有时,它会获得正确结果。 然而,它通常会获得错误结果,这可能会误导。 如果你看到有关缺少符号或未加载符号的警告(如以下示例所示),请从那时起不要信任该堆栈。

SWPerfTest.exe!TextFunction(... ...)    Line 59    C++
d3dx9d.dll!008829b5()
[Frames below may be incorrect and/or missing, no symbols loaded for d3dx9d.dll]
SWPerfTest.exe!main(int argc=, const char * * argv=)  Line 328 + 0x12 bytes     C++
SWPerfTest.exe!__mainCRTStartup() Line 716 + 0x17 bytes    C
kernel32.dll!@BaseThreadInitThunk@12() + 0x12 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x27 bytes

在许多情况下,可以在没有符号的情况下继续调试,因为问题出现在具有准确符号的位置,你无需再往下查看调用堆栈中的函数。 即使调用堆栈中的库没有可用的 PDB,只要使用帧指针进行编译,则调试程序也应该能够正确猜测父函数。 从 Windows XP Service Pack 2 开始,所有 Windows DLL 和可执行文件都是在禁用 FPO 的情况下编译的,因为这会使调试更加准确。 禁用 FPO 还允许采样探查器在运行时遍历堆栈,从而将性能影响降至最低。 在 Windows XP SP2 之前的 Windows 版本上,所有操作系统二进制文件都需要包含 FPO 信息的匹配符号文件,以便进行准确的调试和分析。

如果调试 64 位本机可执行文件,则不需要符号文件来生成有效的堆栈跟踪,因为 x64 操作系统和编译器设计为不需要它们。 但是,仍需要符号文件来检索函数名称、调用参数和局部变量。

但是,在某些情况下,如果没有符号,则特别难以调试。 例如,如果调试为其生成了 PDB 文件的程序,并且在 DLL 中没有符号的函数回调中出现崩溃,则无法查看导致回调的函数,因为你将无法解码堆栈。 这种情况经常发生在第三方库中(如果未提供 PDB),或者发生在旧操作系统组件中(如果 PDB 不可用)。 回调通常在消息传递、枚举、内存分配或异常处理期间发生。 在没有准确堆栈的情况下调试这些函数可能会令人沮丧。

为了可靠地调试在不同计算机上生成或在不归你所有的代码中崩溃的小型转储,你必须能够访问该小型转储中引用的可执行文件的所有符号和二进制文件。 如果符号和二进制文件可从符号服务器获得,则调试程序会自动获取它们。 有关小型转储的详细信息,请参阅故障转储分析白皮书。

获取所需的符号

Visual Studio 和其他 Microsoft 调试程序(如 WinDbg)通常设置为仅在你生成应用程序并在自己的计算机上调试它时才能正常工作。 如果需要为其他人提供可执行文件,或者计算机上有多个版本的 DLL 或 .exe 文件,或者你希望准确调试使用 Windows 或其他库(如 DirectX)的应用程序,则需要了解调试程序是如何查找和加载符号的。 调试程序使用用户指定的符号搜索路径(可在 Visual Studio 中的 Options\Debugging\Symbols 下找到)或 _NT_SYMBOL_PATH 环境变量。 通常,调试程序会在以下位置搜索匹配的 PDB:

  • 在 DLL 或可执行文件中指定的位置。

    如果你在计算机上生成了 DLL 或可执行文件,则默认情况下,链接器会将关联 PDB 文件的完整路径和文件名放在 DLL 或可执行文件中。 调试时,调试程序首先会检查符号文件是否存在于 DLL 或可执行文件中指定的位置。 这非常有用,因为计算机上编译的代码总是有可用的符号。

  • 可能与 DLL 或可执行文件位于同一文件夹中的 PDB。

  • 所有本地符号缓存文件夹。

  • 任何本地网络文件共享符号服务器。

  • 任何 Internet 符号服务器,例如 Microsoft 符号服务器。

为了确保拥有准确调试所需的所有 PDB,请安装适用于 Windows 的调试工具。 可以在适用于 Windows 的调试工具中找到 32 和 64 位版本。

随此包一起安装的有用工具是 symchk.exe。 它可以帮助识别缺失或不正确的符号。 此工具具有大量潜在的命令行选项。 下面是两个更为有用的常用选项。

检查同一文件夹中的给定 DLL 或 .exe 文件和 PDB 是否匹配

"c:\Program Files\Debugging Tools for Windows\symchk" testing.dll /s .

SYMCHK: FAILED files = 0
SYMCHK: PASSED + IGNORED files = 1

/s . 选项告知 symchk 仅查找当前文件夹中的符号,而不在任何符号服务器中查找。

检查一组文件夹中的所有 DLL 和可执行文件是否具有匹配的 PDB

"c:\Program Files\Debugging Tools for Windows\symchk" *.* /r

/r 选项将 symchk 设置为以递归方式遍历文件夹,以检查所有可执行文件是否都具有匹配的 PDB。 如果没有 /s 选项,则 symchk 将使用当前 _NT_SYMBOL_PATH 搜索任何专用或本地服务器上的符号,或者在 Microsoft 符号服务器上搜索符号。 symchk 工具只搜索可执行文件(.exe、.dll 等)的符号。 不能使用通配符搜索非可执行文件的符号。

symchk 的工作原理

当链接器生成 .dll、可执行文件和 PDB 文件时,它会在每个文件中存储相同的 GUID。 该工具使用 GUID 来确定给定的 PDB 文件是否与 DLL 或可执行文件匹配。 如果更改 DLL 或可执行文件(使用资源编辑器或复制保护编码或更改其版本信息),则 GUID 会更新,并且调试程序无法加载 PDB 文件。 因此,请务必避免在链接器创建 DLL 或可执行文件后对其进行操作。

你还可以使用 VS.NET 附带的 DUMPBIN 实用工具来显示搜索的符号路径,并查看是否找到与给定 DLL 或可执行文件匹配的符号文件。 例如:

DUMPBIN /PDBPATH:VERBOSE filename.exe

符号服务器

符号服务器是多个版本的可执行文件和符号文件的存储库。 它包含符号文件本身,或指向关联符号文件的指针。 调试程序知道如何使用符号服务器,并可以使用它们来搜索缺失或未知的符号。

DLL 和可执行文件也可从 Microsoft 符号服务器获得。 这样就可以调试崩溃并检查计算机上可能不存在的操作系统文件的代码。 如果调试程序遇到用于调试的系统上不存在的可执行文件或 DLL,则它会自动从 Microsoft 符号服务器请求符号和二进制文件的副本。 如果要调试具有许多版本的组件(例如 msvcrt.dll),并且需要检查计算机上不存在的版本的代码,则这非常有用。 这也有助于调试在与用于调试的系统不同的操作系统上生成的小型转储。

Microsoft 在其外部可访问的符号服务器上发布所有操作系统和其他重新分发组件(如 DirectX SDK)的所有 PDB 文件。 这让用户能够轻松调试使用这些 DLL 或可执行文件的应用程序。 可以使用 Microsoft 符号服务器解析符号,以及计算机上生成的组件的任何本地符号。

你可以将计算机设置为使用 Microsoft 符号服务器,后者允许你访问所有 Microsoft 符号文件。 你还可以为公司、团队或网络设置专用符号服务器,该服务器可用于存储你正在处理的项目的多个旧版本,或者为从 Microsoft 符号服务器使用的符号提供本地缓存。

若要使用符号服务器,请在名为 _NT_SYMBOL_PATH 的环境变量中指定搜索路径。 调试程序和新式工具(如 WinDbg、NTSD 或 Visual Studio)会自动使用此路径来搜索符号。

当调试程序搜索符号时,它首先在本地进行搜索。 然后,它会在符号服务器上查找。 找到匹配的符号后,它会将符号文件传输到本地缓存。 典型 DLL 或可执行文件的符号大小从 1 MB 到 100 MB 不等。 因此,如果要调试包含许多 DLL 的进程,则解析所有符号并将其传输到本地缓存可能需要一些时间。

使用 Microsoft 符号服务器

Microsoft 符号服务器允许获取所有最新的符号,包括已修补或更新文件的符号。 可从 https://msdl.microsoft.com/download/symbols 获取 Microsoft 符号服务器.

可通过以下方式之一访问符号服务器:

  • 直接输入服务器地址。 在 Visual Studio 中,从“工具”菜单中,选择“选项”,然后选择“调试”,再选择“符号”。

  • 使用环境变量 _NT_SYMBOL_PATH。 我们建议使用此方法。

    它可供所有调试工具使用。 它也可供 Visual Studio 使用,并在 Visual Studio 打开时进行读取和解码。 因此,如果已对其进行更改,则需要重启 Visual Studio。

    此环境变量允许指定多个符号服务器,例如,内部专用符号服务器。 它还允许你指定本地缓存目录,以存储从符号服务器(内部和通过 Internet)查找的所有符号的 PDB。

_NT_SYMBOL_PATH 变量的语法为:

srv*[local cache]*[private symbol server]*https://msdl.microsoft.com/download/symbols

将 [local cache] 替换为计算机上要存储所用符号缓存的目录的名称,例如 %SYSTEMROOT%\Symbols 或 c:\symbols。

[private symbol server] 是可选的。 它可以指向位于网络上的符号服务器,也可以指向团队、产品组或公司共享的符号服务器。

若要仅将 Microsoft 符号服务器与符号的本地缓存一起使用,以加快通过 Internet 进行访问的速度,请使用以下 _NT_symbol_PATH 设置:

srv*c:\symbols*https://msdl.microsoft.com/download/symbols

可以在随适用于 Windows 的 Microsoft 调试工具包一起安装的帮助文件中找到 _NT_SYMBOL_PATH 的其他选项。

如果使用符号服务器,则不带符号的可执行文件可能会增加启动调试程序所需的时间。 这是因为调试程序每次尝试加载可执行文件时都会查询符号服务器。 因此,最好始终为所有组件请求符号。

可能无法为每个组件请求符号 — 例如,视频驱动器在进程空间中可能有 DLL,并且所需的 PDB 文件在 Microsoft 符号服务器上可用。 在这种情况下,启动调试会话时会有一个小延迟。

为了避免此小延迟,可以运行调试程序一次,以从 Microsoft 符号服务器本地缓存所有符号。 然后,修改 _NT_SYMBOL_PATH 以删除 Microsoft 符号服务器。 除非可执行文件发生更改,否则检查不包含符号的可执行文件将不需要通过 Internet 进行查询,因为你具有来自 Microsoft 符号服务器的所有符号的本地缓存副本。

手动获取符号

如果已正确设置调试程序,则它会自动从本地缓存或符号服务器加载所需的任何符号。 如果只想获取单个可执行文件或可执行文件文件夹的符号,则可以使用 symchk。 例如,如果要将 Windows 系统文件夹中 d3dx9_30.dll 文件的符号下载到当前目录中,可以使用以下命令:

"c:\Program Files\Debugging Tools for Windows\symchk" c:\Windows\System32\d3dx9_30.dll /oc \.

symchk 工具具有许多其他用途。 有关详细信息,请参阅 symchk /?,或查看适用于 Windows 的 Microsoft 调试工具文档。

设置符号服务器

设置符号服务器非常简单。 它非常有用,理由如下:

  • 可节省带宽,或加快公司、团队或产品的符号解析速度。 网络上本地文件共享上的内部符号服务器会缓存对外部符号服务器(如 Microsoft 符号服务器)的任何引用。 许多人可以同时快速访问本地或内部符号服务器。 因此,它可节省重复符号请求可能产生的带宽和延迟。
  • 可存储应用程序的旧版本、版本或外部版本的符号。 通过在易于访问的符号服务器上存储这些版本的符号,可以在具有调试程序并连接到本地符号服务器的任何计算机上调试这些版本中的崩溃和问题。 如果调试并非由你自己生成的可执行文件生成的小型转储(即由另一个程序员或生成计算机生成的版本),则它特别有用。 如果这些版本的符号存储在符号服务器上,则可进行可靠且准确的调试。
  • 可使符号保持最新状态。 更新组件(例如通过 Windows 更新或 DirectX SDK 修改的 OS 组件)时,仍可使用所有最新符号进行调试。

在你自己的本地网络上设置符号服务器就像在服务器上创建文件共享并授予用户访问共享、创建文件和文件夹的完整权限一样简单。 应在服务器操作系统(如 Windows Server 2003)上创建此共享,以便可以同时访问该共享的人数不受限制。

例如,如果在 \\mainserver\symbols 上设置了文件共享,则团队成员可将 _NT_SYMBOL_PATH 设置为以下内容:

Srv*c:\symbols*\\mainserver\symbols*https://msdl.microsoft.com/download/symbols

检索符号时,文件和文件夹将显示在 \\mainserver\symbols 共享目录中,以及 c:\symbols 目录中的各个缓存中。

这通常涉及设置和使用你自己的符号服务器或 Microsoft 符号服务器。

将符号添加到符号服务器

若要在符号服务器共享上添加、删除或编辑文件,请使用 symstore.exe 工具。 此工具是适用于 Windows 的 Microsoft 调试工具包的一部分。 适用于 Windows 的调试工具包中包含有关符号服务器、symstore 工具和索引符号的完整文档。

作为生成过程的一部分,你可能希望将符号直接添加到自己的符号服务器,或者让整个团队可在第三方库或工具中使用这些符号。 将符号添加到符号服务器文件共享的过程称为索引符号。 索引符号有两种常见方法。 可以将符号文件复制到符号服务器。 或者,可以将指向符号位置的指针复制到符号服务器。 如果你有包含旧版本的存档文件夹,则可能需要为已位于共享上的 PDB 文件的指针编制索引,而不是复制符号。 由于符号大小有时可能为几十兆字节,因此最好提前规划可能需要多少空间来存档整个开发过程中项目的所有版本。 如果仅为指向符号的指针编制索引,则删除旧版本或更改文件共享的名称时可能会遇到问题。

例如,若要以递归方式将从 2006 年 10 月 DirectX SDK 中获取的 c:\dxsym\Extras\Symbols 中的所有符号索引到名为 \\mainserver\symbols 的符号服务器文件共享,可以使用以下命令:

"c:\Program Files\Debugging Tools for Windows\symstore" add /f "C:\dxsym\Extras\Symbols\*.pdb"
/s \\mainserver\symbols /t "October 2006 DirectX SDK " /r

/t "comment" 参数用于向添加符号的事务添加说明。 在对符号执行管理任务时,这非常有用。

最佳方案

  • 为团队、公司或产品设置自己的符号服务器文件共享。
  • 将 _NT_SYMBOL_PATH 设置为指向本地缓存、专用符号服务器和 Microsoft 符号服务器。
  • 如果调试程序无法为正在调试的组件加载符号,请联系组件的所有者以请求符号 — 至少是一个剥离的 PDB。
  • 对于生成的每个版本,设置一个自动化生成系统,以便为专用符号服务器上的符号编制索引。 确保你分发的版本是此过程生成的版本。 这可确保符号始终可用于调试问题。
  • 设置符号服务器,以允许调试程序直接从基于 Visual Source Safe 或 Perforce 的源代码管理系统访问特定模块的源代码。 如果已对游戏发布版本的源文件信息和符号编制索引,则有权访问符号服务器的开发人员可以对报告的问题进行完整的源代码级别调试,而无需在其开发计算机上保留生成环境或旧版本的源文件。 若要设置符号服务器以允许对源文件信息编制索引,请参阅源服务器文档。