我正在对这个简单的 C++ 程序的二进制文件进行一些修改,以了解 ELF 的程序头:
\n\nint main(){ }\nRun Code Online (Sandbox Code Playgroud)\n\n编译为:
\n\n\xe2\x9d\xaf make\ng++ -O0 -fverbose-asm -no-pie -o main main.cpp\nRun Code Online (Sandbox Code Playgroud)\n\n我曾经readelf -l main得到以下信息:
Elf file type is EXEC (Executable file)\nEntry point 0x401020\nThere are 11 program headers, starting at offset 64\n\nProgram Headers:\n Type Offset VirtAddr PhysAddr\n FileSiz MemSiz Flags Align\n PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040\n 0x0000000000000268 0x0000000000000268 R 0x8\n INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8\n 0x000000000000001c 0x000000000000001c R 0x1\n [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]\n LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000\n 0x00000000000004c0 0x00000000000004c0 R 0x1000\n...\nRun Code Online (Sandbox Code Playgroud)\n\n我在这个文档中看到:http://man7.org/linux/man-pages/man5/elf.5.html PHDR:
\n\n\n\n\n数组元素(如果存在)指定程序头表本身的位置和大小,\n 在文件和程序的内存映像中\xe2\x80\x90\n公克。此段类型在一个文件中最多只能出现一次。\n 此外,只有当程序头表是程序内存映像的一部分时,才会发生这种情况。如果存在,则它必须位于任何可加载段条目之前。
\n
引用中的出现if present让我想知道如果我跳过 PHDR 标题会发生什么。我使用 vim 的十六进制编辑器更改了mainusing的二进制布局:%!xxd(确保:%!xxd -r在保存之前运行,否则它不再是二进制文件)来获取:
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............\n00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....\n00000020: 4000 0000 0000 0000 1839 0000 0000 0000 @........9......\nRun Code Online (Sandbox Code Playgroud)\n\n到:
\n\n00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............\n00000010: 0200 3e00 0100 0000 2010 4000 0000 0000 ..>..... .@.....\n00000020: 7800 0000 0000 0000 1839 0000 0000 0000 @........9......\nRun Code Online (Sandbox Code Playgroud)\n\n(只改变第20个字节),跳过PHDR头的长度。我readelf再次运行以验证它仍然是有效的 ELF 文件:
\xe2\x9d\xaf readelf -l main\n\nElf file type is EXEC (Executable file)\nEntry point 0x401020\nThere are 11 program headers, starting at offset 120\n\nProgram Headers:\n Type Offset VirtAddr PhysAddr\n FileSiz MemSiz Flags Align\n INTERP 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8\n 0x000000000000001c 0x000000000000001c R 0x1\n [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]\n ...\nRun Code Online (Sandbox Code Playgroud)\n\n令人惊讶的是,该程序仍然执行得很好。为什么我们还需要 PHDR 标头?它对于链接和/或其他情况有用吗?看起来它在运行时根本没有被使用,那么为什么我们要把它放在一边呢?
\n如果主程序是类型ET_EXEC(非 PIE),则它可能无需PT_PHDR. 主要用途PT_PHDR是能够将标头中的(未重定位的)地址与程序标头的实际运行时地址(由动态链接器通过AT_PHDRaux 向量获得)进行比较,以确定加载 PIE 可执行文件的偏移量。
我不确定 glibc 的动态链接器的要求是什么PT_PHDR,但在 musl libc 中,我们只需要它来计算这个加载偏移量,否则根本不使用它。