Mic*_*ael 5 c assembly gcc elf dynamic-linking
TL; DR为什么共享库中定义的变量似乎驻留在主程序而不是共享库中定义的段中?
我正在尝试了解ELF文件动态链接。我写了一个虚拟的共享库
// varlib.so
int x = 42;
void set_x() {
x = 16;
}
Run Code Online (Sandbox Code Playgroud)
和使用它的程序
// 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;
}
Run Code Online (Sandbox Code Playgroud)
在查看程序集之前,我假设持有的段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
Run Code Online (Sandbox Code Playgroud)
搬迁
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
Run Code Online (Sandbox Code Playgroud)
其中0x601038在的.bss部分中main.out。
libvar.soset_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
Run Code Online (Sandbox Code Playgroud)
搬迁
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
Run Code Online (Sandbox Code Playgroud)
其中0x200fd8在的.got部分中varlib.so。
因此,似乎x实际上位于main.out(特别是该.bss段)的一个段中,并且libvar.so必须使用它的.got表来访问它。即与我完全相反!x定义为externin main.out并赋予in的值,这似乎很奇怪varlib.so。我想我了解大多数技术细节(尽管仍然对R_X86_64_COPY和R_X86_64_GLOB_DAT重定位类型的确切含义有些困惑;如果有人对重定位类型有很好的指导,将不胜感激)。
所以我的主要问题是,为什么这样做,而不是我原来的方法,尽管它是x在libvar.so段中“生活” 并main.out通过GOT(或其他重定位机制)进行访问?
因此,似乎它
x实际上位于main.out(特别是该.bss段)的一个段中,并且libvar.so必须使用它的.got表来访问它。即与我完全相反!
是的,没有。暂时不考虑哪个ELF对象实际提供的问题x,我们知道该变量是用非零初始化程序定义的。如果看到分配给ELF对象的.bss部分的变量,则说明发生了奇怪的事情,因为该部分用于默认初始化的数据。它在动态对象中不占空间,因为实际上没有存储全零位的初始值。稍后对此进行更多讨论。
[...]我想我了解大多数技术细节(尽管对
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定义为externinmain.out并赋予in的值, 这似乎很奇怪varlib.so。
这似乎很奇怪,因为您认为C翻译单元的逻辑属性应该直接对应地映射到相应ELF对象的物理属性上。这并不疯狂-它们确实映射到很大的程度-但它们不能完美映射,因为ELF语义不能完美地反映C语义。