### 讲讲 Linux 平台的 ELF 文件结构

ELF 文件的两种视图

理解 ELF 文件的关键在于，它有两个不同的、但相互关联的视图：

1. 链接视图（Linking View）： 供编译器和链接器使用。它由节（Sections）组成，主要用于编译、链接和重定位
2. 执行视图（Execution View）： 供操作系统加载器使用。它由段（Segments）组成，用于将程序加载到内存中并执行

这两种视图由 ELF 头部中的两个表来描述：节头部表和程序头部表

ELF 头部

每个 ELF 文件的开头都是一个 ELF 头部，它提供了文件的基本信息，就像 PE 文件的 DOS 头部一样。它定义了文件的类型、机器架构、入口点地址等

ELF 头部最重要的一些字段是：

- `e_ident[EI_MAG0-3]`：4 字节的魔数，固定为 `0x7f, 'E', 'L', 'F'`。这是识别 ELF 文件的唯一标志
- `e_ident[EI_CLASS]`：指定文件架构，`1` 代表 32 位，`2` 代表 64 位
- `e_ident[EI_DATA]`：指定字节序，`1` 代表小端序，`2` 代表大端序
- `e_type`：文件类型，如 `ET_EXEC`（可执行文件）、`ET_DYN`（共享库）、`ET_REL`（可重定位文件）
- `e_entry`：程序的入口点地址（虚拟地址）
- `e_phoff`：程序头部表（Program Header Table）的文件偏移量
- `e_shoff`：节头部表（Section Header Table）的文件偏移量
- `e_phentsize`：程序头部表中每个条目的大小
- `e_phnum`：程序头部表的条目数量
- `e_shentsize`：节头部表中每个条目的大小
- `e_shnum`：节头部表的条目数量

链接视图：节

节是 ELF 文件的基本单元，用于组织文件中的各种数据和代码。每个节都有特定的目的

常见的节有：

- `.text`：包含可执行代码
- `.data`：包含已初始化的全局变量和静态变量
- `.rodata`：包含只读数据，如字符串常量
- `.bss`：包含未初始化的全局变量和静态变量，在文件中不占用空间，加载时由加载器分配和清零
- `.symtab`：符号表，包含了程序中所有符号（函数名、变量名）的信息
- `.strtab`：字符串表，存储符号表中的字符串
- `.debug`：调试信息，用于 GDB 等调试器
- `.got`（Global Offset Table）：全局偏移表，用于在运行时解析外部函数地址
- `.plt`（Procedure Linkage Table）：过程链接表，用于动态链接

节头部表

节头部表是一个描述所有节的数组。每个条目都是一个 `Elf64_Shdr`（对于 64 位）结构体，它包含了每个节的名称、类型、权限、文件偏移、内存地址和大小等信息。链接器和反汇编工具（如 objdump）主要依赖这个表来分析文件结构

执行视图：段

当程序需要被加载到内存中执行时，节会被组合成更大的逻辑单元——段。每个段都具有相同的内存权限（可读、可写、可执行）。这是加载器关心的内容

典型的段有两个：

- 代码段（Code Segment）： 通常包含 `.text` 和 `.rodata` 节。这个段被映射到内存中，并具有可读和可执行权限
- 数据段（Data Segment）： 通常包含 `.data` 和 `.bss` 节。这个段被映射到内存中，并具有可读和可写权限

程序头部表

程序头部表是一个描述所有段的数组。每个条目都是一个 `Elf64_Phdr` 结构体，它包含了每个段的类型、文件偏移、内存地址、大小和权限等信息。加载器通过遍历这个表，将 ELF 文件中的内容映射到内存中

- `p_type`：段的类型，如 `PT_LOAD`（可加载到内存）
- `p_offset`：段在文件中的偏移量
- `p_vaddr`：段在内存中的虚拟地址
- `p_memsz`：段在内存中的大小
- `p_flags`：段的权限，如 `PF_R`（可读）、`PF_W`（可写）、`PF_X`（可执行）

ELF 文件加载过程

1. 操作系统内核的加载器读取 ELF 头部，找到程序头部表
2. 加载器遍历程序头部表中的所有条目
3. 对于每个类型为 `PT_LOAD` 的段，加载器将文件中的相应部分，从 `p_offset` 偏移处开始，映射到内存中的 `p_vaddr` 虚拟地址上
4. 加载器根据 `p_flags` 设置内存页的权限（读、写、执行）
5. 所有段加载完毕后，加载器将程序控制权交给 `e_entry` 字段指定的入口点地址，程序开始执行