用libelf读取elf文件头和程序头表最稳妥:先调用elf_version(ev_current)初始化,再以o_rdonly打开文件并用elf_begin(fd, elf_c_read, null)获取句柄,确认elf_kind(elf)==elf_k_elf后,调用elf64_getehdr()和gelf_getphdr()安全解析。

用 libelf 读取 ELF 文件头和程序头表最稳妥
直接 mmap 或 fread 原始字节去解析 ELF 是可行的,但极易出错:32/64 位结构体对齐不同、字节序混淆、e_ident 校验遗漏、e_phoff/e_shoff 为 0 的合法情况被当成错误……用成熟库才是工程常态。libelf(来自 elfutils)封装了这些细节,且支持只读模式,不修改文件。
关键步骤:
- 调用
elf_version(EV_CURRENT)初始化版本,否则后续所有elf_*调用返回NULL -
elf_begin(fd, ELF_C_READ, NULL)获取Elf *句柄,fd必须是O_RDONLY打开的 - 用
elf_kind(elf)确认是ELF_K_ELF,避免把 core dump 或 archive 当成可执行文件 -
elf64_getehdr()或elf32_getehdr()拿到Ehdr *,再用gelf_getphdr()遍历程序头表(PT_LOAD,PT_INTERP等)
注意:libelf 不自动处理压缩段(如 ZLIB 压缩的 .zdebug),也不解析符号表或重定位项——那是 libdw 的事。
手动解析 e_ident 和基础字段必须校验字节序与 class
如果真要手撸解析(比如嵌入式无 libc 环境),e_ident 是唯一能绕过字节序判断的入口。前 4 字节固定为 \x7fELF,第 5 字节 e_ident[EI_CLASS] 决定后续结构体尺寸:ELFCLASS32(1)还是 ELFCLASS64(2);第 6 字节 e_ident[EI_DATA] 决定字节序:ELFDATA2LSB(1)或 ELFDATA2MSB(2)。
常见翻车点:
- 没检查
e_ident[EI_VERSION]是否为EV_CURRENT(值 1),旧版 ELF 可能被误读 - 把
e_type当作小端读取,但实际它紧跟在e_ident后,已受EI_DATA控制,需用ntohs()/ntohl()转换 -
e_phnum为 0 时,程序头表不存在(如某些 relocatable object),不能硬算偏移
示例:读 e_entry(入口地址)
uint64_t entry;
if (ei_class == ELFCLASS64) {
entry = be64toh(*(uint64_t*)(buf + offsetof(Elf64_Ehdr, e_entry)));
} else {
entry = be32toh(*(uint32_t*)(buf + offsetof(Elf32_Ehdr, e_entry)));
}
readelf -h 输出里哪些字段对应程序实际加载行为
readelf -h 显示的是 ELF 头静态信息,但真正影响 loader 行为的是程序头表(readelf -l)。比如:
-
Entry point address(e_entry)只是提示,内核实际跳转地址可能被 linker 覆盖(如--entry参数)或被 interpreter(如/lib64/ld-linux-x86-64.so.2)接管 -
Flags中的0x0000000000000004(EF_AMD64_ABIMODE)仅用于 ABI 兼容性标记,不影响加载 -
OS/ABI字段(e_ident[EI_OSABI])决定系统调用约定,但现代 Linux 内核只严格检查ELFOSABI_LINUX和ELFOSABI_SYSV,其余常被忽略
真正关键的是 PT_INTERP 段路径(决定用哪个动态链接器)和所有 PT_LOAD 段的 p_vaddr/p_memsz —— 它们被内核 mmap 到虚拟地址空间,顺序和权限(p_flags & PF_R/W/X)直接影响段是否可执行、是否可写。
解析符号表时别漏掉 .dynsym 和 .symtab 的分工
动态链接依赖 .dynsym(通常在 DT_SYMTAB 动态段指向),它体积小、只含全局符号,loader 必须读取;而 .symtab 是全量符号表,用于调试和链接,运行时不加载,strip 后即消失。
手撸解析时容易踩的坑:
- 直接遍历
.symtab段内容却忘了它由sh_link(指向.strtab)和sh_info(本地符号数)控制,不是简单按sizeof(Elf64_Sym)切分 - 没区分
st_bind(STB_GLOBAL/STB_LOCAL)和st_shndx(SHN_UNDEF表示未定义符号),导致把外部引用当成本地函数 - 用
st_name去.strtab查字符串时,没确认该 strtab 是.dynstr还是.strtab,两者内容不重叠
一个快速验证方法:objdump -tT binary 中带 *UND* 的是 .dynsym 里的未定义符号,带 file.o 的是 .symtab 里的本地符号。
ELF 解析最麻烦的从来不是结构体定义,而是各种“合法但罕见”的组合:no program header + dynamic section、non-contiguous load segments、non-SHF_ALLOC sections with non-zero sh_addr……别迷信文档,多用 readelf -a 对比真实二进制,尤其关注 Section Headers 和 Program Headers 两部分的 offset/size/vaddr/paddr 是否自洽。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











