在 ELF(Executable and Linkable Format)文件格式中,PT_LOAD是程序头表中最重要的段类型。以下是对PT_LOAD的定义、具体包含的种类以及与其类似的其他段类型的完整解析。
1. 什么是PT_LOAD?
定义:PT_LOAD表示一个可加载段。当操作系统加载器将 ELF 文件加载到内存中执行时,它会遍历程序头表,寻找所有类型为PT_LOAD的段,并根据其描述信息将文件中的数据映射到进程的虚拟地址空间中。
核心机制:
它是磁盘文件与内存镜像之间的桥梁。PT_LOAD告诉操作系统:
- 从哪里读:文件中的偏移量 (
p_offset)。 - 放到哪里:内存中的虚拟地址 (
p_vaddr)。 - 读多少:文件中的大小 (
p_filesz)。 - 占多大:内存中的大小 (
p_memsz)。- 注意:如果
p_memsz>p_filesz,多余的部分会被填充为 0(这通常用于.bss段,即未初始化的全局变量)。
- 注意:如果
- 权限如何:读 (
PF_R)、写 (PF_W)、执行 (PF_X) 权限。
2. PT_LOAD 包含的具体种类
虽然PT_LOAD在 ELF 头中只是一个常量值(值为 1),但在实际二进制文件中,根据内存权限和包含的具体内容,它逻辑上划分为几种固定的种类。一个标准的 ELF 可执行文件通常包含2 到 3 个PT_LOAD段。
A. 代码段
这是程序中最核心的PT_LOAD段,通常也是第一个被加载的。
- 权限标志:
PF_X | PF_R(可读、可执行),通常不可写。 - 包含的内容:
.text:主要的程序机器指令。.init/.fini:程序初始化和结束时的代码。.plt:动态链接的跳转桩代码。.interp:动态链接器路径字符串(需加载到内存供内核读取)。.rodata:只读数据(如字符串常量),有时会合并在此段中。
- 特点:在内存中,多个进程可以共享同一个物理页(节省内存)。
B. 数据段
这是用于存储程序运行时状态数据的段。
- 权限标志:
PF_W | PF_R(可读、可写),通常不可执行。 - 包含的内容:
.data:已初始化的全局变量和静态变量。.bss:未初始化的全局变量。- 特殊处理:在段描述中,
p_filesz不包含.bss的大小,但p_memsz包含。加载器会将多余的内存空间清零。
- 特殊处理:在段描述中,
.got:全局偏移表。.dynamic:动态链接信息(物理数据存储于此)。
- 特点:这是进程私有的,每个进程运行时都有独立的副本,数据会在运行中发生修改。
C. 只读数据段
现代链接器为了安全性和性能优化,有时会将只读数据从代码段中剥离出来,单独作为一个PT_LOAD段。
- 权限标志:
PF_R(仅可读),不可写,不可执行。 - 包含的内容:
.rodata:字符串常量、虚函数表。.eh_frame_hdr:异常处理信息。
- 存在的意义:增强安全性,防止代码执行攻击。
映射关系示意表:
| PT_LOAD 种类 | 典型包含的 Section (节) | 典型权限 | 说明 |
|---|---|---|---|
| LOAD #1 (代码) | .text,.init,.plt,.rodata | R-X | 最基础的指令段。 |
| LOAD #2 (数据) | .data,.bss,.got,.dynamic | RW- | 可读写的数据段。 |
| LOAD #3 (只读数据) | .rodata,.note | R-- | (可选) 现代安全加固模式下常独立出来。 |
3. 类似的定义有哪些?(程序头类型对比)
在 ELF 标准中,除了PT_LOAD,还有多种段类型。它们虽然也是“段”,但大多数不直接加载用户代码数据,而是提供元数据或特殊功能。
A. 核心标准类型
| 类型名称 | 含义 | 与PT_LOAD的关系/区别 |
|---|---|---|
PT_NULL | 未使用/忽略 | 程序头数组的第一个条目通常作为占位符使用。 |
PT_DYNAMIC | 动态链接信息 | 指向动态链接段(包含.dynamic节)。它告诉动态链接器需要哪些共享库。它通常位于一个PT_LOAD段内部。 |
PT_INTERP | 程序解释器 | 指定要使用的动态链接器路径(如/lib64/ld-linux-x86-64.so.2)。它本身不加载代码,而是告诉系统“先运行这个解释器来加载我”。 |
PT_NOTE | 辅助信息 | 用于存储构建信息、ABI 版本等元数据。加载器通常忽略其内容。 |
PT_PHDR | 程序头表自身 | 指定程序头表在内存中的位置和大小。 |
PT_TLS | 线程局部存储 | 定义线程局部存储的模板。每个线程运行时都会根据此段拥有一份独立的数据副本。 |
B. 特定于架构或扩展的类型 (常见于 Linux)
| 类型名称 | 含义 | 作用 |
|---|---|---|
PT_GNU_EH_FRAME | 异常处理帧 | 用于 C++ 异常处理,帮助运行时快速查找异常处理代码。 |
PT_GNU_STACK | 栈权限控制 | 非常重要。它不加载任何数据,而是用来控制栈内存的权限(是否可执行)。如果权限不可执行,则开启NX保护,防止缓冲区溢出攻击。 |
PT_GNU_RELRO | 只读重定位 | 用于安全加固。指示加载器在完成重定位后,将特定的内存区域(如 GOT 表)设置为只读,防止运行时被篡改。 |
4. 总结
可以将 ELF 加载过程类比为搬运和 setup 一个房间:
PT_LOAD:是**“货物”**(集装箱里的东西,如家具、电器)。这是唯一真正占用进程物理/虚拟内存空间、包含实际指令和数据的段。PT_DYNAMIC/PT_INTERP:是**“说明书”或“搬运工信息”**(告诉系统怎么组装、需要谁帮忙)。PT_GNU_STACK/PT_GNU_RELRO:是**“安全守则”**(规定哪些地方不能放东西、哪些地方要上锁)。
操作系统加载器的核心逻辑就是:遍历程序头表,把所有PT_LOAD段映射到内存,然后根据PT_INTERP调用动态链接器处理依赖,最后跳转执行。