author: hjjdebug
date: 2026年 01月 08日 星期四 15:12:21 CST
descrip: elf 格式 relocation 概念
文章目录
- 1. 查看test 的重定位信息
- 2. .rela.dyn 区与 .rela.plt 区的区别和联系
- 3 概括动态绑定的过程
- 4. 介绍 .rela 结构
- 5. r_info 的type 有多少种?
- 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 0000000000000000gmon_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 区
$ readelf-S test There are34section headers,starting at offset0x2120:节头:[号]名称 类型 地址 偏移量 大小 全体大小 旗标 链接 信息 对齐[0]NULL00000000000000000000000000000000000000000000000000000000000[1].interp PROGBITS000000000040023800000238000000000000001c0000000000000000A001[2].note.ABI-tag NOTE00000000004002540000025400000000000000200000000000000000A004[3].note.gnu.build-i NOTE00000000004002740000027400000000000000240000000000000000A004[4].gnu.hash GNU_HASH000000000040029800000298000000000000001c0000000000000000A508[5].dynsym DYNSYM00000000004002b8000002b800000000000000600000000000000018A618[6].dynstr STRTAB000000000040031800000318000000000000003d0000000000000000A001[7].gnu.version VERSYM00000000004003560000035600000000000000080000000000000002A502[8].gnu.version_r VERNEED00000000004003600000036000000000000000200000000000000000A618[9].rela.dyn RELA00000000004003800000038000000000000000300000000000000018A508[10].rela.plt RELA00000000004003b0000003b000000000000000180000000000000018AI5228[11].init PROGBITS00000000004003c8000003c800000000000000170000000000000000AX004[12].plt PROGBITS00000000004003e0000003e000000000000000200000000000000010AX0016[13].text PROGBITS00000000004004000000040000000000000001720000000000000000AX0016[14].fini PROGBITS00000000004005740000057400000000000000090000000000000000AX004[15].rodata PROGBITS000000000040058000000580000000000000000a0000000000000000A004[16].eh_frame_hdr PROGBITS000000000040058c0000058c000000000000003c0000000000000000A004[17].eh_frame PROGBITS00000000004005c8000005c800000000000001000000000000000000A008[18].init_array INIT_ARRAY0000000000600e1000000e1000000000000000080000000000000008WA008[19].fini_array FINI_ARRAY0000000000600e1800000e1800000000000000080000000000000008WA008[20].dynamic DYNAMIC0000000000600e2000000e2000000000000001d00000000000000010WA608[21].got PROGBITS0000000000600ff000000ff000000000000000100000000000000008WA008[22].got.plt PROGBITS00000000006010000000100000000000000000200000000000000008WA008[23].data PROGBITS00000000006010200000102000000000000000100000000000000000WA008[24].bss NOBITS00000000006010300000103000000000000000080000000000000000WA001[25].comment PROGBITS00000000000000000000103000000000000000290000000000000001MS001[26].debug_aranges PROGBITS00000000000000000000105900000000000000300000000000000000001[27].debug_info PROGBITS000000000000000000001089000000000000031d0000000000000000001[28].debug_abbrev PROGBITS0000000000000000000013a600000000000000e00000000000000000001[29].debug_line PROGBITS00000000000000000000148600000000000000d10000000000000000001[30].debug_str PROGBITS00000000000000000000155700000000000002840000000000000001MS001[31].symtab SYMTAB0000000000000000000017e00000000000000630000000000000001832488[32].strtab STRTAB000000000000000000001e1000000000000001c80000000000000000001[33].shstrtab STRTAB000000000000000000001fd800000000000001430000000000000000001Key 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)参考代码: https://gitee.com/hejinjing/elf-parser.git