和可重定向目标文件的格式类似,也包含一个 ELF头
有一项是程序的入口
可执行文件的头中不需要 rel section,也就是 .rel.txt 和 .rel.data 因为执行的位置已经确定在内存中了

这里我们和其他的目标文件格式做个对比

现在这个可执行目标文件已经被转化为一个二进制文件,且这个二进制文件包含加载程序到内存并运行它所需的所有信息
可执行目标文件的格式类似于可重定位目标文件的格式
ELF头描述文件的总体格式。它还包括程序的入口点(entry point),也就是当程序运行时要执行的第一条指令的地址
.text、.rodata 和 .data 节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时内存地址以外
.init 节定义了一个小函数,叫做 _init,程序的初始化代码会调用它
因为可执行文件是完全链接的(已被重定位),所以它不再需要 .rel 节
ELF 可执行文件被设计得很容易加载到内存,可执行文件的连续的片(chunk)被映射到连续的内存段
程序头部表(program header table)描述了这种映射关系
下面展示了可执行文件 prog 的程序头部表,是由 OBJDUMP 显示的
Read-only code segment
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
filesz 0x000000000000069c memsz 0x000000000000069c flags r-x
Read/write data segment
LOAD off 0x0000000000000df8 vaddr 0x0000000000600df8 paddr 0x0000000000600df8 align 2**21
filesz 0x0000000000000228 memsz 0x0000000000000230 flags rw-- 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}221= 0x200000)
数据段中
vaddr mod align = 0x600df8 mod 0x200000 = 0xdf8
以及
off mod align = 0xdf8 mod 0x200000 = 0xdf8
这个对齐要求是一种优化,使得当程序执行时,目标文件中的段能够很有效率地传送到内存中。原因有点儿微妙,在于虚拟内存的组织方式,它被组织成一些很大的、连续的、大小为 2 的幂的字节片