ELF
ELF 文件概述
ELF(Executable and Linkable Format)是可执行与可链接格式的简称,是Unix-like系统中的标准二进制文件格式。其主要特点包括跨平台性、可扩展性和模块化设计。ELF文件分为四大类型:
- 可执行文件(.out或无扩展名):可直接被操作系统加载执行
- 共享目标文件(.so):作为动态链接库在运行时加载
- 可重定位文件(.o):包含代码和数据的中间文件,用于静态链接
- 核心转储文件(.core):程序异常终止时生成的内存状态快照
源代码
│
├── 编译 (gcc -c) → 目标文件 (.o) → 静态链接 (ar) → 静态库 (.a)
│
├── 编译为PIC (gcc -fPIC -c) → 目标文件 (.o) → 动态链接 (gcc -shared) → 共享库 (.so)
│
└── 直接编译链接 (gcc -o) → 可执行文件 (a.out) → 运行时崩溃 → 核心转储 (.core)
ELF 文件结构图
ELF 文件结构:
+--------------------------------+
| ELF 头 | → 魔数、架构、入口点、头表偏移
+--------------------------------+
| 程序头表 | → 描述如何创建进程映像(执行视图)
+--------------------------------+
| 节头表 | → 描述文件各个节的信息(链接视图)
+--------------------------------+
| .text | → 代码段(指令)
+--------------------------------+
| .data | → 已初始化数据
+--------------------------------+
| .bss | → 未初始化数据(文件不占空间)
+--------------------------------+
| .rodata | → 只读数据
+--------------------------------+
| 动态链接信息 | → .dynamic、.dynsym、.dynstr
+--------------------------------+
| 符号表信息 | → .symtab、.strtab
+--------------------------------+
| 重定位信息 | → .rel.text、.rel.data
+--------------------------------+
- 程序头表在需要加载执行的ELF文件中必须存在,在仅用于链接的目标文件中不存在。
- 动态链接节:只在需要动态链接的文件中存在
- 符号表节:在需要调试或链接的文件中存在,发布时可移除
- 重定位节:在未完全链接的文件中存在,最终可执行文件中通常不存在
ELF 文件分析命令
基本信息查看
readelf -h file.elf # 查看ELF头信息
readelf -l file.elf # 查看程序头表
readelf -S file.elf # 查看节头表
file file.elf # 快速识别文件类型
符号和重定位信息
readelf -s file.elf # 查看符号表
readelf -r file.elf # 查看重定位信息
readelf -d file.elf # 查看动态段信息
nm file.elf # 查看符号表(简版)
反汇编和内容查看
objdump -d file.elf # 反汇编代码段
objdump -s file.elf # 显示所有节内容
objdump -x file.elf # 显示所有头信息
objdump -t file.elf # 显示符号表
动态链接信息
Flag 含义表及组合说明
单标志含义
标志 | 全称 | 含义 | 内存权限 |
---|---|---|---|
A | ALLOC | 该节在程序运行时需要被加载到内存中 | 依赖其他标志 |
X | EXECUTE | 该节包含可执行的机器指令 | 读 + 执行 |
W | WRITE | 该节在内存中可写 | 读 + 写 |
M | MERGE | 该节内容可以被合并 | 通常不加载 |
S | STRINGS | 该节包含null终止的字符串 | 通常不加载 |
空 | - | 无特殊标志 | 不加载到内存 |
常见标志组合
组合 | 含义 | 典型节 | 内存权限 | 工具推荐 |
---|---|---|---|---|
AX | 可分配 + 可执行 | .text, .init | 读 + 执行 | objdump -d |
WA | 可写 + 可分配 | .data, .bss | 读 + 写 | objdump -s |
A | 可分配(只读) | .rodata, .eh_frame | 只读 | objdump -s |
MS | 可合并 + 字符串 | .comment | 不加载 | readelf -p |
空 | 元数据信息 | .symtab, .strtab | 不加载 | readelf |
工具使用对比表
各节适合的分析工具
节名称 | 类型 | 标志 | readelf 查看方法 | objdump 查看方法 | 推荐工具 |
---|---|---|---|---|---|
.text | PROGBITS | AX | readelf -x .text | objdump -d -j .text | objdump |
.data | PROGBITS | WA | readelf -x .data | objdump -s -j .data | objdump |
.rodata | PROGBITS | A | readelf -x .rodata | objdump -s -j .rodata | objdump |
.bss | NOBITS | WA | readelf -S (仅信息) | objdump -h (仅信息) | readelf |
.eh_frame | PROGBITS | A | readelf -wf .eh_frame | objdump -s -j .eh_frame | objdump |
.symtab | SYMTAB | 空 | readelf -s | objdump -t | readelf |
.strtab | STRTAB | 空 | readelf -p .strtab | objdump -s -j .strtab | readelf |
.shstrtab | STRTAB | 空 | readelf -p .shstrtab | objdump -s -j .shstrtab | readelf |
.comment | PROGBITS | MS | readelf -p .comment | objdump -s -j .comment | readelf |
.dynamic | DYNAMIC | WA | readelf -d | objdump -s -j .dynamic | readelf |
.init_array | INIT_ARRAY | WA | readelf -x .init_array | objdump -s -j .init_array | objdump |
工具功能对比
功能 | readelf | objdump | 优势工具 |
---|---|---|---|
查看符号表 | ✅ readelf -s | ✅ objdump -t | readelf(格式更清晰) |
反汇编代码 | ❌ 不支持 | ✅ objdump -d | objdump |
查看节内容 | ✅ readelf -x | ✅ objdump -s | objdump(十六进制显示) |
查看字符串 | ✅ readelf -p | ⚠️ objdump -s | readelf(直接显示字符串) |
查看动态段 | ✅ readelf -d | ✅ objdump -s | readelf(解析结构) |
查看节头 | ✅ readelf -S | ✅ objdump -h | readelf(信息更全) |
查看程序头 | ✅ readelf -l | ✅ objdump -x | readelf |
实用命令参考
readelf 指定节查看方法
# 查看符号表
readelf -s a.out
# 查看特定节的内容(十六进制)
readelf -x .text a.out
# 查看字符串节的内容
readelf -p .strtab a.out
readelf -p .comment a.out
# 查看动态链接信息
readelf -d a.out
# 查看架构特定属性
readelf -A a.out
objdump 指定节查看方法
# 反汇编代码节
objdump -d -j .text a.out
# 查看数据节内容(十六进制)
objdump -s -j .data a.out
# 查看符号表(简化版)
objdump -t a.out
# 查看特定地址范围
objdump -s --start-address=0x1000 --stop-address=0x2000 a.out
选择指南
如何选择工具?
- 要看代码反汇编 → 用
objdump -d
- 要看符号信息 → 用
readelf -s
- 要看数据内容 → 用
objdump -s
或readelf -x
- 要看字符串内容 → 用
readelf -p
- 要看结构信息 → 用
readelf
各种选项 - 要看原始字节 → 用
objdump -s
记忆技巧
readelf
:适合看结构和文本信息- 符号表(-s)
- 字符串内容(-p)
- 动态信息(-d)
-
架构属性(-A)
-
objdump
:适合看内容和代码 - 反汇编(-d)
- 十六进制内容(-s)
- 显示文件头信息(-h)
通用系统下ELF执行流程(虚拟地址环境)
编译链接阶段
执行流程
-
硬盘存储:ELF文件存储在磁盘上,包含完整的程序代码、静态链接的库代码,以及动态符号引用信息
-
内存加载:
- 操作系统解析ELF头,验证文件有效性
- 读取程序头表,创建进程虚拟地址空间
- 将LOAD段映射到虚拟内存:
- 代码段(.text, .rodata):映射为只读、可执行权限
- 数据段(.data, .bss):映射为读写权限,.bss段初始化为零
-
动态链接处理:
- 动态链接器(ld.so)加载依赖的共享库到进程地址空间
- 解析动态符号,遍历重定位表(.rel.dyn, .rel.plt)
- 更新全局偏移表(GOT)和过程链接表(PLT):
- GOT表存储实际函数地址,位于可读写的数据段
- PLT提供跳转桩代码,位于只读的代码段
- 采用延迟绑定策略:第一次调用函数时才进行符号解析
-
执行阶段:
- 从入口点(_start)开始执行程序
- 函数调用通过PLT→GOT间接跳转到实际函数地址
- 动态库函数调用通过更新后的GOT表正确跳转
-
错误处理:
- 程序异常终止时,内核生成核心转储文件
- core文件包含崩溃时的内存状态、寄存器值和堆栈信息
- 用于事后调试和分析程序故障
示例
-
准备静态库
-
准备动态库
-
编译主程序并链接库
-
完整流程
裸机系统下ELF执行流程(物理地址环境)
编译链接阶段
链接脚本示例
MEMORY {
ROM (rx) : ORIGIN = 0x80000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
ENTRY(Reset_Handler) # 指定入口点
SECTIONS {
.text : {
*(.vectors) # 中断向量表必须放在起始位置
*(.text*)
} > ROM
.data : { *(.data*) } > RAM AT > ROM
.bss : { *(.bss*) } > RAM
}
执行流程
-
固化存储:ELF文件通过编程器烧录到ROM/Flash的指定物理地址
-
启动加载:
- 系统上电,CPU从复位向量地址(通常为0x80000000)开始执行
- Bootloader或硬件直接从物理地址加载程序
- 代码段在ROM中直接执行,数据段复制到RAM
-
地址处理:
- 所有地址在链接时已确定为物理地址
- 无运行时重定位,无地址转换
- 中断向量表必须放置在硬件规定的固定地址
-
执行阶段:
- 直接从指定的入口点(Reset_Handler)开始执行
- 所有函数调用为直接跳转,无间接寻址
- 外设寄存器通过固定的物理地址直接访问
-
特点:
- 无进程概念,无虚拟内存保护
- 无动态链接,所有符号在链接时解析
- 执行效率高,确定性好,但灵活性差
总结
ELF文件作为现代计算系统中的核心二进制格式,提供了从编译链接到运行时执行的完整解决方案。在通用系统中,ELF通过虚拟内存管理和动态链接机制实现了灵活的进程隔离和资源共享;在嵌入式裸机系统中,ELF通过物理地址直接映射提供了确定性的硬件访问能力。
两种环境下的主要区别在于地址管理方式:虚拟地址系统提供安全隔离和动态扩展能力,物理地址系统提供直接硬件访问和执行确定性。理解ELF文件的结构和处理流程,对于系统编程、嵌入式开发和性能优化都具有重要意义。