ELF,汇编,符号与重定向表
发布于 • 作者: Ethan
c程序:
#include <stdio.h>
int g_init = 10; // 已初始化全局变量
int g_uninit; // 未初始化全局变量(BSS)
const char *msg = "Hi"; // 只读数据
extern int ext_func(int);
static int helper(int x) {
return x + g_init;
}
int main() {
g_uninit = helper(3);
printf("%s %d\n", msg, ext_func(g_uninit));
return 0;
}
汇编(RISC-V 32bits gcc 15.2.0):
g_init:
.word 10
g_uninit:
.zero 4
.LC0:
.string "Hi"
msg:
.word .LC0
helper:
addi sp,sp,-32
sw ra,28(sp)
sw s0,24(sp)
addi s0,sp,32
sw a0,-20(s0)
lui a5,%hi(g_init)
lw a4,%lo(g_init)(a5)
lw a5,-20(s0)
add a5,a4,a5
mv a0,a5
lw ra,28(sp)
lw s0,24(sp)
addi sp,sp,32
jr ra
.LC1:
.string "%s %d\n"
main:
addi sp,sp,-16
sw ra,12(sp)
sw s0,8(sp)
sw s1,4(sp)
addi s0,sp,16
li a0,3
call helper
mv a4,a0
lui a5,%hi(g_uninit)
sw a4,%lo(g_uninit)(a5)
lui a5,%hi(msg)
lw s1,%lo(msg)(a5)
lui a5,%hi(g_uninit)
lw a5,%lo(g_uninit)(a5)
mv a0,a5
call ext_func
mv a5,a0
mv a2,a5
mv a1,s1
lui a5,%hi(.LC1)
addi a0,a5,%lo(.LC1)
call printf
li a5,0
mv a0,a5
lw ra,12(sp)
lw s0,8(sp)
lw s1,4(sp)
addi sp,sp,16
jr ra

ELF 同时服务于 两类工具,因此有两套组织方式:
| 视角 | 给谁看 | 核心结构 |
|---|---|---|
| Section 视角 | 编译器 / 链接器 | Section Header Table |
| Segment 视角 | Loader / OS | Program Header Table |
所以:
Section 是“链接期”的逻辑单位,Segment 是“运行期”的装载单位
下面逐个分析代码中的对象 分别落在哪些 Section 中。
.data —— 已初始化的可写数据int g_init = 10;
汇编:
g_init:
.word 10
特点:
所以放入 .data
.bss —— 未初始化的全局/静态数据int g_uninit;
汇编:
g_uninit:
.zero 4
关键点:
.bss 不在 ELF 文件中占空间所以放入 .bss
.rodata —— 只读常量数据const char *msg = "Hi";
拆解成两部分:
.LC0:
.string "Hi" // 字符串字面量
msg:
.word .LC0 // 指针变量
| 符号 | Section | 原因 |
|---|---|---|
"Hi" |
.rodata |
字符串字面量只读 |
msg |
.data |
指针变量本身可写 |
.text —— 可执行代码static int helper(int x);
int main();
extern int ext_func(int);
对应汇编:
helper:
main:
说明:
static 不影响 Section所以helper、main 都在 .text
| Section | 作用 |
|---|---|
.symtab |
符号表(链接期) |
.strtab |
符号名字符串 |
.rela.text |
针对 .text 的重定位 |
.debug_* |
-g 产生的调试信息 |
Section ≠ 装载单位 真正映射到内存的是 Segment(Program Header)。
| Segment | 包含的 Section | 权限 |
|---|---|---|
PT_LOAD |
.text .rodata |
R-X |
PT_LOAD |
.data .bss |
R-W |
Loader 做的事情是:
把一组 Section 合并 → 映射成一个 Segment
例如:
.bss 虽然不在文件中此程序中会出现如下符号:
| 符号名 | 类型 | 绑定 | Section |
|---|---|---|---|
g_init |
OBJECT | GLOBAL | .data |
g_uninit |
OBJECT | GLOBAL | .bss |
msg |
OBJECT | GLOBAL | .data |
.LC0 |
OBJECT | LOCAL | .rodata |
helper |
FUNC | LOCAL | .text |
main |
FUNC | GLOBAL | .text |
ext_func |
FUNC | GLOBAL | UND |
关键解释:
helper 为什么是 LOCAL?static int helper(int x)
static → 不导出ext_func 为什么是 UND?extern int ext_func(int);
链接阶段:
.o 或库中解析undefined reference在 .o 阶段:
g_init、msg、g_uninit 的最终地址未知lui a5,%hi(g_init)
lw a4,%lo(g_init)(a5)
这里发生了什么:
%hi(g_init) → 需要 linker 填%lo(g_init) → 需要 linker 填➡ 在 .rela.text 中生成一条 relocation entry:
R_RISCV_HI20 g_init
R_RISCV_LO12 g_init
lui a5,%hi(msg)
lw s1,%lo(msg)(a5)
同样是 符号地址重定位
call ext_func
ext_func 是 UND链接器最终:
每一条 relocation 包含:
| 字段 | 含义 |
|---|---|
| offset | 需要修补的位置 |
| type | 重定位类型(HI20 / LO12 / CALL 等) |
| symbol | 关联的符号 |
| addend | 常量修正值 |
C 源码
↓
.o 文件
- Section
- Symbol Table
- Relocation
↓
Linker
- 合并 Section
- 解析符号
- 应用 Relocation
- 生成 Segment
↓
ELF 可执行文件
↓
Loader
- 映射 Segment
- 清零 .bss
- 跳转到 _start
Section 是给链接器用的逻辑结构,Segment 是给 Loader 用的内存映射结构;Symbol Table 告诉链接器“有什么”,Relocation 告诉它“哪里需要补地址”。