7.8 可执行目标文件

我们已经看到链接器如何将多个目标文件合并成一个可执行目标文件。我们的示例 C 程序,开始时是一组 ASCII 文本文件,现在已经被转化为一个二进制文件,且这个二进制文件包含加载程序到内存并运行它所需的所有信息。图 7-13 概括了一个典型的 ELF 可执行文件中的各类信息。

可执行目标文件的格式类似于可重定位目标文件的格式。ELF 头描述文件的总体格式。它还包括程序的入口点(entry point),也就是当程序运行时要执行的第一条指令的地址。.text、.rodata 和 .data 节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时内存地址以外。.init 节定义了一个小函数,叫做 _init,程序的初始化代码会调用它。因为可执行文件是完全链接的(已被重定位),所以它不再需要 .rel 节。

ELF 可执行文件被设计得很容易加载到内存,可执行文件的连续的片(chunk)被映射到连续的内存段。程序头部表(program header table)描述了这种映射关系。图 7-14 展示了可执行文件 prog 的程序头部表,是由 OBJDUMP 显示的。

Read-only code segment
LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
     filesz 0x000000000000069c memsz 0x00000oo000o0069c flags r-x

Read/write data segment
LOAD off    0x0000000000000df8 vaddr 0x0000000000600df8 paddr 0x0000000000600df8 align 2**21
     filesz 0x0000000000000228 memsz 0x0000000000000230 flags rw-

图 7-14 示例可执行文件 prog 的程序头部表

off:目标文件中的偏移;vaddr/paddr:内存地址;align:对齐要求;filesz:目标文件中的段大小;memsz:内存中的段大小;flags:运行时访问权限。

从程序头部表,我们会看到根据可执行目标文件的内容初始化两个内存段。第 1 行和第 2 行告诉我们第一个段(代码段)有读/执行访问权限,开始于内存地址 0x400000 处,总共的内存大小是 0x69c 字节,并且被初始化为可执行目标文件的头 0x69c 个字节,其中包括 ELF 头、程序头部表以及 .initx.text 和 .rodata 节。

第 3 行和第 4 行告诉我们第二个段(数据段)有读/写访问权限,开始于内存地址 0x600df8 处,总的内存大小为 0x230 字节,并用从目标文件中偏移 0xdf8 处开始的 .data 节中的 0x228 个字节初始化。该段中剩下的 8 个字节对应于运行时将被初始化为 0 的 .bss 数据。

对于任何段 s,链接器必须选择一个起始地址 vaddr,使得

vaddr mod align = off mod align

这里,off 是目标文件中段的第一个节的偏移量,align 是程序头部中指定的对齐( 2212^{21} = 0x200000)。例如,图 7-14 中的数据段中

vaddr mod align = 0x600df8 mod 0x200000 = 0xdf8

以及

off mod align = 0xdf8 mod 0x200000 = 0xdf8

这个对齐要求是一种优化,使得当程序执行时,目标文件中的段能够很有效率地传送到内存中。原因有点儿微妙,在于虚拟内存的组织方式,它被组织成一些很大的、连续的、大小为 2 的幂的字节片。第 9 章中你会学习到虚拟内存的知识。

最后更新于