我目前正在研究 ELF 二进制文件以及它们如何加载到 Linux 内核的内存中。我真的很困惑 PT_LOAD 段是如何加载到内存中的。我有一个名为 test.elf:
的 ELF 可执行文件root@ubuntu-s-4vcpu-8gb-amd-sgp1-01:~/C# readelf -l test.elf
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x10c0
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000718 0x0000000000000718 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x0000000000000259 0x0000000000000259 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000124 0x0000000000000124 R 0x1000
LOAD 0x0000000000002da0 0x0000000000003da0 0x0000000000003da0
0x0000000000000274 0x0000000000000278 RW 0x1000
DYNAMIC 0x0000000000002db0 0x0000000000003db0 0x0000000000003db0
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000030 0x0000000000000030 R 0x8
从上面的输出中可以看到,该文件有四个 PT_LOAD 段。我希望它能以四个段的形式加载到内存中。但是,当我检查 /proc/pid/maps 时,它显示正在运行的进程有从可执行文件加载的五个段。
root@ubuntu-s-4vcpu-8gb-amd-sgp1-01:~/C# cat /proc/209183/maps
55acd5d31000-55acd5d32000 r--p 00000000 fc:01 517731 /root/C/test.elf
55acd5d32000-55acd5d33000 r-xp 00001000 fc:01 517731 /root/C/test.elf
55acd5d33000-55acd5d34000 r--p 00002000 fc:01 517731 /root/C/test.elf
55acd5d34000-55acd5d35000 r--p 00002000 fc:01 517731 /root/C/test.elf
55acd5d35000-55acd5d36000 rw-p 00003000 fc:01 517731 /root/C/test.elf
55acd6e88000-55acd6ea9000 rw-p 00000000 00:00 0 [heap]
7f85da4fe000-7f85da501000 rw-p 00000000 00:00 0
7f85da501000-7f85da529000 r--p 00000000 fc:01 31232 /usr/lib/x86_64-linux-gnu/libc.so.6
7f85da529000-7f85da6be000 r-xp 00028000 fc:01 31232 /usr/lib/x86_64-linux-gnu/libc.so.6
7f85da6be000-7f85da716000 r--p 001bd000 fc:01 31232 /usr/lib/x86_64-linux-gnu/libc.so.6
我还注意到第三和第四段具有相同的偏移量和权限。经过大量搜索,我仍然不明白为什么。我还检查了段中驻留的部分,这些部分没有什么特别的。
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
谁能解释一下,我真的很想知道这种差异背后的原因!
我预计它只有四个部分
这是我系统上
readelf -Wl /usr/bin/cat
的输出:
Elf file type is DYN (Position-Independent Executable file)
Entry point 0x3990
There are 13 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000040 0x0000000000000040 0x0000000000000040 0x0002d8 0x0002d8 R 0x8
INTERP 0x000318 0x0000000000000318 0x0000000000000318 0x00001c 0x00001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000000000 0x0000000000000000 0x001600 0x001600 R 0x1000
LOAD 0x002000 0x0000000000002000 0x0000000000002000 0x004111 0x004111 R E 0x1000
LOAD 0x007000 0x0000000000007000 0x0000000000007000 0x0011a0 0x0011a0 R 0x1000
LOAD 0x008a90 0x0000000000009a90 0x0000000000009a90 0x0005d8 0x000730 RW 0x1000
DYNAMIC 0x008c08 0x0000000000009c08 0x0000000000009c08 0x0001f0 0x0001f0 RW 0x8
NOTE 0x000338 0x0000000000000338 0x0000000000000338 0x000050 0x000050 R 0x8
NOTE 0x000388 0x0000000000000388 0x0000000000000388 0x0000d0 0x0000d0 R 0x4
GNU_PROPERTY 0x000338 0x0000000000000338 0x0000000000000338 0x000050 0x000050 R 0x8
GNU_EH_FRAME 0x007dc8 0x0000000000007dc8 0x0000000000007dc8 0x0000b4 0x0000b4 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x008a90 0x0000000000009a90 0x0000000000009a90 0x000570 0x000570 R 0x1
Section to Segment mapping:
Segment Sections...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .note.package .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag .note.package
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .data.rel.ro .dynamic .got
这表示内核应该执行 4 个
mmap
来建立 4 个 LOAD
段。让我们看看是否如此。
gdb -q /usr/bin/cat
(gdb) starti < /dev/null
Starting program: /usr/bin/cat < /dev/null
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc7000
Program stopped.
0x00007ffff7fe4bc0 in _start () from /lib64/ld-linux-x86-64.so.2
(gdb) info prog
Last stopped for thread 1 (process 1304).
Using the running image of child process 1304.
Program stopped at 0x7ffff7fe4bc0.
Type "info stack" or "info registers" for more information.
(gdb) shell cat /proc/1304/maps
555555554000-555555556000 r--p 00000000 08:20 92665 /usr/bin/cat
555555556000-55555555b000 r-xp 00002000 08:20 92665 /usr/bin/cat
55555555b000-55555555d000 r--p 00007000 08:20 92665 /usr/bin/cat
55555555d000-55555555f000 rw-p 00008000 08:20 92665 /usr/bin/cat
7ffff7fc3000-7ffff7fc7000 r--p 00000000 00:00 0 [vvar]
7ffff7fc7000-7ffff7fc9000 r-xp 00000000 00:00 0 [vdso]
7ffff7fc9000-7ffff7fca000 r--p 00000000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7fca000-7ffff7ff1000 r-xp 00001000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7ff1000-7ffff7ffb000 r--p 00028000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7ffb000-7ffff7fff000 rw-p 00031000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
事实上,只有 4 个映射。现在让我们运行直到
main
:
(gdb) b main
Downloading source file /usr/src/debug/coreutils-9.3-5.fc39.x86_64/separate/../src/cat.c
Breakpoint 1 at 0x5555555566c0: file ../src/cat.c, line 540.
(gdb) c
Continuing.
Breakpoint 1, main (argc=1, argv=0x7fffffffde48) at ../src/cat.c:540
540 {
(gdb) shell cat /proc/1304/maps
555555554000-555555556000 r--p 00000000 08:20 92665 /usr/bin/cat
555555556000-55555555b000 r-xp 00002000 08:20 92665 /usr/bin/cat
55555555b000-55555555d000 r--p 00007000 08:20 92665 /usr/bin/cat
55555555d000-55555555e000 r--p 00008000 08:20 92665 /usr/bin/cat
55555555e000-55555555f000 rw-p 00009000 08:20 92665 /usr/bin/cat
7ffff7dd4000-7ffff7dd7000 rw-p 00000000 00:00 0
7ffff7dd7000-7ffff7dfd000 r--p 00000000 08:20 117079 /usr/lib64/libc.so.6
7ffff7dfd000-7ffff7f5d000 r-xp 00026000 08:20 117079 /usr/lib64/libc.so.6
7ffff7f5d000-7ffff7fab000 r--p 00186000 08:20 117079 /usr/lib64/libc.so.6
7ffff7fab000-7ffff7faf000 r--p 001d3000 08:20 117079 /usr/lib64/libc.so.6
7ffff7faf000-7ffff7fb1000 rw-p 001d7000 08:20 117079 /usr/lib64/libc.so.6
7ffff7fb1000-7ffff7fbb000 rw-p 00000000 00:00 0
7ffff7fc3000-7ffff7fc7000 r--p 00000000 00:00 0 [vvar]
7ffff7fc7000-7ffff7fc9000 r-xp 00000000 00:00 0 [vdso]
7ffff7fc9000-7ffff7fca000 r--p 00000000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7fca000-7ffff7ff1000 r-xp 00001000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7ff1000-7ffff7ffb000 r--p 00028000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7ffb000-7ffff7ffd000 r--p 00031000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffff7ffd000-7ffff7fff000 rw-p 00033000 08:20 117069 /usr/lib64/ld-linux-x86-64.so.2
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]
发生什么事了?为什么会出现第5个映射?
这是之前的状态:
55555555d000-55555555f000 rw-p 00008000 08:20 92665 /usr/bin/cat
这是之后的状态:
55555555d000-55555555e000 r--p 00008000 08:20 92665 /usr/bin/cat
55555555e000-55555555f000 rw-p 00009000 08:20 92665 /usr/bin/cat
注意映射的第一页如何变成只读的吗?
但这正是
GNU_RELRO
告诉加载程序要做的事情:加载程序完成更新重定位后,它应该对它们进行写保护。更准确地说,它应该对第四个映射的一部分进行写保护,该部分覆盖从 [0x8a90, 0x8a90+0x570)
开始偏移 /usr/bin/cat
处的字节。