csn*_*ate 5 c linux linker elf clang
我正在编写一个ELF加载程序并研究ELF格式。我有一个简单的 hello world 二进制文件,是在 Fedora 38 中使用 Clang 16 创建的,它可以工作,并且 I\xe2\x80\x99ve 未编译\\链接到任何特定选项 ( clang hello.c -o hello)。我检查了程序头readelf,发现一些与我对 LOAD 和 DYNAMIC 头的理解不相符的东西。
例如:
\n\nProgram Headers:\n Type Offset VirtAddr PhysAddr\n FileSiz MemSiz Flags Align\n\xe2\x80\xa6\n LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000\n 0x00000000000004f0 0x00000000000004f0 R 0x1000\n LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000\n 0x000000000000016d 0x000000000000016d R E 0x1000\n LOAD 0x0000000000002000 0x0000000000402000 0x0000000000402000\n 0x00000000000000dc 0x00000000000000dc R 0x1000\n LOAD 0x0000000000002df8 0x0000000000403df8 0x0000000000403df8\n 0x0000000000000214 0x0000000000000218 RW 0x1000\n DYNAMIC 0x0000000000002e08 0x0000000000403e08 0x0000000000403e08\n 0x00000000000001d0 0x00000000000001d0 RW 0x8\nRun Code Online (Sandbox Code Playgroud)\n有两个问题我无法解决。请注意前三个 LOAD 标头如何对齐虚拟地址,这对我来说很有意义。但最后一个 LOAD 标头和 DYNAMIC 标头重叠且未对齐。可执行文件在 Linux 中加载得很好,但 Linux 似乎也解决了这个问题。这里是/proc/<pid>/maps:
00400000-00401000 r--p 00000000 00:23 16905823 /path/to/hello\n00401000-00402000 r-xp 00001000 00:23 16905823 /path/to/hello\n00402000-00403000 r--p 00002000 00:23 16905823 /path/to/hello\n00403000-00404000 r--p 00002000 00:23 16905823 /path/to/hello\n00404000-00405000 rw-p 00003000 00:23 16905823 /path/to/hello\nRun Code Online (Sandbox Code Playgroud)\n所以我有几个问题:
\n\n\n为什么我的链接器会产生未对齐的段?
\n
事实并非如此。要求是应该可以直接mmap访问LOAD段,为此,必须满足以下条件:p_vaddr % pagesize == p_offset % pagesize。LOAD对于输出中的所有段都是如此。
\n\n为什么我的链接器会创建重叠的段?
\n
这没有什么问题——相同的文件内容会在内存中出现两次。
\n另一种方法是在文件中插入一个大洞,从而浪费磁盘空间。
\n另请参阅此答案。
\n\n\nLinux 是否实现了特定的约定、标准或算法来纠正此问题?
\n
无需修正。仔细阅读man mmap。
\n由于offset必须页面对齐,动态加载器将两者 p_vaddr向下舍入p_offset以进行页面对齐,并mmap(..., MAP_FIXED, ...)使用两者的页面对齐值执行。这“覆盖”了一点数据,但保证了代码或数据最终准确地位于其链接的地址处。
更新:
\n\n\n为什么链接器总是将 p_offset 和 p_vaddr 设置为运行时使用的实际值
\n
一般来说,不会这样做,因为静态链接器在运行时不知道页面大小。在 上x86_64,pagesize可以是 4KiB、2MiB 或 1GiB。
然而,这个特定的二进制文件与0x1000(4KiB) 对齐方式链接,因此(静态)链接器可以.p_vaddar将和设置.p_offset为其向下舍入的值(并向.p_memsize上调整以补偿)。静态链接器中的代码与对齐和页面大小无关,因此它不会将 4KiB 视为特殊的(完全合理的方法)。