author: hjjdebug
date: 2026年 01月 08日 星期四 15:12:21 CST
descrip: elf 格式 relocation 概念
文章目录
- [1. 查看test 的重定位信息](#1. 查看test 的重定位信息)
- [2. .rela.dyn 区与 .rela.plt 区的区别和联系](#2. .rela.dyn 区与 .rela.plt 区的区别和联系)
- [3 概括动态绑定的过程](#3 概括动态绑定的过程)
- [4. 介绍 .rela 结构](#4. 介绍 .rela 结构)
- [5. r_info 的type 有多少种?](#5. r_info 的type 有多少种?)
- [6. 补充: 节区表](#6. 补充: 节区表)
关于符号的概念,请参考链接:
计算机中符号是什么意思
elf 文件仍然采用链接中hello-world 产生的文件 test
1. 查看test 的重定位信息
$ readelf -r test
重定位节 '.rela.dyn' at offset 0x380 contains 2 entries:
偏移量 信息 类型 符号值 符号名称 + 加数
000000600ff0 000200000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000600ff8 000300000006 R_X86_64_GLOB_DAT 0000000000000000 gmon_start + 0
重定位节 '.rela.plt' at offset 0x3b0 contains 1 entry:
偏移量 信息 类型 符号值 符号名称 + 加数
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
2. .rela.dyn 区与 .rela.plt 区的区别和联系
.rela.dyn 和 .rela.plt 是ELF文件格式中用于动态链接的重定位节,
相同点:
从结构上看, .rela.dyn 和 .rela.plt 的重定位条目结构相同(均基于ELF64_Rela或ELF32_Rela),
RELA结构(包含Addend字段),但用途和处理对象有明确区别。
不同点:前者关联动态符号表(.dynsym节)中的数据符号,后者关联动态符号表(.dynsym节)中的函数符号。
.rela.dyn 处理数据符号(如全局变量、静态变量等)的重定位,其重定位目标通常位于.got节中;
.rela.plt 处理函数符号的重定位,目标位于.got.plt节中。
这种分工源于动态链接机制不同:数据引用和函数调用需要不同的绑定策略。
.rela.dyn 通常在程序加载时完成重定位,用于修正数据符号的地址;
.rela.plt 支持延迟绑定(Lazy Binding),即函数重定位在首次调用时才完成,通过.plt节(过程链接表)和.got.plt节协作实现,以提升程序启动效率。
具体延迟绑定细节可以参考以下链接
elf 文件动态加载过程
3 概括动态绑定的过程
这里概括一下, 外部printf函数由于其只调用了字符串太简单被简化为puts 函数调用. 这部分会形成一小段调用代码位于plt.sec 节中
为 jmp (*addr), 从指定的地址中取出目标地址, 到那个地址中去执行
这部分 plt.sec 是代码区, 是不能改的. 可改的是addr 处存储的地址.
那个地址是函数实际的入口地址, 但第一次调用时还不是,第一次调用存储的是地址解析函数调用地址.
其中 addr 所处的那个节叫 .got.plt, 就是说它将来存储实际函数入口地址,第一次存储地址解析函数地址. 是可更改的.
地址解析函数地址所处的节叫plt 节, 也是代码节,不可更改. 它的代码是这样的.
push 0
jmpq resolve
第二个函数调用则是
push 1
jmpq resolve
resolve地址解析函数很厉害, 它根据槽位号能找到外部绑定的函数名,并能确定外部函数地址,并将结果存储到.got.plt对应位置处.
解析一次,以后就不会跳到这里了,而是直接跳转到真实地址去了.
4. 介绍 .rela 结构
首先, 重定位是对符号的重定位, 所以被重定位的符号名称是一项内容.
被重定位的符号值, 肯定都是0. 不知道为什么要定义它
类型. 是外部变量还是外部函数等.
偏移量. 是说明在内存的什么地址来修改, 把原来的0改为解析到的地址.
信息. 就是其它的属性信息.
有了这些基础,我们再看elf64.h 中的结构定义
结构很简单,三个变量. 都是8bytes 数据
typedef struct
{
Elf64_Addr r_offset; /* Address /
Elf64_Xword r_info; / Relocation type and symbol index /
Elf64_Sxword r_addend; / Addend */
} Elf64_Rela;
typedef uint64_t Elf64_Addr;
typedef uint64_t Elf64_Xword;
typedef int64_t Elf64_Sxword;
r_offset指示需要修改的地址
r_info的高32位指向符号表索引,低32位指定重定位类型
符号值 + r_addend得到最终地址
r_addend 一般是0, 不是0的情况以后碰到再给实例吧.
5. r_info 的type 有多少种?
switch(ELF64_R_TYPE(r_info)) {
case 1: return "R_X86_64_32";
case 2: return "R_X86_64_PC32";
case 5: return "R_X86_64_COPY";
case 6: return "R_X86_64_GLOB_DAT";
case 7: return "R_X86_64_JUMP_SLOT";
default: return "OTHERS";
6. 补充: 节区表
想看看前边提到的重定位地址 0x600ff0, 0x600ff8, 0x601018属于哪个节区,
可以打出节表, 如下. 即知:
0x600ff0,0x600ff8 属于 .got 区
0x601018 属于 属于 .got.plt 区
cpp
$ readelf -S test
There are 34 section headers, starting at offset 0x2120:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 00000238
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 00000254
0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-i NOTE 0000000000400274 00000274
0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 00000298
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 00000356
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000030 0000000000000018 A 5 0 8
[10] .rela.plt RELA 00000000004003b0 000003b0
0000000000000018 0000000000000018 AI 5 22 8
[11] .init PROGBITS 00000000004003c8 000003c8
0000000000000017 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003e0 000003e0
0000000000000020 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400400 00000400
0000000000000172 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000400574 00000574
0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000400580 00000580
000000000000000a 0000000000000000 A 0 0 4
[16] .eh_frame_hdr PROGBITS 000000000040058c 0000058c
000000000000003c 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 00000000004005c8 000005c8
0000000000000100 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000008 WA 0 0 8
[20] .dynamic DYNAMIC 0000000000600e20 00000e20
00000000000001d0 0000000000000010 WA 6 0 8
[21] .got PROGBITS 0000000000600ff0 00000ff0
0000000000000010 0000000000000008 WA 0 0 8
[22] .got.plt PROGBITS 0000000000601000 00001000
0000000000000020 0000000000000008 WA 0 0 8
[23] .data PROGBITS 0000000000601020 00001020
0000000000000010 0000000000000000 WA 0 0 8
[24] .bss NOBITS 0000000000601030 00001030
0000000000000008 0000000000000000 WA 0 0 1
[25] .comment PROGBITS 0000000000000000 00001030
0000000000000029 0000000000000001 MS 0 0 1
[26] .debug_aranges PROGBITS 0000000000000000 00001059
0000000000000030 0000000000000000 0 0 1
[27] .debug_info PROGBITS 0000000000000000 00001089
000000000000031d 0000000000000000 0 0 1
[28] .debug_abbrev PROGBITS 0000000000000000 000013a6
00000000000000e0 0000000000000000 0 0 1
[29] .debug_line PROGBITS 0000000000000000 00001486
00000000000000d1 0000000000000000 0 0 1
[30] .debug_str PROGBITS 0000000000000000 00001557
0000000000000284 0000000000000001 MS 0 0 1
[31] .symtab SYMTAB 0000000000000000 000017e0
0000000000000630 0000000000000018 32 48 8
[32] .strtab STRTAB 0000000000000000 00001e10
00000000000001c8 0000000000000000 0 0 1
[33] .shstrtab STRTAB 0000000000000000 00001fd8
0000000000000143 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)