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_EXEC
、PROT_READ
和 PROT_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_RELRO
在 elf/dl-load.c
中与其他程序头一起被解析。重定位完成后,只读设置本身应用于 elf/dl-reloc.c
、函数 _dl_protect_relro
。 RELRO
代表 relocation (and then) read-only.
只读部分没有单独的 PT_LOAD
段,因为最初出于性能原因希望限制加载段的数量,但由于性能原因,这不再有效竞争要求。
我研究了 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_EXEC
、PROT_READ
和 PROT_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_RELRO
在 elf/dl-load.c
中与其他程序头一起被解析。重定位完成后,只读设置本身应用于 elf/dl-reloc.c
、函数 _dl_protect_relro
。 RELRO
代表 relocation (and then) read-only.
只读部分没有单独的 PT_LOAD
段,因为最初出于性能原因希望限制加载段的数量,但由于性能原因,这不再有效竞争要求。