C语言 为什么在一个简单的ELF二进制文件中会有重叠和未对齐的段?

zqdjd7g9  于 2023-08-03  发布在  其他
关注(0)|答案(1)|浏览(109)

我正在写一个ELF加载器,并研究ELF格式。我有一个简单的hello world二进制文件,我在Fedora 38中使用Clang 16创建的,它工作正常,我没有编译\链接任何特定的选项(clang hello.c -o hello)。我用readelf检查了程序头,发现有些东西与我对LOAD和DYNAMIC头的理解不一致。
举例来说:

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
…
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000004f0 0x00000000000004f0  R      0x1000
  LOAD           0x0000000000001000 0x0000000000401000 0x0000000000401000
                 0x000000000000016d 0x000000000000016d  R E    0x1000
  LOAD           0x0000000000002000 0x0000000000402000 0x0000000000402000
                 0x00000000000000dc 0x00000000000000dc  R      0x1000
  LOAD           0x0000000000002df8 0x0000000000403df8 0x0000000000403df8
                 0x0000000000000214 0x0000000000000218  RW     0x1000
  DYNAMIC        0x0000000000002e08 0x0000000000403e08 0x0000000000403e08
                 0x00000000000001d0 0x00000000000001d0  RW     0x8

字符串
有两个问题我无法理解。请注意前三个LOAD头如何对齐虚拟地址,这对我来说很有意义。但最后一个LOAD信头和DYNAMIC信头重叠且未对齐。可执行文件在Linux中加载得很好,但Linux似乎也在考虑这一点。下面是/proc/<pid>/maps

00400000-00401000 r--p 00000000 00:23 16905823                         /path/to/hello
00401000-00402000 r-xp 00001000 00:23 16905823                         /path/to/hello
00402000-00403000 r--p 00002000 00:23 16905823                         /path/to/hello
00403000-00404000 r--p 00002000 00:23 16905823                         /path/to/hello
00404000-00405000 rw-p 00003000 00:23 16905823                         /path/to/hello


所以我有几个问题:

  • 为什么我的连接器会产生未对齐的片段?依靠操作系统来修复对齐似乎是个坏主意。
  • 为什么我的连接器会产生重叠的区段?同样,依靠操作系统来解决这个问题似乎是个坏主意。
  • 是否有Linux实现的特定约定、标准或算法来纠正这一点?感觉这里有一些没有记录的行为,或者是我找不到的有记录的行为。
cygmwpex

cygmwpex1#

为什么我的连接器会产生未对齐的片段?
它没有。要求是应该可以直接mmapLOAD段,为此,以下 * 必须 * 为真:p_vaddr % pagesize == p_offset % pagesize。对于输出中的所有LOAD段都是如此。
为什么我的连接器会产生重叠的区段?
这没有什么问题--相同的文件内容将在内存中出现两次。
另一种方法是在文件中插入一个大洞,浪费磁盘空间。
参见this answer
是否有Linux实现的特定约定、标准或算法来纠正这一点?
无需纠正。仔细阅读man mmap
由于offset必须是页对齐的,因此动态加载器将 bothp_vaddrp_offset向下舍入为页对齐,并使用两者的页对齐值执行mmap(..., MAP_FIXED, ...)。这“覆盖”了一点数据,但保证代码或数据最终精确地到达它所链接的地址。

更新:

为什么链接器不总是将p_offset和p_vaddr设置为运行时使用的实际值
一般情况下,这是不这样做的,因为静态链接器在运行时不知道页面大小。在x86_64上,pagesize可以是4KiB、2 MiB或1GiB。
然而,这个特定的二进制文件是用0x1000(4KiB)对齐链接的,所以(静态)链接器 * 可以 * 将.p_vaddar.p_offset设置为它们的四舍五入值(并将.p_memsize向上调整以补偿)。静态链接器中的代码对对齐和页面大小是不可知的,所以它没有将4KiB视为特殊的(完全合理的方法)。

相关问题