为什么ELF程序headers有两个LOAD入口,而程序布局三段
Why ELF program headers have two LOAD entries, while the program layout three sections
我写了一个 "hello world" 程序并启用了 PIE/PIC。我观察到程序 headers 有 2 个 LOAD 条目:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
... ...
LOAD 0x000000 0x00000000 0x00000000 0x00870 0x00870 R E 0x1000
LOAD 0x000eb0 0x00001eb0 0x00001eb0 0x0015c 0x00164 RW 0x1000
所以在我的理解中,ELF 二进制文件将被加载到 2 个页面中。第一页包含从偏移量 0 到文件大小 0x870 的二进制文件,它是 Read & Execute。由于它们是 0x1000 对齐的,因此第二个条目将加载到第二页,其中包含二进制文件(从偏移量 0xeb0 到 0xeb0+0x15c) .此页面具有读写权限。
当我 "pmap" 运行 进程(或 cat /proc/pid/maps)时,它显示 运行 程序有 3 个页面:
b7554000 4K rw--- [ anon ]
b7555000 1700K r-x-- libc-2.19.so
b76fe000 4K ----- libc-2.19.so
b76ff000 8K r---- libc-2.19.so
b7701000 4K rw--- libc-2.19.so
b7702000 12K rw--- [ anon ]
b771b000 16K rw--- [ anon ]
b771f000 4K r-x-- [ anon ]
b7720000 128K r-x-- ld-2.19.so
b7740000 4K r---- ld-2.19.so
b7741000 4K rw--- ld-2.19.so
b7742000 4K r-x-- main
b7743000 4K r---- main
b7744000 4K rw--- main
bfe68000 132K rw--- [ stack ]
total 2032K
那么 ELF 二进制文件是如何加载的,程序 header 是如何指示 LOAD 指令的?
部分章节 header 如下:
[13] .text PROGBITS 00000480 000480 0002c2 00 AX 0 0 16
[14] .fini PROGBITS 00000744 000744 000014 00 AX 0 0 4
[15] .rodata PROGBITS 00000758 000758 000060 00 A 0 0 4
[16] .eh_frame PROGBITS 000007b8 0007b8 000094 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000084c 00084c 000024 00 A 0 0 4
[18] .jcr PROGBITS 00001eb0 000eb0 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 00001eb4 000eb4 000004 00 WA 0 0 4
[20] .init_array INIT_ARRAY 00001eb8 000eb8 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 00001ebc 000ebc 000108 08 WA 5 0 4
[22] .got PROGBITS 00001fc4 000fc4 00001c 00 WA 0 0 4
[23] .got.plt PROGBITS 00001fe0 000fe0 000020 00 WA 0 0 4
[24] .data PROGBITS 00002000 001000 00000c 00 WA 0 0 4
[25] .tm_clone_table PROGBITS 0000200c 00100c 000000 00 WA 0 0 4
[26] .bss NOBITS 0000200c 00100c 000008 00 WA 0 0 4
I observed the program headers have 2 LOAD entries:
您遗漏了一个重要的程序头:GNU_RELRO
,它告诉加载器 在 映射 LOAD
段之后,它应该 mprotect
其中一部分为只读。
当对现有映射调用具有不同权限的 mprotect
时,内核必须将该映射拆分为多个映射,这就是额外条目的来源。
在我的测试二进制文件中:
LOAD 0x000000 0x00000000 0x00000000 0x0075c 0x0075c R E 0x1000
LOAD 0x000ef4 0x00001ef4 0x00001ef4 0x0012c 0x00130 RW 0x1000
...
GNU_RELRO 0x000ef4 0x00001ef4 0x00001ef4 0x0010c 0x0010c R 0x1
内核实际上在加载程序启动之前就映射了 LOAD
个段,此时的映射如下所示:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56558000 rw-p 00000000 fc:02 801500 /tmp/a.out
f7fdb000-f7fdc000 r-xp 00000000 00:00 0 [vdso]
...
mprotect
之后的地图是这样的:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56557000 r--p 00000000 fc:02 801500 /tmp/a.out
56557000-56558000 rw-p 00001000 fc:02 801500 /tmp/a.out
...
我写了一个 "hello world" 程序并启用了 PIE/PIC。我观察到程序 headers 有 2 个 LOAD 条目:
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
... ...
LOAD 0x000000 0x00000000 0x00000000 0x00870 0x00870 R E 0x1000
LOAD 0x000eb0 0x00001eb0 0x00001eb0 0x0015c 0x00164 RW 0x1000
所以在我的理解中,ELF 二进制文件将被加载到 2 个页面中。第一页包含从偏移量 0 到文件大小 0x870 的二进制文件,它是 Read & Execute。由于它们是 0x1000 对齐的,因此第二个条目将加载到第二页,其中包含二进制文件(从偏移量 0xeb0 到 0xeb0+0x15c) .此页面具有读写权限。
当我 "pmap" 运行 进程(或 cat /proc/pid/maps)时,它显示 运行 程序有 3 个页面:
b7554000 4K rw--- [ anon ]
b7555000 1700K r-x-- libc-2.19.so
b76fe000 4K ----- libc-2.19.so
b76ff000 8K r---- libc-2.19.so
b7701000 4K rw--- libc-2.19.so
b7702000 12K rw--- [ anon ]
b771b000 16K rw--- [ anon ]
b771f000 4K r-x-- [ anon ]
b7720000 128K r-x-- ld-2.19.so
b7740000 4K r---- ld-2.19.so
b7741000 4K rw--- ld-2.19.so
b7742000 4K r-x-- main
b7743000 4K r---- main
b7744000 4K rw--- main
bfe68000 132K rw--- [ stack ]
total 2032K
那么 ELF 二进制文件是如何加载的,程序 header 是如何指示 LOAD 指令的?
部分章节 header 如下:
[13] .text PROGBITS 00000480 000480 0002c2 00 AX 0 0 16
[14] .fini PROGBITS 00000744 000744 000014 00 AX 0 0 4
[15] .rodata PROGBITS 00000758 000758 000060 00 A 0 0 4
[16] .eh_frame PROGBITS 000007b8 0007b8 000094 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000084c 00084c 000024 00 A 0 0 4
[18] .jcr PROGBITS 00001eb0 000eb0 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 00001eb4 000eb4 000004 00 WA 0 0 4
[20] .init_array INIT_ARRAY 00001eb8 000eb8 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 00001ebc 000ebc 000108 08 WA 5 0 4
[22] .got PROGBITS 00001fc4 000fc4 00001c 00 WA 0 0 4
[23] .got.plt PROGBITS 00001fe0 000fe0 000020 00 WA 0 0 4
[24] .data PROGBITS 00002000 001000 00000c 00 WA 0 0 4
[25] .tm_clone_table PROGBITS 0000200c 00100c 000000 00 WA 0 0 4
[26] .bss NOBITS 0000200c 00100c 000008 00 WA 0 0 4
I observed the program headers have 2 LOAD entries:
您遗漏了一个重要的程序头:GNU_RELRO
,它告诉加载器 在 映射 LOAD
段之后,它应该 mprotect
其中一部分为只读。
当对现有映射调用具有不同权限的 mprotect
时,内核必须将该映射拆分为多个映射,这就是额外条目的来源。
在我的测试二进制文件中:
LOAD 0x000000 0x00000000 0x00000000 0x0075c 0x0075c R E 0x1000
LOAD 0x000ef4 0x00001ef4 0x00001ef4 0x0012c 0x00130 RW 0x1000
...
GNU_RELRO 0x000ef4 0x00001ef4 0x00001ef4 0x0010c 0x0010c R 0x1
内核实际上在加载程序启动之前就映射了 LOAD
个段,此时的映射如下所示:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56558000 rw-p 00000000 fc:02 801500 /tmp/a.out
f7fdb000-f7fdc000 r-xp 00000000 00:00 0 [vdso]
...
mprotect
之后的地图是这样的:
56555000-56556000 r-xp 00000000 fc:02 801500 /tmp/a.out
56556000-56557000 r--p 00000000 fc:02 801500 /tmp/a.out
56557000-56558000 rw-p 00001000 fc:02 801500 /tmp/a.out
...