Linux 用-fPIC编译的共享库的内存映射和变量位置
我使用的是Linux设备,我想在运行时计算出独立于位置的代码共享库中的符号地址,现在根据一些观察,我可以做到这一点,但是,我仍然对程序/库的加载有一些疑问(是的,我知道怎么做,但我不知道为什么)。假设我们有以下两个C源文件:Linux 用-fPIC编译的共享库的内存映射和变量位置,linux,gcc,shared-libraries,elf,position-independent-code,Linux,Gcc,Shared Libraries,Elf,Position Independent Code,我使用的是Linux设备,我想在运行时计算出独立于位置的代码共享库中的符号地址,现在根据一些观察,我可以做到这一点,但是,我仍然对程序/库的加载有一些疑问(是的,我知道怎么做,但我不知道为什么)。假设我们有以下两个C源文件: // file: main.c #include <stdio.h> extern int global_field; void main() { printf("global field(%p) = %d\n", &global_field,
// file: main.c
#include <stdio.h>
extern int global_field;
void main() {
printf("global field(%p) = %d\n", &global_field, global_field);
}
// file: lib.c
int global_field = 1;
和readelf-sW lib。因此
显示global_字段
符号:
Num: Value Size Type Bind Vis Ndx Name
...
8: 0000000000201028 4 OBJECT GLOBAL DEFAULT 21 global_field
...
和readelf-lW lib.so
输出以下程序头:
...
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x00065c 0x00065c R E 0x200000
LOAD 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000234 0x000238 RW 0x200000
DYNAMIC 0x000e18 0x0000000000200e18 0x0000000000200e18 0x0001c0 0x0001c0 RW 0x8
NOTE 0x000190 0x0000000000000190 0x0000000000000190 0x000024 0x000024 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x000df8 0x0000000000200df8 0x0000000000200df8 0x000208 0x000208 R 0x1
现在我们运行程序,它输出以下内容:
global field(0x7ffff7dda028) = 1
...
7ffff7bd9000-7ffff7bda000 r-xp 00000000 fd:02 18650951 /.../lib.so
7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951 /.../lib.so
7ffff7dd9000-7ffff7dda000 r--p 00000000 fd:02 18650951 /.../lib.so
7ffff7dda000-7ffff7ddb000 rw-p 00001000 fd:02 18650951 /.../lib.so
...
和cat/proc//maps
输出以下内容:
global field(0x7ffff7dda028) = 1
...
7ffff7bd9000-7ffff7bda000 r-xp 00000000 fd:02 18650951 /.../lib.so
7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951 /.../lib.so
7ffff7dd9000-7ffff7dda000 r--p 00000000 fd:02 18650951 /.../lib.so
7ffff7dda000-7ffff7ddb000 rw-p 00001000 fd:02 18650951 /.../lib.so
...
抱歉,这里的代码有点太多。。。现在我的问题是:
加载段,但有四个内存映射,为什么还有两个映射
LOAD
段,如何确定哪个段映射到哪个内存区域?是否有任何标准或手册global\u字段
的值为0000000000 201028
(参见readelf-sW lib.so
的输出),但是,根据ELF标准:st_值
包含一个虚拟地址。使这些文件的符号对运行时更有用
链接器,节偏移量(文件解释)让位给
虚拟地址(内存解释),其节号为
这无关紧要
我知道这是独立于位置的代码,它不能是虚拟地址,必须是某种偏移量。用符号值减去global\u字段的地址:0x7ffff7dda028-0x201028=0x7FF7BD9000
,偏移量似乎基于最低内存映射的起始地址(请参见cat/proc//maps
的输出)。但是,有没有标准告诉我们,如何以编程方式检测符号的值类型(虚拟地址或偏移量)?如果它是一个偏移量,为什么偏移量应该基于它,为什么它不基于它自己的内存区域(我猜它自己的区域是最后一个,因为它有写权限)
如您所见,程序头中有两个加载段,但有四个内存映射,为什么还有两个映射
因为GNU_RELRO
告诉动态加载程序将第二个PT_加载
段的第一个0x208
字节设为只读
如果将库链接到gcc-shared-o lib.so lib.o-Wl,-z,norelro
,则只会得到3个映射。。。这仍然留下了一个问题,为什么有三个而不是两个
您将注意到此映射:
7ffff7bda000-7ffff7dd9000 ---p 00001000 fd:02 18650951 /.../lib.so
实际上是进程空间中的一个“洞”(不允许访问)。
您还将注意到,第二个PT_LOAD
(实际上对于这两个)的对齐非常大:0x200000
这样做是为了适应运行1MB页面的可能性
如果您再次使用gcc-shared-o lib.so lib.o-Wl,-z,norelro,-z,max page size=4096
重新链接,您现在将只拥有所需的两个映射
默认情况下实际发生的情况是,加载器必须保留第一个和第二个PT_LOAD
之间的偏移量(否则二进制文件将无法正常工作)。因此,它在内核选择的地址(通过mmap(0,…)
)创建一个大型映射(覆盖PT\u加载
段)。然后m保护
s从第一次PT_加载结束
开始的区域,直到整个映射结束且无访问权限为止。最后,它使用MAP\u FIXED
标志将第二个PT\u加载
段固定在所需地址,在两个映射之间留下一个洞
对于两个加载段,如何确定哪个段映射到哪个内存区域?是否有任何标准或手册
你可以很容易地从偏移量中分辨出来。偏移量0
的映射对应于第一个PT\u载荷
,孔不对应于任何东西,偏移量00001000
的映射对应于第二个PT\u载荷
偏移量似乎是基于最低内存映射的起始地址
正确:这是整个库的重新定位。因此
ELF图像(由第一个mmap(0,…)
确定)。该重新定位应用于图像中的每个符号
但是,有没有标准告诉我们,如何以编程方式检测符号的值类型(虚拟地址或偏移量)
没有标准。但您可以使用查找“基址”(重新定位)。特别是,dli\u fbase;/*加载共享对象的基址*/