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