【程序员的自我修养05】符号修正的功臣——重定位表
绪论
大家好,欢迎来到【程序员的自我修养】专栏。正如其专栏名,本专栏主要分享学习《程序员的自我修养——链接、装载与库》的知识点以及结合自己的工作经验以及思考。编译原理相关知识本身就比较有难度,我会尽自己最大的努力,争取深入浅出。若你希望与一群志同道合的朋友一起学习,也希望加入到我们的学习群中。文末有加入方式。
简介
在上篇文章【程序员的自我修养04】目标文件生成可执行文件过程文章中,我们了解了目标文件如何生成可执行文件的两个步骤:空间与地址分配、符号解析和重定位。建议读者看一遍上文,方便理解本章内容。
在上篇文章的最后,并提出了一个思考题:链接器是如何知道哪些符号需要被修正的呢?
从上文中,我们知道对外部引用的符号或变量,目标文件暂时会设置成0,待符号分析完成后,可执行程序已经确认其真实的虚拟地址,再进行修正。我们是否可以根据变量值是否为0来进行修改正呢?这样的思路,对于函数引用好像是可以的,毕竟没有哪个函数的地址是0吧。但是变量的好像就不行了,因为变量存在初始值为0的情况。这便是本章的核心:重定位表。
重定位表
重定位表是ELF文件中专门用来保存需要重定位信息的段,它的作用是指导链接器如何修改相应的段内容。对于每一个要被重定位的ELF段都有一个对应的重定位表。比如代码段.text
如果有要被重定义的地方,那么就会有一个相对应的段称为.rel.text
。如上文案例中a.o
的段表信息。
yihua@ubuntu:~/test/static-linker$ readelf -S a.o
There are 12 section headers, starting at offset 0x2c8:
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040 0000000000000029 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000220 0000000000000030 0000000000000018 I 9 1 8
[ 3] .data PROGBITS 0000000000000000 00000069 0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000069 0000000000000000 0000000000000000 WA 0 0 1
[ 5] .comment PROGBITS 0000000000000000 00000069 000000000000002a 0000000000000001 MS 0 0 1
[ 6] .note.GNU-stack PROGBITS 0000000000000000 00000093 0000000000000000 0000000000000000 0 0 1
[ 7] .eh_frame PROGBITS 0000000000000000 00000098 0000000000000038 0000000000000000 A 0 0 8
[ 8] .rela.eh_frame RELA 0000000000000000 00000250 0000000000000018 0000000000000018 I 9 7 8
[ 9] .symtab SYMTAB 0000000000000000 000000d0 0000000000000120 0000000000000018 10 8 8
[10] .strtab STRTAB 0000000000000000 000001f0 000000000000002c 0000000000000000 0 0 1
[11] .shstrtab STRTAB 0000000000000000 00000268 0000000000000059 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)
yihua@ubuntu:~/test/static-linker$ readelf -S b.o
There are 11 section headers, starting at offset 0x270:
如上,因为a.o
中引用了外部函数swap
,故在链接时需要对代码段进行重定位,则新增描述段.rel.text
。同理,还可能存在.rel.data
重定位段。我们可以通过如下命令查看文件中重定位表的信息。
yihua@ubuntu:~/test/static-linker$ objdump -r a.o
a.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000016 R_X86_64_PC32 shared-0x0000000000000004
000000000000001e R_X86_64_PLT32 swap-0x0000000000000004
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text
可知,a.o
中有两段重定位符号表,分别用于修改.text
段和.eh_frame
段。.eh_frame
段是一个特殊的段,它与异常处理和调试信息相关,我们暂时不关注。仅关注.text
段。
分析:
- 其中
RELOCATION RECORDS FOR [.text]
中的[.text]代表是代码段的重定位表。 - OFFSET列:表示该段中需要被调整的位置。结合
a.o
的反汇编可知,0x0000000000000016
和0x000000000000001e
分别是代码段中,对外部变量shared
和外部函数swap
的引用。
yihua@ubuntu:~/test/static-linker$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 1a <main+0x1a>
1a: 48 89 c7 mov %rax,%rdi
1d: e8 00 00 00 00 callq 22 <main+0x22>
22: b8 00 00 00 00 mov $0x0,%eax
27: c9 leaveq
28: c3 retq
- TYPE列:表示重定位符号的类型。不同的平台,其类型不同,而类型决定了修复符号的方式,如
a.o
重定位符号表中。
R_X86_64_PC32:重定位公式为S+A-P
。
- S:符号链接后的最终的虚拟地址。
- A:加数,也称修正值。
- P:需要被重定位处在链接后最终的虚拟地址。
R_X86_64_PLT32:重定位公式为L+A-P
。
-
L:符号的实际虚拟地址或在PLT表中的地址。当函数符号定义在目标文件中时,L则为符号的实际虚拟地址;当函数符号定义动态库中时,L则为PLT表中的地址。很明显,
swap
属于前者。在后续章节中,我们再深入讨论第二种情况。 -
VALUE列:表示符号与修正值。比如
shared-0x0000000000000004
,表示对应符号表中shared
符号,修正值为-0x0000000000000004
。
符号重定位流程
通过对重定位表的了解,我们可以通过案例,尝试推论符号重定位流程。代码示例如下:
a.o
的需要重定位的代码段信息:
yihua@ubuntu:~/test/static-linker$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: 48 8d 35 00 00 00 00 lea 0x0(%rip),%rsi # 1a <main+0x1a>
1a: 48 89 c7 mov %rax,%rdi
1d: e8 00 00 00 00 callq 22 <main+0x22>
22: b8 00 00 00 00 mov $0x0,%eax
27: c9 leaveq
28: c3 retq
a.o
的重定位表信息:
yihua@ubuntu:~/test/static-linker$ readelf -r a.o
Relocation section '.rela.text' at offset 0x220 contains 2 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000016 000900000002 R_X86_64_PC32 0000000000000000 shared - 4
00000000001e 000b00000004 R_X86_64_PLT32 0000000000000000 swap - 4
Relocation section '.rela.eh_frame' at offset 0x250 contains 1 entry:
Offset Info Type Sym. Value Sym. Name + Addend
000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0
ab
的符号表信息:
yihua@ubuntu:~/test/static-linker$ readelf -s ab
Symbol table '.symtab' contains 16 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000004000e8 0 SECTION LOCAL DEFAULT 1
2: 0000000000400160 0 SECTION LOCAL DEFAULT 2
3: 0000000000601000 0 SECTION LOCAL DEFAULT 3
4: 0000000000601018 0 SECTION LOCAL DEFAULT 4
5: 0000000000000000 0 SECTION LOCAL DEFAULT 5
6: 0000000000000000 0 FILE LOCAL DEFAULT ABS a.c
7: 0000000000000000 0 FILE LOCAL DEFAULT ABS b.c
8: 0000000000000000 0 FILE LOCAL DEFAULT ABS
9: 0000000000601000 0 OBJECT LOCAL DEFAULT 3 _GLOBAL_OFFSET_TABLE_
10: 0000000000400111 75 FUNC GLOBAL DEFAULT 1 swap
11: 0000000000601018 4 OBJECT GLOBAL DEFAULT 4 shared
12: 000000000060101c 0 NOTYPE GLOBAL DEFAULT 4 __bss_start
13: 00000000004000e8 41 FUNC GLOBAL DEFAULT 1 main
14: 000000000060101c 0 NOTYPE GLOBAL DEFAULT 4 _edata
15: 0000000000601020 0 NOTYPE GLOBAL DEFAULT 4 _end
a.o
代码段在ab
中最终加载的虚拟地址。
yihua@ubuntu:~/test/static-linker$ objdump -d ab
ab: file format elf64-x86-64
Disassembly of section .text:
00000000004000e8 <main>:
4000e8: 55 push %rbp
4000e9: 48 89 e5 mov %rsp,%rbp
4000ec: 48 83 ec 10 sub $0x10,%rsp
4000f0: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
4000f7: 48 8d 45 fc lea -0x4(%rbp),%rax
4000fb: 48 8d 35 16 0f 20 00 lea 0x200f16(%rip),%rsi # 601018 <shared>
400102: 48 89 c7 mov %rax,%rdi
400105: e8 07 00 00 00 callq 400111 <swap>
40010a: b8 00 00 00 00 mov $0x0,%eax
40010f: c9 leaveq
400110: c3 retq
分析:
- 通过
a.o
的重定位表可知,有两个需要重定位的项,分别在.text
段offset 0x16 、0x1e。并且代码段对应位置为0。 - 通过
a.o
的重定位表可知,shared变量的修正值为-4
。在ab
符号表中,shared的虚拟地址为0x0000000000601018
。重定位处shared在链接后最终的虚拟地址为0x00000000004000e8+0x16
。根据重定位公式为S+A-P
,得出修改后的地址为0x0000000000601018+(-4)-(0x00000000004000e8+0x16) = 0x200f16
。与实际相符。 - 同理,因为外部函数swap定义在目标文件中。因此定位表达式
L+A-P
=S+A-P
=0x0000000000400111+(-4)-(4000e8+1e)= 7
。与实际相符。 - 至此,是不是觉得很神奇,内心很欢喜~~~
结合上一章内容,我们大致可以总结可重定位文件链接成可执行文件的流程如下:
- 空间分配。将各个
.o
文件的相似段进行整合,输出目标文件ab
。 - 地址分配。对
ab
需要加载到内存中的段分配虚拟内存。比如.text
、data
段。 - 符号解析。将
ab
文件中全局符号符号表进行完善。比如根据步骤二,计算出符号的对应虚拟地址。 - 符号重定位。链接器根据各
.o
文件中的重定位表结合ab
文件中的全局符号表信息,计算出最终的虚拟地址,并修正。
总结
通过本章,再结合上篇【程序员的自我修养04】目标文件生成可执行文件过程 基本已经掌握了 目标文件静态链接成可执行程序的过程。后续我会从动态链接可执行程序角度,进行剖析。有兴趣的朋友可以持续关注哦。
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!