我正在对这个简单的 C++ 程序的二进制文件进行一些修改,以了解 ELF 的程序头:
int main(){ }
编译:
❯ make
g++ -O0 -fverbose-asm -no-pie -o main main.cpp
我曾经readelf -l main
得到以下内容:
Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x0000000000000268 0x0000000000000268 R 0x8
INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000004c0 0x00000000000004c0 R 0x1000
...
我在本文档中看到:http ://man7.org/linux/man-pages/man5/elf.5.html for PHDR:
数组元素(如果存在)指定程序头表本身在文件和程序内存映像中的位置和大小。这种段类型在一个文件中不能出现多次。此外,只有当程序头表是程序内存映像的一部分时,才会出现这种情况。如果它存在,它必须在任何可加载的段条目之前。
引用中的出现if present
让我想知道如果我跳过 PHDR 标题会发生什么。我使用vim的十六进制编辑器来更改使用的二进制布局main
(:%!xxd
一定要:%!xxd -r
在保存之前运行,否则它不再是二进制文件)从:
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....
00000020: 4000 0000 0000 0000 1839 0000 0000 0000 @........9......
至:
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....
00000020: 7800 0000 0000 0000 1839 0000 0000 0000 @........9......
(仅更改第 20 个字节),跳过 PHDR 标头的长度。我readelf
再次运行以验证它仍然是有效的 ELF 文件:
❯ readelf -l main
Elf file type is EXEC (Executable file)
Entry point 0x401020
There are 11 program headers, starting at offset 120
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
...
令人惊讶的是,该程序仍然执行得非常好。为什么我们甚至需要 PHDR 标头?它对链接和/或其他情况有用吗?似乎它在运行时根本没有使用,那么为什么我们会有这个?