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

时间:2019-06-03 16:01:16

标签: c gcc assembly elf dynamic-linking

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

所以我的主要问题是为什么要这样做,而不是我最初的方式是通过R_X86_64_COPY段中的R_X86_64_GLOB_DAT“生活”并访问x来完成此操作是通过GOT(或其他重定位机制)实现的?

1 个答案:

答案 0 :(得分:5)

  

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

是,不是。暂时不考虑哪个ELF对象实际上提供了x的问题,我们知道该变量是用非零初始值设定项定义的。如果看到分配给ELF对象的.bss部分的变量,则说明发生了奇怪的事情,因为该部分用于 default-initialized 数据。它在动态对象中不占空间,因为实际上没有存储全零位的初始值。稍后再详细介绍。

  

[...]我想我了解大多数技术细节   (尽管对于的确切含义还是有些困惑   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对象提供,但其初始值由该对象提供。

动态链接器将它们一起用于将库中的初始值复制到可执行文件的变量副本中,并(贪婪地)处理变量在库中的重定位。

  

这   似乎很奇怪,因为xextern中定义为main.out并在   varlib.so

这似乎很奇怪,因为您假设C翻译单元的逻辑属性应该直接对应地映射到相应ELF对象的物理属性上。这并不疯狂-它们在很大范围内 do 进行映射-但是它们不能完美地映射,因为ELF语义不能完美地反映C语义。