CFN Cloud
Cloud Future New Life
en zh
2026-01-09 · 0 次浏览

ELF 文件简介:从 Section 到 Segment

用结构、示例与工具把 ELF 的类型、布局、重定位和动态链接串起来。

ELF(Executable and Linkable Format)是类 Unix 系统中最常见的目标文件与可执行文件格式。理解它的结构,你就能把“编译—链接—加载—运行”的关键路径串起来。

ELF 的三种类型

  • 可重定位文件(Relocatable):编译器/汇编器输出的目标文件(.o),等待链接器合并与修正地址。
  • 可执行文件(Executable):可直接被加载并执行的程序。
  • 共享库(Shared Object):运行时动态链接的库(.so)。

从源代码到运行:一条清晰的链路

源代码/汇编
  ↓ 编译/汇编
目标文件(.o,含 Section)
  ↓ 链接
可执行文件(ELF,含 Segment)
  ↓ 加载器
映射到内存并执行

两种视角:Section vs Segment

ELF 提供两套视角:

  • 链接器视角:ELF 是一组 Section,保存代码、数据、符号、重定位信息等。
  • 加载器视角:ELF 是一组 Segment,描述需要映射到内存的区域以及权限(R/W/X)。

一个简化的对应关系示意:

Section 视角(链接器)               Segment 视角(加载器)
[ELF Header]                        [ELF Header]
[Section Header Table]              [Program Header Table]
  .text                               LOAD (R-X) <- .text
  .data                               LOAD (RW-) <- .data + .bss
  .bss
  .symtab/.strtab
  .rel.*

目标文件示例:ELF Header + Section 表

下面是一个 目标文件(.o)readelf -h 输出节选:

ELF Header:
  Class:                             ELF64
  Data:                              2's complement, little endian
  Type:                              REL (Relocatable file)
  Machine:                           Advanced Micro Devices X86-64
  Entry point address:               0x0
  Start of section headers:          0x2c0 (bytes into file)
  Number of section headers:         12

readelf -S 的 Section 表节选(仅示意关键列):

[Nr] Name      Type      Addr   Off    Size   ES Flg Lk Inf Al
[ 1] .text     PROGBITS  0000   0040   0038   00 AX  0  0  16
[ 2] .rel.text REL       0000   0210   0018   08     6  1  8
[ 3] .data     PROGBITS  0000   0080   0020   00 WA  0  0  8
[ 4] .bss      NOBITS    0000   00a0   0010   00 WA  0  0  8
[ 5] .symtab   SYMTAB    0000   0240   00f0   18     6  8  8
[ 6] .strtab   STRTAB    0000   0330   0048   00     0  0  1

字段要点:

  • Addr:加载地址(目标文件里常为 0,待链接修正)。
  • Off/Size:在文件中的偏移与大小。
  • Flg:权限标记(A=可分配,X=可执行,W=可写)。

目标文件布局示意

0x0000  ELF Header
0x0040  .text
0x0080  .data
0x00a0  .bss(文件中不占空间)
0x0210  .rel.text
0x0240  .symtab
0x0330  .strtab
0x02c0  Section Header Table

可执行文件示例:Program Header 与 Segment

链接完成后,可执行文件会出现 Program Header(Segment 表):

Program Headers:
  Type   Offset  VirtAddr  FileSiz MemSiz  Flg Align
  LOAD   0x0000  0x400000  0x0800  0x0800  R E 0x1000
  LOAD   0x1000  0x601000  0x0200  0x0300  RW  0x1000

解释:

  • 第一段 LOAD:包含 .text,权限 R-X
  • 第二段 LOAD:包含 .data + .bss,权限 RW-
  • MemSiz > FileSiz 通常说明 .bss 仅占内存而不占文件空间。

可用 readelf -l 查看 Section to Segment mapping,理解“Section 合并成 Segment”的过程。

重定位:把“占位地址”改成“真实地址”

目标文件中常见“占位地址”,链接器根据 .rel.* 修正它们。

一个简化示意(伪汇编):

mov    data_items(%rip), %rax   ; 访问全局数组

在目标文件里,编码可能是占位地址:

8b 04 bd 00 00 00 00

链接后被改成真实地址:

8b 04 bd a0 90 04 08

对应的重定位条目(节选):

Relocation section '.rel.text' contains 1 entry:
  Offset  Info   Type       Sym.Name
  0x0008  ...    R_X86_64_32 data_items

核心点:链接器根据重定位表,在特定偏移修正指令或数据。

共享库与 PIC / GOT / PLT

共享库需要在任意地址加载,通常使用 PIC(位置无关代码):

  • GOT(Global Offset Table):保存变量/函数的真实地址。
  • PLT(Procedure Linkage Table):函数调用跳板,用于延迟绑定。

一个常见的 PLT 入口(简化示意):

push@plt:
  jmp *GOT[push]
  pushq $reloc_index
  jmp plt0

第一次调用会进入动态链接器;后续调用直接通过 GOT 跳转到真实地址。

动态链接流程(高层)

  1. 动态链接器加载依赖库。
  2. 首次调用外部符号,PLT 触发解析。
  3. 解析结果写入 GOT。
  4. 后续调用直接跳转,减少开销。

常用工具清单

# ELF 头、节、段
readelf -h a.out
readelf -S a.out
readelf -l a.out

# 反汇编、符号
objdump -d a.out
objdump -t a.out
nm -n a.out

# 体积、依赖、字符串
size a.out
ldd a.out
strings a.out

小结

理解 ELF 的关键在于:链接器关心 Section,加载器关心 Segment
目标文件强调“可链接”,可执行文件强调“可加载”,共享库强调“可重定位与动态链接”。

FAQ

Q1:为什么可执行文件还保留 Section Header Table?
A:加载器不需要,但调试与分析工具需要它来理解符号与结构。

Q2:.bss 为什么不占文件空间?
A:它只记录大小,加载时分配内存并清零即可。

Q3:目标文件里很多地址为什么是 0?
A:它们是占位地址,等待链接器根据重定位表修正。

Q4:共享库为什么要用 PIC?
A:共享库要在不同进程、不同地址加载,PIC 避免绝对地址写死。

Q5:为什么 Segment 权限以页为单位?
A:MMU 以页为最小保护单位,代码与数据通常分离到不同页。

参考链接