.so段之间的距离总是保留,无论在内存中的.so被加载?



TLDR: .so不能知道它将加载到哪里,但每个段肯定知道其他段在哪里,相对于自己?


我有以下.c文件:

int shared_variable = 3;
int shared_func()
{
return shared_variable;
}

我用PIC:gcc -fpic -c -g shared.cobjdump编译它,并看到正在使用的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,我看到.text0x1040开始,.got0x3fd0开始,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)。