共享库中定义的变量的内存位置
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 访问它。即与我的想法完全相反!这看起来很奇怪,因为 x
在 main.out
中定义为 extern
,并在 varlib.so
中给出了一个值。我想我了解大部分技术细节(尽管我仍然对 R_X86_64_COPY
和 R_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 语义。
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 访问它。即与我的想法完全相反!这看起来很奇怪,因为 x
在 main.out
中定义为 extern
,并在 varlib.so
中给出了一个值。我想我了解大部分技术细节(尽管我仍然对 R_X86_64_COPY
和 R_X86_64_GLOB_DAT
重定位类型的确切含义感到困惑;如果有人对重定位类型有很好的指导,我们将不胜感激).
所以我的主要问题是为什么要这样做,而不是我原来的方式,尽管它是在 libvar.so
段中用 x
'living' 完成的,并且main.out
通过 GOT(或其他重定位机制)访问它?
So it would seem that
x
is actually located in a segment ofmain.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
andR_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 asextern
inmain.out
and given a value invarlib.so
.
这看起来很奇怪,只是因为你假设 C 翻译单元的逻辑属性应该直接对应地映射到相应的 ELF 对象的物理属性上。这并不疯狂——它们 做 映射到很大程度 —— 但它们不能完美映射,因为 ELF 语义不能完美地反映 C 语义。