Windows 调试符号详解

本文详细介绍 Windows 系统中调试符号的概念、分类、生成和使用方法。

简介

调试符号是用来将二进制汇编信息还原成可读信息的辅助数据,我们使用常见调试工具查看符号信息。

使用x64dbg查看调试符号

如图所示,在 x64dbg 中,我们可以在最右侧栏中看到代码位置、变量值、函数名等符号信息。

Windows 下,符号信息中可能包含但不限于类型、函数、行号、调用约定等信息,根据信息量的不同,可以分为私有符号、公开符号和导出符号。

分类

私有符号

私有符号是包含信息最完整的调试符号,通常会包含类型名/函数名/行号等信息,在有完整内存数据的前提下,我们可以在调试器中看到函数名,参数类型/值,变量类型和值等。

公开符号

由于私有符号包含了太多源码相关信息,因此如果对外公开,会导致软件极易被逆向。在部分场景下(如作为SDK的提供方),我们可能希望提供一些符号信息辅助用户开发,这时就可以使用公开符号。公开符号通常仅包含函数名和外部全局变量信息,调试者可以看到完整的调用堆栈函数名。Win32 相关动态库符号都是以这种方式提供的。

导出符号

严格来说,导出符号并不属于调试符号。导出符号是动态库的接口列表,本意是在运行时用来定位调用点的。但由于其也是符号名和地址的映射表,因此刚好也可以用来将地址转换为符号名。但这里的问题是由于不是所有符号都在导出符号表中,因此使用导出符号进行解析的调用栈往往会不太准确。调试器会在导出符号表中寻找和目标地址最接近的符号名。

使用 Dependencies 工具,我们可以看到一个 dllexe 的导出符号表。
导出符号表

仅有导出符号表时调试器解析函数地址时会使用与函数地址最接近的一个导出符号来显示函数调用栈,导致可能对调试产生误导,此时函数名后的偏移量往往巨大。
错误的函数调用栈

在Windbg中,我们可以通过lmi命令来查看各个模块当前加载的符号类型
模块符号类型

  • private pdb symbbos: 私有符号
  • pdb symbols: 公开符号
  • export symbols: 导出符号

生成

私有/公有符号主要有两种格式,一种是较为古老的CodeView格式,符号数据和二进制数据是在一个文件内存储的。另一种是目前最常用的PDB(程序数据库)格式,符号数据单独保存在pdb文件中。

MSVC 编译器默认只在 Debug 下生成符号信息,Release 不会生成,关键原因是 Release 编译和链接时缺少了一些参数。

格式 编译参数 链接参数
CodeView /Z7 /DEBUG
PDB /Zi /DEBUG

如果使用 CMake 等构建工具,更为简单的方式是使用 RelWithDebInfo 构建类型,并通过以下 CMake 变量控制符号格式(需 CMake 3.25 以上版本且将 CMP0141 规则打开)。

CMAKE_MSVC_DEBUG_INFORMATION_FORMAT

静态库的符号信息会在链接时合入可执行文件的符号信息中,因此非常建议静态库符号信息使用CodeView格式。而对于可执行文件,为了减少文件体积,避免私有信息泄露,更推荐使用PDB格式。

PDB符号文件中会带有一个唯一的ID(通常根据编译时间等信息生成),用于后续使用时进行匹配。因此即使是完全相同的源码,两次构建产生的符号默认是无法混用的。我们可以通过ChkMatch工具来检查pdb和dll或exe是否匹配。

检查PDB和DLL是否匹配

对于导出符号,我们可以在函数或类前添加__declspec(dllexport)进行导出,CMake也提供了一个CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS用于将所有符号均设为导出。但这样一方面会导致二进制文件增大,链接速度变慢,也会导致内部信息泄露,还会导致不同模块间出现符号冲突,因此尽量不要使用该方式。

使用

为了避免调试时需要从大量 PDB 文件中手动寻找到对应的文件,微软提供了名为符号服务器的机制用于管理符号文件。符号服务器实际上就是一个 HTTP 文件服务器,用户需要事先将编译生成的二进制文件和符号文件上传,并按照 <文件名>/<ID>/文件 的目录格式保存。当使用基于 dbghelp 的调试器时,可以通过设置符号服务器地址来自动下载对应符号。例如,我们可以使用 Artifactory 作为存储服务,在构建时将符号上传,供后续调试使用,具体可参考相关文章1。

artifactory保存符号文件

对于 WinDbg/VS 等,除了在软件内部设置中设置符号服务器路径,我们可以直接设置环境变量 _NT_SYMBOL_PATH

_NT_SYMBOL_PATH

当使用Windbg无法加载预期可以加载的符号时,可以通过以下命令来打开符号加载详细信息:

1
!sym noisy

备注

工具

  1. x64dbg - 一个强大的开源逆向调试器,在分析没有源码的程序时比windbg更易用
  2. Dependencies - 现代化的PE文件依赖关系查看器,用于查看导出符号表,性能远超传统的Dependency Walker
  3. ChkMatch - 用于检查PDB文件与二进制文件是否匹配的工具
  4. PDBRipper - 一个专业的PDB文件查看和分析工具

相关文章

  1. 在本机搭建调试符号服务器