---
title: ELF 文件格式
url: https://doc.liz6.com/systems-programming/01-elf-and-binary-formats/01-elf-file-format
locale: zh
area: systems-programming
tags:
- systems-programming
- ELF与二进制格式
date: 2026-06-30
modified: 2026-06-30
description: '覆盖: ELF header → section header table → program header table → 手动解析 → readelf/objdump 用法 适用: x86-64 / ARM64 ELF, ET_EXEC / ET_DYN / ET_REL'
---

# 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 三种视图

```mermaid
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

```c
// /usr/include/elf.h
typedef struct {
    unsigned char e_ident[16];   // Magic + Class + Data + Version + ABI
    uint16_t      e_type;        // ET_EXEC, ET_DYN (PIE/.so), ET_REL (.o), ET_CORE
    uint16_t      e_machine;     // EM_X86_64, EM_AARCH64, EM_RISCV
    uint32_t      e_version;     // EV_CURRENT (1)
    uint64_t      e_entry;       // 入口地址 (_start)
    uint64_t      e_phoff;       // Program Header Table offset
    uint64_t      e_shoff;       // Section Header Table offset
    uint32_t      e_flags;
    uint16_t      e_ehsize;      // ELF header size (64 bytes on x86_64)
    uint16_t      e_phentsize;   // 每个 program header 的大小
    uint16_t      e_phnum;       // program header 的数量
    uint16_t      e_shentsize;   // 每个 section header 的大小
    uint16_t      e_shnum;       // section header 的数量
    uint16_t      e_shstrndx;    // section name string table 的索引
} Elf64_Ehdr;
```

### e_ident 魔数

```bash
$ xxd /bin/ls | head -1
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............

# 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 描述文件中的一个区域——代码、数据、符号表、重定位表等。

```c
typedef struct {
    uint32_t   sh_name;       // section 名字在 .shstrtab 中的偏移
    uint32_t   sh_type;       // SHT_PROGBITS, SHT_SYMTAB, SHT_STRTAB, SHT_RELA, ...
    uint64_t   sh_flags;      // SHF_WRITE, SHF_ALLOC, SHF_EXECINSTR, ...
    uint64_t   sh_addr;       // 加载到内存后的虚拟地址 (可执行文件中)
    uint64_t   sh_offset;     // 在文件中的偏移
    uint64_t   sh_size;       // section 大小
    uint32_t   sh_link;       // 关联的 section 索引 (如 symtab 关联 strtab)
    uint32_t   sh_info;       // 额外信息
    uint64_t   sh_addralign;  // 对齐
    uint64_t   sh_entsize;    // 如果是表，每个 entry 的大小
} 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) 指定一块数据要映射到哪个虚拟地址、什么权限。

```c
typedef struct {
    uint32_t   p_type;    // PT_LOAD, PT_DYNAMIC, PT_INTERP, PT_NOTE, PT_GNU_STACK, ...
    uint32_t   p_flags;   // PF_R(4) | PF_W(2) | PF_X(1)
    uint64_t   p_offset;  // segment 在文件中的偏移
    uint64_t   p_vaddr;   // 虚拟地址 (ASLR 会加上随机基址)
    uint64_t   p_paddr;   // 物理地址 (无关，只用于没有 MMU 的系统)
    uint64_t   p_filesz;  // 文件中的大小
    uint64_t   p_memsz;   // 内存中的大小 (memsz > filesz → 剩余补零，用于 .bss)
    uint64_t   p_align;   // 对齐
} Elf64_Phdr;
```

### PT_LOAD 映射过程

```c
// 内核 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)
```

---

## 手动解析

```bash
# ELF 头部
readelf -h /bin/ls

# Section headers
readelf -S /bin/ls

# Program headers (segments)
readelf -l /bin/ls

# 动态符号表
readelf --dyn-syms /bin/ls

# 重定位表
readelf -r /bin/ls

# .dynamic section (DT_NEEDED, DT_RPATH, ...)
readelf -d /bin/ls

# 字符串全文搜索
strings /bin/ls | head -20

# 反汇编 (disassemble)
objdump -d /bin/ls | head

# 检查是否 PIE
file /bin/ls
# "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*
