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 的两种视角

elf-vm efl-2p

ELF 同时服务于 两类工具,因此有两套组织方式:

视角 给谁看 核心结构
Section 视角 编译器 / 链接器 Section Header Table
Segment 视角 Loader / OS Program Header Table

所以:

Section 是“链接期”的逻辑单位,Segment 是“运行期”的装载单位


Section 层面的程序

下面逐个分析代码中的对象 分别落在哪些 Section 中


一、全局与静态数据相关 Section

一、.data —— 已初始化的可写数据

int g_init = 10;

汇编:

g_init:
    .word   10

特点:

  • 已初始化
  • 可写
  • 程序加载时就要占据实际空间

所以放入 .data


二、.bss —— 未初始化的全局/静态数据

int g_uninit;

汇编:

g_uninit:
    .zero   4

关键点:

  • .bss 不在 ELF 文件中占空间
  • 只记录大小
  • Loader 在运行时清零

所以放入 .bss


三、.rodata —— 只读常量数据

const char *msg = "Hi";

拆解成两部分:

.LC0:
    .string "Hi"   // 字符串字面量
msg:
    .word .LC0     // 指针变量
符号 Section 原因
"Hi" .rodata 字符串字面量只读
msg .data 指针变量本身可写

二、代码相关 Section

四、.text —— 可执行代码

static int helper(int x);
int main();
extern int ext_func(int);

对应汇编:

helper:
main:

说明:

  • static 不影响 Section
  • 只影响 符号可见性

所以helpermain 都在 .text


三、其他常见 Section

Section 作用
.symtab 符号表(链接期)
.strtab 符号名字符串
.rela.text 针对 .text 的重定位
.debug_* -g 产生的调试信息

从 Section 到 Segment(真正会被加载的东西)

Section ≠ 装载单位 真正映射到内存的是 Segment(Program Header)

映射关系

Segment 包含的 Section 权限
PT_LOAD .text .rodata R-X
PT_LOAD .data .bss R-W

Loader 做的事情是:

把一组 Section 合并 → 映射成一个 Segment

例如:

  • .bss 虽然不在文件中
  • 但属于 RW Segment
  • Loader 会额外分配并清零

Symbol Table(符号表)——链接器的“目录”

此程序中会出现如下符号:

符号名 类型 绑定 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);
  • 本目标文件 只声明
  • 没有定义
  • 符号表中记为 Undefined

链接阶段:

  • 由 linker 在其他 .o 或库中解析
  • 否则报 undefined reference

Relocation(重定位)——汇编里正在发生什么

为什么需要重定位?

.o 阶段:

  • g_initmsgg_uninit最终地址未知
  • 只能先写成“占位引用”

代码里的典型重定位点

示例 1:访问全局变量

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

示例 2:访问 msg

lui a5,%hi(msg)
lw  s1,%lo(msg)(a5)

同样是 符号地址重定位


示例 3:函数调用

call ext_func
  • ext_func 是 UND
  • 生成 函数调用重定位

链接器最终:

  • 找到定义
  • 计算 PC-relative offset
  • 回填指令

relocation 表中记录了什么?

每一条 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 告诉它“哪里需要补地址”。