本页目录
ELF 文件格式
覆盖: ELF header → section header table → program header table → 手动解析 → readelf/objdump 用法 适用: x86-64 / ARM64 ELF, ET_EXEC / ET_DYN / ET_REL
概述
ELF (Executable and Linkable Format) 是 Linux 上几乎所有二进制文件的标准格式——可执行文件、共享库 (.so)、目标文件 (.o)、core dump 都使用 ELF。理解 ELF 不是学术练习——当你需要排查"为什么这个 .so 加载失败"、"为什么 LD_PRELOAD 不生效"、"为什么 strip 后 coredump 不可分析"时,ELF header 中的细节就是答案。
ELF 三种视图
flowchart LR
subgraph LINK["🔗 Linking View (编译/链接时)"]
L_HDR["ELF Header"]
L_SHDR["Section Header Table"]
L_TEXT[".text (代码)"]
L_RODATA[".rodata"]
L_DATA[".data"]
L_BSS[".bss"]
L_SYMTAB[".symtab"]
L_DYNSYM[".dynsym"]
L_PLT[".plt"]
L_GOT[".got"]
end
subgraph EXEC["▶️ Execution View (运行时)"]
E_HDR["ELF Header"]
E_PHDR["Program Header Table"]
E_LOAD_RX["PT_LOAD<br/>可读+可执行"]
E_LOAD_RW["PT_LOAD<br/>可读+可写"]
E_DYNAMIC["PT_DYNAMIC<br/>动态链接信息"]
E_INTERP["PT_INTERP<br/>ld.so 路径"]
end
L_TEXT -.->|"属于"| E_LOAD_RX
L_RODATA -.->|"属于"| E_LOAD_RX
L_DATA -.->|"属于"| E_LOAD_RW
L_BSS -.->|"属于"| E_LOAD_RW
L_DYNSYM -.->|"属于"| E_DYNAMIC
classDef link fill:#e3f2fd,stroke:#1565c0
classDef exec fill:#fff3e0,stroke:#ef6c00
class LINK link
class EXEC exec
Section 是给链接器看的(按功能组织),Segment 是给内核/动态链接器看的(按权限和加载需求组织)。同一个 .text section 属于一个 PT_LOAD segment。
ELF Header
// /usr/include/elf.h
typedef struct Elf64_Ehdr;
e_ident 魔数
|
# byte 0-3: 0x7f 'E' 'L' 'F' — 魔数
# byte 4: EI_CLASS — 1=32-bit, 2=64-bit
# byte 5: EI_DATA — 1=little-endian, 2=big-endian
# byte 6: EI_VERSION — always 1
# byte 7: EI_OSABI — 0=UNIX System V, 3=GNU/Linux
# byte 8-15: padding
e_type 关键值
ET_EXEC (2): 传统可执行文件,加载到固定地址 (非 PIE)
ET_DYN (3): 共享库或 PIE 可执行文件,加载到随机基址 (现代默认)
ET_REL (1): 可重定位目标文件 (.o),链接器的输入
ET_CORE (4): core dump 文件
现代 Linux (GCC 默认 -pie) 的可执行文件都是 ET_DYN。可用 file /bin/ls 确认。
Section Header Table
每个 section 描述文件中的一个区域——代码、数据、符号表、重定位表等。
typedef struct Elf64_Shdr;
关键 section
| Section | 类型 | 内容 |
|---|---|---|
| .text | SHT_PROGBITS | 可执行代码 |
| .rodata | SHT_PROGBITS | 只读数据 (字符串常量等) |
| .data | SHT_PROGBITS | 已初始化的全局/静态变量 |
| .bss | SHT_NOBITS | 未初始化的全局变量 (占空间但不占文件大小) |
| .plt | SHT_PROGBITS | Procedure Linkage Table (延迟绑定) |
| .got / .got.plt | SHT_PROGBITS | Global Offset Table |
| .dynsym | SHT_DYNSYM | 动态符号表 (运行时需要) |
| .symtab | SHT_SYMTAB | 完整符号表 (strip 后可移除) |
| .dynstr / .strtab | SHT_STRTAB | 字符串表 |
| .rela.dyn / .rela.plt | SHT_RELA | 重定位表 |
| .interp | SHT_PROGBITS | 动态链接器路径 (如 /lib64/ld-linux-x86-64.so.2) |
| .dynamic | SHT_DYNAMIC | 动态链接信息 (DT_NEEDED, DT_SONAME, ...) |
| .init / .fini | SHT_PROGBITS | 初始化/终结代码 (GCC -finit-array) |
Program Header Table
Program header 告诉内核"怎么加载这个文件"。每个 segment (PT_LOAD) 指定一块数据要映射到哪个虚拟地址、什么权限。
typedef struct Elf64_Phdr;
PT_LOAD 映射过程
// 内核 fs/binfmt_elf.c: elf_map()
// 对每个 PT_LOAD segment:
// 1. mmap(p_vaddr, p_memsz, PROT from p_flags, MAP_PRIVATE, fd, p_offset)
// 2. 如果 p_memsz > p_filesz → 剩余字节映射为 0 (anonymous MAP_ANONYMOUS)
// 这就是 .bss 的运行时实现
// 例: /bin/ls 有 2 个 PT_LOAD:
// Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
// LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x002e58 0x002e58 R E 0x1000
// LOAD 0x0000000000003000 0x0000000000403000 0x0000000000403000 0x000fb8 0x001640 RW 0x1000
//
// 第一个 LOAD: 代码+只读数据 → R+X (memsz == filesz, 无 .bss)
// 第二个 LOAD: 数据+.bss → R+W (memsz > filesz, 剩余补齐 .bss)
手动解析
# ELF 头部
# Section headers
# Program headers (segments)
# 动态符号表
# 重定位表
# .dynamic section (DT_NEEDED, DT_RPATH, ...)
# 字符串全文搜索
|
# 反汇编 (disassemble)
|
# 检查是否 PIE
# "ELF 64-bit LSB pie executable" → PIE
# "ELF 64-bit LSB executable" → non-PIE
参考
- 规范: ELF spec (System V ABI, Chapter 4-5),
man 5 elf - 头文件:
/usr/include/elf.h,/usr/include/link.h - 工具:
readelf,objdump,nm,size,strip,patchelf - LWN: "The ELF object file format" 系列
关键词: ELF header, section, segment, PT_LOAD, SHT_SYMTAB, ET_DYN, ET_EXEC, PIE, readelf, objdump