.o / .a / .so 文件中到底有什么?

What exactly is in a .o / .a / .so file?

我想知道编译 C++ 程序产生的 .o 或 .so 文件中到底存储了什么。 This post gives a quite good overview of the compilation process and the function of a .o file in it, and as far as I understand from this post,.a 和 .so 文件只是多个 .o 文件合并成一个文件,以静态 (.a) 或动态 (.so) 方式链接。

但我想检查一下我是否正确理解了这样一个文件中存储的内容。编译如下代码后

void f();
void f2(int);

const int X = 25;

void g() {
  f();
  f2(X);
}

void h() {
  g();
}

我希望在 .o 文件中找到以下项目:

然后像 nm 这样的程序会列出来自两个 table 的所有符号名称。

我想编译器可以通过调用 f2(25) 来优化调用 f2(X),但是它仍然需要在 .o 文件中保留符号 X,因为无法知道是否会在不同的 .o 文件中使用。

这样说对吗? .a 和 .so 文件是否一样?

感谢您的帮助!

您在问题中描述的内容(函数的机器代码、初始化数据和重定位表)与 .o(对象)和 .so(共享对象)文件中的内容几乎完全相同。

.a(存档)基本上是多个 .o(对象)文件,它们捆绑在一起以便在 linking 期间更容易参考。 ("Link libraries")

.so(共享对象)文件包含一些额外的元数据,例如 other.so 需要 linked。(xyz.so可能引用驻留在 abc.so 中的一些函数,以及 abc.so 需要 link 编辑的信息,以及可选的查找 abc.so 的路径(RPATH) , 需要编码成 xyz.so.)

Windows .dll(动态 link 库)文件基本上是具有不同名称的共享对象 (.so)。

免责声明: 这大大简化了事情,但足以满足 "The Truth (tm)" 的日常开发人员需求。

您对 object 文件的总体思路非常正确。在 "table that specifies at which addresses in the file" 中,我会将 "addresses" 替换为 "offsets",但这只是措辞。

.a 文件只是存档(一种早于 tar 的旧格式,但功能相同)。你可以用 tar 文件替换 .a 文件,只要你教 linker 解压它们并且只用 link 替换其中包含的所有 .o 文件(或多或少,有不 link 与存档中 object 文件的逻辑有点多,但这只是一种优化)。

.so 文件不同。它们比 object 文件更接近最终的二进制文件。解析了所有符号的 .so 文件至少理论上可以 运行 作为程序。事实上,对于 PIE(位置无关的 executables),共享库和程序之间的区别(至少在理论上)只是 header 中的几位。它们包含有关动态 linker 如何加载库的说明(或多或少与普通程序相同的说明)和包含告诉动态 linker 如何加载的指令的重定位 table解析外部符号(再次,在程序中相同)。动态库(和程序)中所有未解析的符号都通过间接 tables 访问,这些符号在动态 linking 时间填充(程序 start 或 dlopen) .

如果我们将其简化很多,objects 和共享库之间的区别在于共享库中已经做了更多的工作来不进行文本重定位(这不是绝对必要和强制执行的,但这是一般的想法)。这意味着在 object 文件中,汇编程序只为 linker 然后填充的地址生成了占位符,对于共享库,地址中填充了要跳转的地址 tables 所以库的文本不需要更改,只需要有限的跳转 table.

顺便说一句。我说的是 ELF。较旧的格式在程序和库之间存在更多差异。