dlopen() 如何创建只读 VMA?

How does dlopen() create the read-only VMA?

我研究了 Linux 下 dlopen() 如何在内存中加载动态库。但我找不到 glibc 库如何或在何处创建内存中的只读区域。

Glibc 的 dlopen() 使用程序头来查找 LOAD 类型的段并将它们映射到内存中。对于动态库,这只是前两个:

Program Headers:
Type           Offset             VirtAddr           PhysAddr           FileSiz            MemSiz              Flags  Align
LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000 0x000000000000075c 0x000000000000075c  R E    0x200000
LOAD           0x0000000000000e00 0x0000000000200e00 0x0000000000200e00 0x0000000000000228 0x0000000000000230  RW     0x200000
...

保护位(前列)用于第一个 read/execute 和第二个 read/write。相应的部分是:

 Section to Segment mapping:
  Segment Sections...
   00     .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame
   01     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss

从init进程中随机选择一个动态库的内存布局如下:

7f4b67833000-7f4b67837000 r-xp 000000 08:01 393388 /lib/x86_64-linux-gnu/libcap.so.2.25
7f4b67837000-7f4b67a37000 ---p 004000 08:01 393388 /lib/x86_64-linux-gnu/libcap.so.2.25
7f4b67a37000-7f4b67a38000 r--p 004000 08:01 393388 /lib/x86_64-linux-gnu/libcap.so.2.25
7f4b67a38000-7f4b67a39000 rw-p 005000 08:01 393388 /lib/x86_64-linux-gnu/libcap.so.2.25

在这种情况下,有一个额外的内存区域,只有读取设置的保护位。这样做的原因是什么,这是在哪里完成的?为什么 .rodata 部分包含在数据可执行的第一个段中?

加载部分在elf/dl-load.c中完成:

有趣的函数是 _dl_map_segments,它在第 1181 行从 _dl_map_object_from_fd 函数调用。 此函数在文件 elf/dl-map-segments.h.

中定义

但是这个函数只映射了带有保护位的段。我错过了什么吗?

And why is the .rodata section contained in the first segment where data is executable?

可能把它和代码放在一起,这样就可以一起写保护了。

But this function only maps the segments with their protection bits.

保护位设置只读区; __mmap 在两个地方被调用,第三个参数由 c->prot 给出。这是 PROT_EXECPROT_READPROT_WRITE 的按位组合。如果 PROT_READ 不存在,则映射将是只读的。

中间的只读区是响应PT_GNU_RELRO程序头使用mprotect创建的。这是由 eu-readelf 输出建议的:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x001698 0x001698 R   0x1000
  LOAD           0x002000 0x0000000000002000 0x0000000000002000 0x001b01 0x001b01 R E 0x1000
  LOAD           0x004000 0x0000000000004000 0x0000000000004000 0x000bdc 0x000bdc R   0x1000
  LOAD           0x005950 0x0000000000006950 0x0000000000006950 0x000800 0x000808 RW  0x1000
  DYNAMIC        0x005cf0 0x0000000000006cf0 0x0000000000006cf0 0x0001f0 0x0001f0 RW  0x8
  NOTE           0x000238 0x0000000000000238 0x0000000000000238 0x000024 0x000024 R   0x4
  GNU_EH_FRAME   0x0045b4 0x00000000000045b4 0x00000000000045b4 0x00010c 0x00010c R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
  GNU_RELRO      0x005950 0x0000000000006950 0x0000000000006950 0x0006b0 0x0006b0 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00      [RO: .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt]
   01      [RO: .init .plt .plt.got .text .fini]
   02      [RO: .rodata .eh_frame_hdr .eh_frame]
   03      [RELRO: .init_array .fini_array .data.rel.ro .dynamic .got] .data .bss
   04      [RELRO: .dynamic]
   05      [RO: .note.gnu.build-id]
   06      [RO: .eh_frame_hdr]
   07     
   08      [RELRO: .init_array .fini_array .data.rel.ro .dynamic .got]

(我假设您的示例共享对象来自 Debian 10 或一些类似的发行版。)

PT_GNU_RELROelf/dl-load.c 中与其他程序头一起被解析。重定位完成后,只读设置本身应用于 elf/dl-reloc.c、函数 _dl_protect_relroRELRO 代表 relocation (and then) read-only.

只读部分没有单独的 PT_LOAD 段,因为最初出于性能原因希望限制加载段的数量,但由于性能原因,这不再有效竞争要求。