【问题标题】:What exactly is in a .o / .a / .so file?.o / .a / .so 文件中到底有什么?
【发布时间】:2015-09-19 16:20:13
【问题描述】:

我想知道编译 C++ 程序产生的 .o 或 .so 文件中究竟存储了什么。 This post 很好地概述了编译过程和其中的 .o 文件的功能,据我了解 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 文件中找到以下项目:

  • g() 的机器代码,包含调用 f()f2(int) 的一些占位符地址。
  • h() 的机器代码,没有占位符
  • X 的机器码,即数字 25
  • 可以找到某种表格,指定文件中符号g()h()X的地址
  • 另一个表指定了哪些占位符用于引用未定义符号 f()f2(int),在链接期间必须解决这些符号。

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

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

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

感谢您的帮助!

【问题讨论】:

  • 这些文件中有位 (0,1)... ;) [我无法抗拒,我 100% 确定某处有一个“好”的答案,有人会标记这个作为一个骗子]
  • 在 C++ 中,与 C 不同,所有使用 const 注释的命名空间级对象都具有内部链接,除非使用 extern 显式“发布”。因此,在您的情况下,C++ 编译器有权优化 X 。至于你的问题,我建议尝试使用objdumpreadelf 实用程序,它们允许探索.o.a.so 文件的内容。
  • @Mats:给我看一个没有位的文件 :-) [我知道,一个空文件...]
  • 内联函数的工作方式略有不同。 C++ 标准规定每个使用内联函数的编译单元 (CU) 必须 对其进行定义。因此,如果在 CU 中不使用内联函数,编译器有权(但没有义务)将其丢弃。但是,GCC 有 certain pragmas 来缓解代码膨胀。
  • 他们当然不能。没有办法声明一个类的静态数据成员不能从另一个编译单元访问。

标签: c++ c compilation object-files archive-file


【解决方案1】:

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

.a(档案)基本上是多个 .o(对象)文件捆绑在一起,以便在链接期间更容易参考。 (“链接库”)

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

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

免责声明:这大大简化了事情,但足够接近“真相 (tm)”,可以满足日常开发人员的需求。

【讨论】:

    【解决方案2】:

    您对目标文件的一般概念非常正确。在“指定文件中哪些地址的表”中,我会将“地址”替换为“偏移量”,但这只是措辞。

    .a 文件只是档案(一种早于 tar 的旧格式,但做同样的事情)。你可以用 tar 文件替换 .a 文件,只要你教链接器解压缩它们并只链接其中包含的所有 .o 文件(或多或少,有一点逻辑不与目标文件链接存档不是必需的,但这只是一种优化)。

    .so 文件不同。它们比目标文件更接近最终的二进制文件。一个解析了所有符号的 .so 文件至少在理论上可以作为程序运行。事实上,对于 PIE(位置无关的可执行文件),共享库和程序之间的区别(至少在理论上)只是标题中的几位。它们包含动态链接器如何加载库的指令(与普通程序或多或少相同的指令)和一个重定位表,其中包含告诉动态链接器如何解析外部符号的指令(同样,在程序中相同) .动态库(和程序)中所有未解析的符号都通过在动态链接时填充的间接表访问(程序启动或dlopen)。

    如果我们对此进行大量简化,对象和共享库之间的区别在于,在共享库中做了更多的工作以不进行文本重定位(这不是严格必要和强制执行的,但这是一般的想法)。这意味着在目标文件中,汇编器只为链接器填充的地址生成占位符,对于共享库,地址填充有跳转表的地址,因此库的文本不需要更改,只有一个有限的跳转表。

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

    【讨论】:

    • .so 不能包含 main() 函数,可以吗?也许这也值得指出,因为main() 是可执行文件的入口点,还是我弄错了?
    • 如果 .so 文件包含未解析的符号,是否有可能出现问题?
    • @MäxMüller 你错了。我见过很多main 的共享库。 main 不是可执行文件的入口点。入口点只是标头中的一个地址(或偏移量)。在 main 之前,在普通程序中运行大量代码,设置 stdio,设置环境变量,解析共享库等。即使是最简单的程序也应该有一个入口点,至少可以执行以下操作:exit(main(argc, argv)); 来处理从main返回退出。
    • @PieterNuyts 共享库包含大量未解析的符号,通常要从其他共享库(通常是 libc)中解析。也没有什么能阻止您创建从主程序解析符号的共享库。
    • 有趣。这就是为什么我喜欢这个网站,有很多东西要学,还有很多很棒的人来回答你的问题。感谢您的洞察力!
    猜你喜欢
    • 2015-07-23
    • 2012-08-30
    • 2012-04-06
    • 2010-11-10
    • 2021-06-02
    • 2012-08-27
    • 1970-01-01
    • 1970-01-01
    • 2016-06-06
    相关资源
    最近更新 更多