ELF
ELF 文件核心概念
ELF (Executable and Linkable Format) 是类 Unix 系统的标准二进制格式,包含:
- 核心作用:定义程序如何被操作系统加载到内存、如何执行、如何动态链接依赖库,以及存储调试信息。
- 适用场景
- 可执行文件(如 Linux 的
/bin/bash
) - 动态链接库(如
.so
、.dll
) - 编译中间文件(如
.o
目标文件) - Core Dump(程序崩溃时的内存快照)
- 可执行文件(如 Linux 的
ELF(Executable and Linkable Format)是一种跨平台、跨操作系统的标准二进制文件格式,用于描述可执行程序、共享库、目标文件等。
ELF(Executable and Linkable Format)是 可执行文件、库文件、目标文件 的标准格式。
通俗理解:
- 像一本书的目录:告诉操作系统和硬件如何加载、运行程序。
- 跨平台兼容:RISC-V、x86、ARM 等架构都有自己的 ELF 文件变体。
- 统一规范:链接器、加载器、调试工具都依赖 ELF 的结构解析程序。
ELF 文件的核心内容(三大部分)
ELF 头
ELF 头(ELF Header)
- 作用:文件的全局描述符,标识文件类型和架构信息。
- 关键字段
- Magic Number:前4字节固定为
7F 45 4C 46
(ASCII为\x7FELF
),表明这是一个 ELF 文件。 - 文件类型:
e_type
标识是可执行文件(ET_EXEC
)、共享库(ET_DYN
)还是目标文件(ET_REL
)。 - 目标架构:
e_machine
字段指定 CPU 架构(如EM_X86_64
、EM_ARM
、EM_RISCV
)。 - 入口地址:
e_entry
是程序开始执行的地址(如_start
函数)。 - 程序头表/节头表位置:指向段(Segment)和节(Section)的元数据表。
- 其他信息:32位/64位、大端/小端等。
- Magic Number:前4字节固定为
节
节(Sections)
- 作用:存储程序的代码、数据、调试信息等,主要用于编译和链接阶段。
- 常见节及其作用:
节名 | 作用 |
---|---|
.text |
存放 RISC-V 指令代码(如函数、跳转指令)。 |
.data |
已初始化的全局变量(如int a=10; )。 |
.rodata |
只读数据(如字符串常量"Hello" )。 |
.bss |
未初始化的全局变量(运行时置零)。 |
.symtab |
符号表(记录函数、变量名和地址的对应关系)。 |
.strtab |
字符串表(存储符号名称、节名称等字符串)。 |
.riscv.attributes |
RISC-V 特有:记录支持的指令集扩展(如 C 扩展、浮点指令等)。 |
.comment |
编译器/工具链的版本信息(如GCC: (x.x.x) )。 |
.rela.text |
重定位表:记录 .text 节中需要重定位的指令(用于链接阶段)。 |
.debug_* |
调试信息:DWARF 格式的源码行号、变量类型等(如 .debug_info )。 |
段
段(Segments / Program Headers)
- 作用:告诉操作系统如何将程序加载到内存,用于程序执行阶段。
- 关键段类型:
段类型 | 作用 |
---|---|
PT_LOAD |
需要加载到内存的段(如代码段.text 、数据段.data )。 |
PT_DYNAMIC |
动态链接信息(如依赖的共享库.so )。 |
PT_INTERP |
动态链接器的路径(如/lib/ld-linux-riscv64-lp64d.so.1 )。 |
PT_GNU_STACK |
栈的可执行权限(决定是否允许栈上执行代码,防范安全漏洞)。 |
查看 ELF 文件信息
- 文件类型查看
readelf
:查看 ELF 结构
readelf -h program.elf # 查看 ELF 头
readelf -S program.elf # 查看所有节(Sections)
readelf -l program.elf # 查看所有段(Segments)
readelf -s program.elf # 查看符号表(Symbols)
readelf -a program.elf # 查看所有信息(头、节、段、符号表等)
objdump
:反汇编与节内容查看
ELF 文件的生命周期
- 编译阶段
- 编译器将源码编译为目标文件(
.o
),生成.text
、.data
等节,但未解决外部符号引用。
- 编译器将源码编译为目标文件(
- 链接阶段
- 链接器合并多个目标文件,解析符号引用,生成可执行文件或共享库。
- 重定位表(
.rela.text
)指导链接器修正代码中的地址偏移。
- 加载阶段
- 操作系统读取
PT_LOAD
段,将代码和数据加载到内存指定地址。 - 动态链接器(
ld.so
)处理PT_DYNAMIC
段,加载依赖的共享库。
- 操作系统读取
- 执行阶段
- CPU 从
e_entry
地址开始执行指令,程序启动。
- CPU 从
调试信息
DWARF 格式:ELF 文件中通过 .debug_*
节存储调试信息,包括:
.debug_info
:源码文件、函数、变量类型。.debug_line
:机器指令与源码行号的映射。.debug_frame
:栈帧布局(用于回溯调用栈)。
调试工具:
gdb
:加载 ELF 文件后,可结合调试信息进行源码级调试。addr2line
:将内存地址转换为源码位置。
剥离调试信息:
重定位
重定位(Relocation)
- 作用:解决跨文件符号引用问题。
- 示例:
- 在目标文件
a.o
中调用另一个目标文件b.o
的函数foo()
,编译时a.o
无法确定foo()
的地址。 - 链接阶段,链接器根据重定位表将
call foo
的地址修正为实际地址。
- 在目标文件
常见重定位类型:
R_X86_64_PC32
(x86-64 相对地址调用)R_AARCH64_CALL26
(ARM64 函数调用)R_386_32
(x86 绝对地址引用)
动态链接
-
核心机制:
- 动态链接库(Shared Library):代码在运行时加载,多个程序可共享同一份库代码。
- 全局偏移表(GOT):存储外部符号(如库函数)的实际地址。
- 过程链接表(PLT):实现延迟绑定(Lazy Binding),首次调用函数时通过 GOT 解析地址。
-
工具支持:
ldd program
:查看程序依赖的共享库。LD_PRELOAD
环境变量:预加载自定义库(用于调试或劫持函数)。