This commit is contained in:
author
2025-11-08 21:00:06 +08:00
parent 95a4182500
commit fc176f1b7d
1793 changed files with 2099698 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
### 讲讲 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` 字段指定的入口点地址,程序开始执行