公共和专用符号
当链接器生成全尺寸的 .pdb 或 .dbg 符号文件时,它包含两个不同的信息集合:私有符号数据和公共符号表。 这些集合在包含的项列表和存储的关于每个项的信息方面有所不同。
私有符号数据包括以下项:
函数
全局变量
局部变量
有关用户定义的结构、类和数据类型的信息
源文件的名称和该文件中对应于每个二进制指令的行号
公共符号表包含的项较少:
函数 (,声明为 静态) 的函数除外
指定为 外部 (的全局变量,以及跨多个对象文件可见的任何其他全局变量)
作为一般规则,公共符号表只包含可从一个源文件到另一个源文件访问的项。 仅在一个对象文件中可见的项(例如 静态 函数、仅在单个源文件中的全局变量和局部变量)不包含在公共符号表中。
这两个数据集合在针对每个项包含的信息方面也有所不同。 对于 私有 符号数据中包含的每个项,通常包含以下信息:
项的名称
虚拟内存中项的地址
每个变量、结构和函数的数据类型
每个函数的参数类型和名称
每个局部变量的范围
与每个源文件中的每一行关联的符号
帧指针省略 (FPO) 用于访问堆栈的每个函数的记录
另一方面, 公共 符号表仅存储有关其中包含的每个项的以下信息:
项的名称。
项在其模块的虚拟内存空间中的地址。 对于函数,这是其入口点的地址。
框架指针省略 (FPO) 每个函数的记录。
可能包含称为修饰的符号前缀/后缀。
可以通过两种方式将公共符号数据视为私有符号数据的子集:它包含较短的项列表,并且还包含有关每个项的较少信息。 例如,公共符号数据根本不包括局部变量。
每个局部变量仅包含在专用符号数据中,其地址、数据类型和范围。 另一方面,函数同时包含在私有符号数据和公共符号表中,但尽管私有符号数据包括函数名称、地址、FPO 记录、输入参数名称和类型和输出类型,但公共符号表仅包括函数名称、地址和 FPO 记录。
私有符号数据和公共符号表之间还有一个区别。 公共符号表中的许多项的名称都 带有 前缀和/或后缀。 这些修饰由 C 编译器、C++ 编译器和 MASM 汇编程序添加。 典型的前缀包括一系列下划线或字符串 __imp_ (指定导入的函数) 。 典型的后缀包括一个或多个 at 符号 ( @ ) 后跟地址或其他标识字符串。 链接器使用这些修饰来消除符号的歧义,因为函数名称或全局变量名称可能在不同的模块之间重复。 这些修饰是公共符号表是私有符号数据的子集的一般规则的例外。
完整符号文件和去除符号文件
完整的符号文件同时包含私有符号数据和公共符号表。 此类文件有时称为 专用符号文件,但此名称具有误导性,因为此类文件同时包含私有符号和公共符号。
去除符号文件是仅包含公共符号表的较小文件,或者在某些情况下,仅包含公共符号表的子集。 此文件有时称为 公共符号文件。
创建完整和去除符号文件
如果使用 Visual Studio 生成二进制文件,则可以创建完整符号文件或带去符号文件。 有关构建剥离符号的信息,请参阅 /PDBSTRIPPED (Strip Private Symbols) 。
使用 BinPlace 工具,可以从完整的符号文件创建去除的符号文件。 当使用最常见的 BinPlace 选项 (-a -x -s -n) 时,去除的符号文件将放在 -s 开关后列出的目录中,而完整的符号文件将放在 -n 开关后列出的目录中。 当 BinPlace 去除符号文件时,会为该文件的去除版本和完整版本提供相同的签名和其他标识信息。 这允许使用任一版本进行调试。 有关 BinPlace 的详细信息,请参阅 BinPlace。
使用 PDBCopy 工具,可以通过删除私有符号数据,从完整的符号文件创建去除的符号文件。 PDBCopy 还可以删除公共符号表的指定子集。 有关详细信息,请参阅 PDBCopy。
使用 SymChk 工具,可以确定符号文件是否包含专用符号。 有关详细信息,请参阅 SymChk。
在调试器中查看公共符号和专用符号
可以使用 WinDbg、KD 或 CDB 查看符号。 当其中一个调试器有权访问完整的符号文件时,它同时具有私有符号数据中列出的信息和公共符号表中列出的信息。 公共符号数据包含符号修饰。
访问专用符号时,始终使用私有符号数据,因为这些符号不包含在公共符号表中。 这些符号永远不会修饰。
.symopt (Set Symbol Options) 命令可用于控制确定调试器如何使用公共符号和专用符号的符号选项。 例如,此命令打开符号调试信息。
.symopt+ 0x80000000
以下选项更改了在调试器中使用公共符号和专用符号的方式。
打开 “SYMOPT_UNDNAME ”选项时,显示公共符号的名称时不包括修饰。 此外,搜索符号时,会忽略修饰。 当此选项处于关闭状态时,显示公共符号时会显示修饰,并在搜索中使用修饰。 任何情况下都不会修饰私有符号。 默认情况下,此选项在所有调试器中都处于打开状态。
打开 “SYMOPT_PUBLICS_ONLY ”选项时,将忽略私有符号数据,并且仅使用公共符号表。 默认情况下,此选项在所有调试器中处于关闭状态。
打开 “SYMOPT_NO_PUBLICS ”选项时,将忽略公共符号表,搜索和符号信息仅使用专用符号数据。 默认情况下,此选项在所有调试器中处于关闭状态。
当 SYMOPT_AUTO_PUBLICS 选项处于 (并且SYMOPT_PUBLICS_ONLY和SYMOPT_NO_PUBLICS都关闭) 时,将在私有符号数据中执行第一个符号搜索。 如果找到所需的符号,则搜索将终止。 如果没有,则搜索公共符号表。 由于公共符号表在私有数据中包含符号的子集,因此这通常会导致忽略公共符号表。
当SYMOPT_PUBLICS_ONLY、SYMOPT_NO_PUBLICS和SYMOPT_AUTO_PUBLICS选项全部关闭时,每次需要符号时,都会搜索私有符号数据和公共符号表。 但是,当在这两个位置找到匹配项时,将使用私有符号数据中的匹配项。 因此,此实例中的行为与打开 SYMOPT_AUTO_PUBLICS 时的行为相同,只是使用 SYMOPT_AUTO_PUBLICS 可能会导致符号搜索的发生速度略快一些。
以下示例使用命令 x (检查符号) 三次。 第一次使用默认符号选项,因此信息取自私有符号数据。 请注意,这包括有关数组 typeString 的地址、大小和数据类型的信息。 接下来,使用命令 .symopt+ 4000,导致调试器忽略专用符号数据。 当 x 命令再次运行时,将使用公共符号表;这次没有 用于 typeString 的大小和数据类型信息。 最后,使用命令 .symopt-2,这会导致调试器包含修饰。 最后一次运行 x 命令时,将显示函数名称的修饰版本 (_typingString)。
0:000> x /t /d *!*typingstring*
00434420 char [128] TimeTest!typingString = char [128] ""
0:000> .symopt+ 4000
0:000> x /t /d *!*typingstring*
00434420 <NoType> TimeTest!typingString = <no type information>
0:000> .symopt- 2
0:000> x /t /d *!*typingstring*
00434420 <NoType> TimeTest!_typingString = <no type information>
使用 DBH 工具查看公共符号和专用符号
查看符号的另一种方法是使用 DBH 工具。 使用 /?
选项显示帮助选项。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64>dbh /?
dbh dbghelp shell
usage: dbh [-n] [-c] [-d] [-?] [-??] [-p] [targetmodule] [command]
[-n] display noisy symbol spew
[-d] use decorated publics
[-p:XXXX] attaches to process ID XXXX
[-s:SSSS] set symbol path to SSSS
[-c] callbacks return false
[targetmodule] load symbols for specified module
[command] execute command and exit
[-?] display these usage instructions
[-??] display detailed usage instructions
使用 Tlist 等工具列出进程 ID,使用 -p 选项附加到现有进程。
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64>dbh -p:4308
DBH 使用与调试器相同的符号选项。 与调试器一样,DBH 默认保留 SYMOPT_PUBLICS_ONLY 并 SYMOPT_NO_PUBLICS 关闭,默认情况下会打开 SYMOPT_UNDNAME 和 SYMOPT_AUTO_PUBLICS 。 这些默认值可由命令行选项或 DBH 命令替代。
以下示例使用 DBH 命令 addr 414fe0 三 次。 首次使用默认符号选项,因此信息取自私有符号数据。 请注意,这包括有关函数 fgets 的地址、大小和数据类型的信息。 接下来,使用命令 symopt +4000,这会导致 DBH 忽略专用符号数据。 当 再次运行 addr 414fe0 时,将使用公共符号表;这一次,函数 fgets 没有大小和数据类型信息。 最后,使用命令 symopt -2,这会导致 DBH 包含修饰。 当最后一次运行 addr 414fe0 时,将显示函数名称 _fgets的修饰版本。
pid:4308 mod:TimeTest[400000]: addr 414fe0
fgets
name : fgets
addr : 414fe0
size : 113
flags : 0
type : 7e
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagFunction (5)
index : 7d
pid:4308 mod:TimeTest[400000]: symopt +4000
Symbol Options: 0x10c13
Symbol Options: 0x14c13
pid:4308 mod:TimeTest[400000]: addr 414fe0
fgets
name : fgets
addr : 414fe0
size : 0
flags : 0
type : 0
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagPublicSymbol (a)
index : 7f
pid:4308 mod:TimeTest[400000]: symopt -2
Symbol Options: 0x14c13
Symbol Options: 0x14c11
pid:4308 mod:TimeTest[400000]: addr 414fe0
_fgets
name : _fgets
addr : 414fe0
size : 0
flags : 0
type : 0
modbase : 400000
value : 0
reg : 0
scope : SymTagNull (0)
tag : SymTagPublicSymbol (a)
index : 7f