TLDR: .so不能知道它将加载到哪里,但每个段肯定知道其他段在哪里,相对于自己?
我有以下.c
文件:
int shared_variable = 3;
int shared_func()
{
return shared_variable;
}
我用PIC:gcc -fpic -c -g shared.c
和objdump
编译它,并看到正在使用的GOT(第1行),以及GOT条目的重定位(第2行),然后最后获取shared_variable
的值(第3行):
8: 48 8b 05 00 00 00 00 mov rax,QWORD PTR [rip+0x0] # f <shared_func+0xf>
b: R_X86_64_REX_GOTPCRELX shared_variable-0x4
f: 8b 00 mov eax,DWORD PTR [rax]
然后我创建一个共享库:gcc -shared -o libshared.so shared.o
,objdump
,并看到GOT项的重定位已经解决:
1101: 48 8b 05 d0 2e 00 00 mov rax,QWORD PTR [rip+0x2ed0] # 3fd8 <shared_variable-0x48>
1108: 8b 00 mov eax,DWORD PTR [rax]
所以这段代码现在假设GOT条目位于.text
部分的0x2ed0
字节处。事实上,如果我运行readelf
,我看到.text
从0x1040
开始,.got
从0x3fd0
开始,0x2f90
开始。我还看到.text
和.got
在不同的段。
.so
无法知道它将实际上在内存中的位置被加载,所以我认为readelf
的section头中的地址只是建议。但是,由于到GOT条目(mov rax,QWORD PTR [rip+0x2ed0]
)的距离是硬编码的,我假设.so
的段之间的距离将始终与程序头中指定的一样?这也感觉很合理,因为.so
中的代码如何能够找到GOT?
。so无法知道它将在内存中实际加载,
正确的。
所以我认为readelfs节头中的地址只是建议。
不正确的。
在静态链接之后,section头的地址是固定的,并且将保持在它们所在的位置(相对于彼此)。但是请注意,section头在运行时不是必需的,可以删除(例如通过strip
命令)——运行时没有任何东西会注意它们。
但是由于到GOT条目的距离(mov rax,QWORD PTR [rip+0x2ed0])是硬编码的,我假设.so的段之间的距离将始终如程序头中指定的那样?
正确的。所有(可加载的)段一起加载。也就是说,加载器计算所有加载段需要的空间,然后执行单个mmap(0, ...)
1。该mmap
(该.so
的负载基)的结果决定了该.so
中的所有段的位置。
1假设非预链接.so
.
我找到了一个来源。参见John R. Levine著,第10.2章:
因为GOT和引用它的代码在同一个可加载的ELF文件中,而且无论程序在哪里加载,文件中的相对地址都不会改变,代码可以定位具有相对地址的get,将get的地址加载到寄存器中,然后在需要处理静态数据时从get加载指针。(我的重点)
因此,在链接 ELF文件时,中的相对地址是已知的,然后可以解决同一文件中符号的重定位(例如GOT)。