目标文件(Object File)是源代码经过编译或汇编后的二进制中间产物,通常以 .o 或 .obj 结尾,包含机器码、数据、符号表和重定位信息,供链接器生成可执行文件或动态库。
核心问题
编译器无法在编译单个翻译单元时知道最终程序中所有地址和库依赖。例如 main.o 调用了 foo,但 foo 的地址可能来自 foo.o、libfoo.a 或动态库。目标文件的作用是先把“本文件能确定的机器码和元信息”写下来,把“最终地址和跨文件引用”留给链接器处理。
核心对象
| 对象 | 作用 |
|---|---|
| text section | 机器指令,通常是函数体。 |
| data / bss section | 已初始化和未初始化的全局数据。 |
| symbol table | 记录本文件定义了哪些符号、引用了哪些外部符号。 |
| relocation entries | 记录哪些地址需要链接器在最终布局后修正。 |
| debug info | 可选调试信息,例如源码行号、变量位置、类型信息。 |
| LTO sections | 启用 LTO(链接时优化) 时可能包含的中间表示(IR)。 |
核心机制
最小流程如下:
a.cpp -> a.o
b.cpp -> b.o
a.o + b.o -> app假设 a.cpp 调用 int foo();,但 foo 定义在 b.cpp。编译 a.cpp 时,编译器会在 a.o 中生成一条调用指令占位,并在符号表里记录“我引用了外部符号 foo”。由于 foo 的最终地址未知,a.o 还会包含重定位项,告诉链接器“这里需要在知道 foo 地址后修正”。
链接器处理目标文件时会:
- 合并各目标文件的 section。
- 建立全局符号表,匹配定义和引用。
- 抽取静态库中需要的对象。
- 根据最终布局修正重定位项。
- 生成可执行文件、共享库或新的可重定位对象。
LTO 目标文件
启用 LTO 后,目标文件可能不只是普通机器码容器。以 GCC 为例,-flto 会把 GIMPLE bytecode 写入特殊 ELF section;以 LLVM 为例,Clang 可生成 LLVM bitcode 形式的对象。最终链接时,工具链从这些对象中取出 IR 交给 LTO 优化器。
这解释了两个常见现象:
- LTO 对象需要匹配的编译器、链接器插件和归档工具。
- 非 fat LTO 对象可能不能被完全不懂 LTO 的传统工具链正常链接。
边界与常见坑
- 目标文件不是可执行文件。它通常缺少入口点布局、最终地址和完整依赖解析。
- 静态库不是一个巨大目标文件,而是一组目标文件的归档;链接器通常只抽取被需要的成员。
nm、objdump、readelf看到的是目标文件层面的符号和 section,不一定能还原源码层语义。- 调试信息和优化级别会影响目标文件内容;同一源码可以生成差异很大的目标文件。
- LTO 目标文件可能依赖工具链版本,不能把其中的 IR 当稳定发布接口。
相关术语
- 翻译单元
- 链接器
- LTO(链接时优化)
- 中间表示(IR)
- 符号可见性
- ABI