共享库中定义的变量的内存位置

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.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    
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_COPYR_X86_64_GLOB_DAT重定位类型的确切含义有些困惑;如果有人对重定位类型有很好的指导,将不胜感激)。

所以我的主要问题是,为什么这样做,而不是我原来的方法,尽管它是xlibvar.so段中“生活” 并main.out通过GOT(或其他重定位机制)进行访问?

Joh*_*ger 5

因此,似乎它x实际上位于main.out (特别是该.bss段)的一个段中,并且libvar.so必须使用它的.got 表来访问它。即与我完全相反!

是的,没有。暂时不考虑哪个ELF对象实际提供的问题x,我们知道该变量是用非零初始化程序定义的。如果看到分配给ELF对象的.bss部分的变量,则说明发生了奇怪的事情,因为该部分用于默认初始化的数据。它在动态对象中不占空间,因为实际上没有存储全零位的初始值。稍后对此进行更多讨论。

[...]我想我了解大多数技术细节(尽管对R_X86_64_COPYR_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定义为externin main.out并赋予in的值, 这似乎很奇怪varlib.so

这似乎很奇怪,因为您认为C翻译单元的逻辑属性应该直接对应地映射到相应ELF对象的物理属性上。这并不疯狂-它们确实映射到很大的程度-但它们不能完美映射,因为ELF语义不能完美地反映C语义。