TL; DR为什么共享库中定义的变量似乎驻留在主程序中定义的段中,而不是共享库中?
我试图了解ELF文件动态链接。我写了一个虚拟的共享库
// varlib.so
int x = 42;
void set_x() {
x = 16;
}
和使用它的程序
// main.out
#include <stdlib.h>
#include <stdio.h>
extern int x;
void set_x();
int f() {
return x;
}
int main(int argc, char** argv) {
set_x();
printf("%d\n", f());
return 0;
}
在查看程序集之前,我假设保存x
的段将来自varlib.so
(可能是.data
段),而main.out
将使用它的GOT表(以及用于修复GOT表条目的重定位)以访问x
。但是在检查中我发现
main.out
函数f
定义为
0000000000400637 <f>:
400637: 55 push rbp
400638: 48 89 e5 mov rbp,rsp
40063b: 8b 05 f7 09 20 00 mov eax,DWORD PTR [rip+0x2009f7] # 601038 <x>
400641: 5d pop rbp
400642: c3 ret
具有重定位
Relocation section '.rela.dyn' at offset 0x490 contains 3 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000601038 0000000600000005 R_X86_64_COPY 0000000000601038 x + 0
其中0x601038在.bss
的{{1}}部分中。
main.out
libvar.so
定义为
set_x
具有重定位
00000000000005aa <set_x>:
5aa: 55 push rbp
5ab: 48 89 e5 mov rbp,rsp
5ae: 48 8b 05 23 0a 20 00 mov rax,QWORD PTR [rip+0x200a23] # 200fd8 <x-0x48>
5b5: c7 00 10 00 00 00 mov DWORD PTR [rax],0x10
5bb: 90 nop
5bc: 5d pop rbp
5bd: c3 ret
其中0x200fd8在Relocation section '.rela.dyn' at offset 0x3d0 contains 8 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
0000000000200fd8 0000000500000006 R_X86_64_GLOB_DAT 0000000000201020 x + 0
的{{1}}部分中。
因此,看来.got
实际上位于varlib.so
的一个段(特别是x
的段)中,而main.out
必须使用它的.bss
表来访问它。即与我完全相反!这似乎很奇怪,因为libvar.so
在.got
中被定义为x
,并且在extern
中被赋予了一个值。我想我了解大多数技术细节(尽管对main.out
和varlib.so
重定位类型的确切含义还是有些困惑;如果有人对重定位类型有很好的指导,将不胜感激。 )。
所以我的主要问题是为什么要这样做,而不是我最初的方式是通过R_X86_64_COPY
段中的R_X86_64_GLOB_DAT
“生活”并访问x
来完成此操作是通过GOT(或其他重定位机制)实现的?
答案 0 :(得分:5)
因此,看来
x
实际上位于main.out
的一段中 (特别是.bss
段)和libvar.so必须使用它的.got
表来访问它。即与我完全相反!
是,不是。暂时不考虑哪个ELF对象实际上提供了x
的问题,我们知道该变量是用非零初始值设定项定义的。如果看到分配给ELF对象的.bss部分的变量,则说明发生了奇怪的事情,因为该部分用于 default-initialized 数据。它在动态对象中不占空间,因为实际上没有存储全零位的初始值。稍后再详细介绍。
[...]我想我了解大多数技术细节 (尽管对于的确切含义还是有些困惑
R_X86_64_COPY
和R_X86_64_GLOB_DAT
重定位类型;
这些重定位类型是关键。 R_X86_64_COPY
是在不同ELF对象中定义的已初始化外部变量的重定位类型,而R_X86_64_GLOB_DAT
是其初始值存储在当前ELF对象中的全局可见对象的相应重定位类型。>
回想一下,每个使用该库的程序都必须拥有其自己的所有可修改对象的副本,而共享库的主要意义在于它仅在内存中驻留一次。因此,由程序而不是由库提供变量是有意义的。但是,它们必须出现在每个ELF对象的重定位表中,因为库的功能需要访问变量的正确副本。
另一方面,此类变量的初始值需要记录在库中,因为在构建其客户端时没有其他地方可以获取它们。原则上,可以在构建初始值时将其复制到可执行文件中,但随后它们会不必要地增加可执行对象的大小(因为无论如何它们都必须位于库对象中),并且可执行文件必须如果修改了库以对变量进行不同的初始化,则会重新构建。
如果有人有 有关重定位类型的良好指南,将不胜感激。
对于场外资源的请求对于SO来说是不重要的,但是我敢肯定Google可以提供几个。简而言之,他们会告诉您的是:
R_X86_64_COPY
标识一个对象,该对象的存储由当前ELF对象提供,但其初始值需要从另一个对象复制,并且
R_X86_64_GLOB_DAT
标识一个对象,该对象的存储由其他ELF对象提供,但其初始值由该对象提供。
动态链接器将它们一起用于将库中的初始值复制到可执行文件的变量副本中,并(贪婪地)处理变量在库中的重定位。
这 似乎很奇怪,因为
x
在extern
中定义为main.out
并在varlib.so
。
这似乎很奇怪,因为您假设C翻译单元的逻辑属性应该直接对应地映射到相应ELF对象的物理属性上。这并不疯狂-它们在很大范围内 do 进行映射-但是它们不能完美地映射,因为ELF语义不能完美地反映C语义。