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

Memory location of variables defined in a shared library

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 table(以及修复 GOT table 条目的重定位)以访问 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 在 main.out.bss 部分。

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    

搬迁

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

其中 0x200fd8 在 varlib.so.got 部分。

所以看起来 x 实际上位于 main.out 的一段(特别是 .bss 段)并且 libvar.so 必须使用它是 .got table 访问它。即与我的想法完全相反!这看起来很奇怪,因为 xmain.out 中定义为 extern,并在 varlib.so 中给出了一个值。我想我了解大部分技术细节(尽管我仍然对 R_X86_64_COPYR_X86_64_GLOB_DAT 重定位类型的确切含义感到困惑;如果有人对重定位类型有很好的指导,我们将不胜感激).

所以我的主要问题是为什么要这样做,而不是我原来的方式,尽管它是在 libvar.so 段中用 x 'living' 完成的,并且main.out 通过 GOT(或其他重定位机制)访问它?

So it would seem that x is actually located in a segment of main.out (specifically the .bss segment) and libvar.so has to use it's .got table to access it. i.e. the exact opposite of what I though!

是也不是。暂时搁置哪个 ELF 对象实际提供 x 的问题,我们知道该变量是用非零初始值设定项定义的。如果我们看到分配给 ELF 对象的 .bss 部分的这样一个变量,那么我们就知道发生了一些奇怪的事情,因为该部分用于 default-initialized 数据。它在动态对象中不占用 space 因为 all-bits-zero 初始值实际上并没有存储。稍后会详细介绍。

[...] I think I understand most of the technical details (although am still a bit confused about the exact meanings of the R_X86_64_COPY and R_X86_64_GLOB_DAT relocation types;

而那些搬迁类型是关键。 R_X86_64_COPY是定义在不同ELF对象中的已初始化外部变量的重定位类型,R_X86_64_GLOB_DAT是对应的globally-visible对象的重定位类型,其初始值存储在当前ELF对象中。

回想一下,每个使用该库的程序都必须拥有自己的所有可修改对象的副本,而共享库的很大一部分要点在于它仅驻留在内存中一次。因此,由程序而不是库提供变量是有意义的。但是它们必须出现在每个 ELF 对象的重定位中 table,但是,因为库的函数需要访问变量的正确副本。

另一方面,这些变量的初始值需要记录在库中,因为在构建客户端时没有其他地方可以获取它们。原则上,初始值可以在构建时复制到 executables 中,但随后它们会不必要地增加 executable 对象的大小(因为它们需要在库对象中没有不管怎样),如果修改库以不同方式初始化变量,则需要重建 executables。

If anyone has a good guide on relocation types that would be much appreciated).

对 off-site 资源的请求是 off-topic SO,但我相信 Google 可以提供几个。然而,简而言之,他们会告诉你的是:

  • R_X86_64_COPY标识一个对象,其存储由当前ELF对象提供,但其初始值需要从另一个对象复制,并且

  • R_X86_64_GLOB_DAT标识一个对象,其存储由不同的ELF对象提供,但其初始值由该对象提供

动态链接器一起使用这些将初始值从库复制到 executable 的变量副本,并(贪婪地)处理库中变量的重定位。

This seems odd as x is defined as extern in main.out and given a value in varlib.so.

这看起来很奇怪,只是因为你假设 C 翻译单元的逻辑属性应该直接对应地映射到相应的 ELF 对象的物理属性上。这并不疯狂——它们 映射到很大程度 —— 但它们不能完美映射,因为 ELF 语义不能完美地反映 C 语义。