Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/drupal/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Linux 用-fPIC编译的共享库的内存映射和变量位置_Linux_Gcc_Shared Libraries_Elf_Position Independent Code - Fatal编程技术网

Linux 用-fPIC编译的共享库的内存映射和变量位置

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,

我使用的是Linux设备,我想在运行时计算出独立于位置的代码共享库中的符号地址,现在根据一些观察,我可以做到这一点,但是,我仍然对程序/库的加载有一些疑问(是的,我知道怎么做,但我不知道为什么)。假设我们有以下两个C源文件:

// 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;/*加载共享对象的基址*/