公共和专用符号

当链接器生成完全大小的 .pdb 或 .dbg 符号文件时,它包含两个不同的信息集合:专用符号数据和公共符号表。 这些集合在它们包含的项列表中有所不同,以及它们存储的有关每个项的信息。

专用符号数据包括以下项:

  • Functions

  • 全局变量

  • 局部变量

  • 有关用户定义的结构、类和数据类型的信息

  • 源文件的名称和该文件中对应于每个二进制指令的行号

公共符号表包含的项较少:

  • 函数(声明 为静态的函数除外)

  • 指定为 extern 的全局变量(以及在多个对象文件中可见的任何其他全局变量)

一般情况下,公共符号表仅包含可从一个源文件访问到另一个源文件的项。 仅一个对象文件中可见的项(例如 静态 函数、仅在单个源文件中全局的变量和局部变量)不包含在公共符号表中。

这两组数据也因每个项包含的信息而异。 对于 专用 符号数据中包含的每个项,通常包括以下信息:

  • 项的名称

  • 虚拟内存中项的地址

  • 每个变量、结构和函数的数据类型

  • 每个函数的参数的类型和名称

  • 每个局部变量的范围

  • 与每个源文件中的每个行关联的符号

  • 用于访问堆栈的每个函数的帧指针遗漏(FPO)记录

另一方面, 公共 符号表仅存储有关其中包含的每个项的以下信息:

  • 项的名称。

  • 其模块虚拟内存空间中某项的地址。 对于函数,这是其入口点的地址。

  • 每个函数的帧指针省略(FPO)记录。

  • 可能包括符号前缀/后缀,称为修饰。

公共符号数据可以通过两种方式视为私有符号数据的子集:它包含较短的项目列表,并且还包含有关每个项的信息较少。 例如,公共符号数据根本不包括局部变量。

每个局部变量仅包含在专用符号数据中,其地址、数据类型和作用域。 另一方面,函数同时包含在专用符号数据和公共符号表中,但私有符号数据包括函数名称、地址、FPO 记录、输入参数名称和类型以及输出类型,但公共符号表仅包括函数名称、地址和 FPO 记录。

专用符号数据和公共符号表之间还有一个区别。 公共符号表中的许多项都有用前缀、后缀或两者 修饰 的名称。 这些修饰由 C 编译器、C++编译器和 MASM 汇编程序添加。 典型前缀包括一系列下划线或字符串 __imp_ (指定导入的函数)。 典型的后缀包括一个或多个at符号( @ )后接地址或其他标识字符串。 链接器使用这些修饰来消除符号的歧义,因为函数名称或全局变量名称可以在不同的模块之间重复。 这些修饰是公共符号表是私有符号数据的子集的一般规则的例外。

完整符号文件和精简符号文件

完整符号文件包含专用符号数据和公共符号表。 此类文件有时称为 专用符号文件,但此名称具有误导性,因为此类文件包含专用符号和公共符号。

带状符号文件是一个较小的文件,它仅包含公共符号表,在某些情况下,仅包含公共符号表的子集。 此文件有时称为 公共符号文件

创建完整符号文件和精简符号文件

如果使用 Visual Studio 生成二进制文件,则可以创建完整或剥离的符号文件。 有关构建精简符号的信息,请参阅 /PDBSTRIPPED(剥离私有符号)。

使用 BinPlace 工具,可以从完整符号文件创建去符号文件。 如果使用最常见的 BinPlace 选项(-a -x -s -n),则剥离的符号文件将放置在 -s 开关后列出的目录中,并且完整符号文件放置在 -n 开关之后列出的目录中。 当 BinPlace 剥离一个符号文件时,剥离版和完整版会被赋予相同的签名和其他标识信息。 这允许你使用任一版本进行调试。 有关 BinPlace 的详细信息,请参阅 BinPlace

使用 PDBCopy 工具,可以通过删除私有符号数据,从完整符号文件创建去除私有符号数据的符号文件。 PDBCopy 还可以删除公共符号表的指定子集。 有关详细信息,请参阅 PDBCopy

使用 SymChk 工具,可以确定符号文件是否包含专用符号。 有关详细信息,请参阅 SymChk

在调试器中查看公共和专用符号

可以使用 WinDbg、KD 或 CDB 查看符号。 当其中一个调试器有权访问完整的符号文件时,它既具有专用符号数据中列出的信息,也具有公共符号表中所列的信息。 公共符号数据包含符号修饰。

访问专用符号时,始终使用专用符号数据,因为这些符号不包括在公共符号表中。 这些符号永远不会修饰。

.symopt (设置符号选项) 命令可用于控制用于确定调试器如何使用公共符号和专用符号的符号选项。 例如,此命令启用符号信息调试功能。

 .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 命令时,使用的是公共符号表;这一次没有关于 typingString 的大小和数据类型信息。 最后,使用命令 .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_ONLYSYMOPT_NO_PUBLICS,并默认打开 SYMOPT_UNDNAMESYMOPT_AUTO_PUBLICS。 可以通过命令行选项或 DBH 命令覆盖这些默认值。

下面是 DBH 命令 地址 414fe0 被使用三次的示例。 第一次使用默认符号选项,因此信息取自专用符号数据。 请注意,这包括有关函数 fgets 的地址、大小和数据类型的信息。 接下来,使用命令 symopt +4000,这会导致 DBH 忽略私有符号数据。 当 addr 414fe0 再次运行时,将使用公共符号表;这一次,函数 fget 没有大小和数据类型信息。 最后,使用命令符号 -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 

其他信息

有关符号的其他信息,请参阅 符号语法和符号匹配符号选项符号状态缩写延迟符号加载