### 讲讲 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` 字段指定的入口点地址，程序开始执行