我想知道究竟是什么存储在编译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(X)
来优化调用f2(25)
,但是它仍然需要将符号X保留在.o文件中,因为无法知道是否会从其他.o文件中使用它。那是正确的吗? .a和.so文件是否相同?
谢谢你的帮助!
最佳答案
您对目标文件的一般想法非常正确。在“指定文件中哪个地址的表”中,我将“地址”替换为“偏移量”,但这仅是措辞。
.a文件只是归档文件(一种旧格式,早于tar,但功能相同)。您可以使用tar文件替换.a文件,只要您教过链接程序解压缩它们并仅与其中包含的所有.o文件进行链接(或多或少,有更多的逻辑可以不与目标文件中的目标文件链接存档,但这不是一个最佳选择)。
.so文件是不同的。它们比目标文件更接近最终二进制文件。解析了所有符号的.so文件至少可以在理论上作为程序运行。实际上,使用PIE(与位置无关的可执行文件),共享库和程序之间的区别(至少在理论上)仅是头文件中的几位。它们包含有关动态链接程序如何加载库的指令(与普通程序几乎相同的指令)和重定位表,该表包含告诉动态链接程序如何解析外部符号的指令(同样,程序中的相同)。 。动态库(和程序)中所有未解析的符号都通过在动态链接时(程序启动或dlopen
)填充的间接表进行访问。
如果我们将其简化很多,则对象和共享库之间的区别在于,在共享库中已经进行了更多工作以不进行文本重定位(这不是严格必要的,也不是强制执行的,但这是总的思路)。这意味着,在目标文件中,汇编器仅生成了链接器随后要填充的地址的占位符,对于共享库,地址中填充了用于跳转表的地址,因此不需要更改库的文本,只有一个有限的跳转表。
顺便提一句。我在说ELF。较旧的格式在程序和库之间存在更多差异。