Skip to content

GCC/LLVM 编译符号表、死代码消除与调试信息原理总结

本文档总结了关于编译器(GCC/LLVM)在处理符号表、不同构建产物(静态库/动态库)、死代码消除(DCE)以及调试信息(Debug Info)时的核心行为与原理。


1. 核心概念:两套符号表

在 ELF 格式(Linux 下的标准二进制格式)中,存在两张截然不同的符号表:

A. 全量符号表 (.symtab)

  • 性质:编译构建时的“完整花名册”。
  • 内容:包含所有符号(全局函数、全局变量、局部 static 变量、未导出符号)。
  • 用途:主要供 静态链接器 (ld)调试器 (gdb) 使用。
  • 生命周期:非运行时必需。可以通过 strip 命令被彻底移除,不影响程序正常运行(但影响调试)。

B. 动态符号表 (.dynsym)

  • 性质:运行时的“对外接口目录”。
  • 内容:仅包含 导出符号(供外部调用的非静态全局函数/变量)和 导入符号(程序依赖的外部符号)。
  • 用途:供 动态链接器 (ld-linux.so) 和运行时加载器 (dlopen) 使用。
  • 生命周期:运行时必需。绝对不能被 strip 移除,否则程序无法启动或加载。

2. 不同编译产物的行为差异

静态库 (.a)

  • 本质:目标文件 (.o) 的归档集合。
  • 符号行为保留所有符号。因为在链接前无法预知哪些符号会被使用。
  • Strip 操作极度危险。对 .a 使用 strip --strip-all 会导致链接时出现 Undefined Reference 错误。通常仅允许 strip --strip-debug

动态库 (.so) 与 可执行文件

  • 本质:已部分链接或完全链接的 ELF 文件。
  • 符号行为:默认同时包含 .symtab.dynsym
  • Strip 操作
    • 使用 strip(或 -s 参数)会移除 .symtab,但 永久保留 .dynsym
    • 可通过 -fvisibility=hidden 编译参数减少进入 .dynsym 的符号数量(仅导出显式标记为 default 的符号)。

3. 死代码消除 (Dead Code Elimination) 与符号表

死代码是否出现在符号表中,取决于它是在哪个阶段被消除的:

  1. 编译阶段消除 (Compiler Level)

    • 场景:同一个源文件内的 static 函数未被调用。
    • 结果:编译器(-O1 及以上)直接不生成机器码。
    • 符号表不会出现
  2. 链接阶段消除 (Linker Level)

    • 默认行为:未被调用的全局函数,默认保留代码和符号(因为链接器不知道其他模块是否会用到)。符号表中 存在
    • 开启 GC (--gc-sections)
      • 配合编译参数 -ffunction-sections -fdata-sections
      • 链接器发现某个 Section 未被引用,会丢弃该段。
      • 结果:代码被删除,符号表中 不会出现
  3. 未被消除的死代码

    • 如果未使用上述 GC 选项,即使代码永远不执行,它依然占用空间,且符号表中 存在 该符号。

4. 静态变量 (static) 的处理逻辑

同名冲突问题

  • 源文件内 static 变量:具有 Internal Linkage(内部链接)属性。
  • 符号属性:在符号表中标记为 LOCAL
  • 冲突处理:链接器允许存在多个同名的 LOCAL 符号。
  • 内存布局:不同源文件的同名 static 变量在内存中地址不同,互不干扰,完全独立。

导出 (Export) 问题

  • 是否导出不会。静态变量永远不会进入动态符号表 (.dynsym)。
  • 是否存在存在(仅在 .symtab 中)。这意味着它们存在于二进制文件中用于调试或静态链接,但对外部动态库是不可见的。

5. 调试信息 (-g) 与符号表的关系

使用 -g 编译时生成的 DWARF 调试信息符号表 是两套独立的数据结构。

特性符号表 (.symtab)调试信息 (DWARF, .debug_*)
内容名字 地址地址 源代码行号 / 变量类型 / 结构体定义
作用告诉调试器函数在哪里告诉调试器这行代码对应源码哪一行,变量是什么结构
strip 行为strip --strip-all 会删除它strip --strip-debug 就会删除它
独立性默认生成-g 显式开启
  • 总结-g 不会改变符号表的内容,而是额外增加了一组庞大的 Section(如 .debug_info, .debug_line),赋予调试器“源码级”的透视能力(查看源码行、打印结构体成员)。没有 -g 只有符号表时,只能进行汇编级调试。