4.1 空间与地址分配
目标文件“a.o”和“b.o”,输出可执行文件“ab”。
可执行文件中的代码段和数据段都是由输入的目标文件中合并而来的
4.1.2 相似段合并
第一步 空间与地址分配
所有的输入目标文件的各个段的长度、属性和位置,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
符号定义和符号引用,放到全局符号表。
第二步 符号解析与重定位
使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,进行符号解析与重定位、调整代码中的地址
//a.o objdump -h a.o
Idx Name Size VMA LMA File off Algn
0 .text 00000027 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
...................
//b.o objdump -h b.o
Idx Name Size VMA LMA File off Algn
0 .text 0000004a 0000000000000000 0000000000000000 00000040 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
.......
在链接之前,目标文件中的所有段的VMA都是0,
.text段,a.o长度27,b.o长度4a
一起是71
//ab.o ld a.o b.o -e main -o ab objdump -h ab
Idx Name Size VMA LMA File off Algn
0 .text 00000071 00000000004000e8 00000000004000e8 000000e8 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
..................
链接后.text段长度71
4.1.3 符号地址的确定
“a.o”中的“main”函数在“.text”段的偏移是X,链接后,“a.o”的“.text”段位于虚拟地址0x08048094
那么“main”的地址应该是0x08048094 + X
4.2 符号解析与重定位
4.2.1 重定位
目标文件代码段中的起始地址以0x00000000开始,等到空间分配完成以后,各个函数才会确定自己在虚拟地址空间中的位置。
//objdump -d a.o
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: be 00 00 00 00 mov $0x0,%esi
//第14字节是shared,地址用$0x0暂时表示,对应后面重定位表
18: 48 89 c7 mov %rax,%rdi
1b: e8 00 00 00 00 callq 20 <main+0x20>
//swap的地址用00 00 00 00暂时表示
20: b8 00 00 00 00 mov $0x0,%eax
25: c9 leaveq
26: c3 retq
“main”的起始地址为0x00000000
这两条指令的地址部分用“0x00000000”和“0xFFFFFFFC”代替,把真正的地址计算工作留给了链接器
[wangruiqi@host-10-46-52-171 LDLearn]$ objdump -d ab
00000000004000e8 <main>:
4000e8: 55 push %rbp
.........
4000fb: be 00 10 60 00 mov $0x601000,%esi
........
400103: e8 07 00 00 00 callq 40010f <swap>
400108: b8 00 00 00 00 mov $0x0,%eax
//400108+07000000(小端)=40010f
.......................................................
.......................................................
000000000040010f <swap>:
40010f: 55 push %rbp
............
400158: c3 retq
main函数的两个重定位入口都已经被修正到正确的位置
4.2.2 重定位表 .rel.xxxx objdump -r a.o
//objdump -r a.o
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000014 R_X86_64_32 shared
//14表示在.text的偏移14处,需要重定向
//见objdump -d a.o
000000000000001c R_X86_64_PC32 swap-0x0000000000000004
RELOCATION RECORDS FOR [.eh_frame]:
OFFSET TYPE VALUE
0000000000000020 R_X86_64_PC32 .text
4.2.3 符号解析
每个目标文件定义一些符号,引用到定义在其他目标文件的符号。
重定位的入口都是对一个符号的引用,
对某个符号的引用进行重定位时,它就要确定这个符号的目标地址。
这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。
//readelf -s a.o
Symbol table '.symtab' contains 11 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FILE LOCAL DEFAULT ABS a.c
2: 0000000000000000 0 SECTION LOCAL DEFAULT 1
.............................
8: 0000000000000000 39 FUNC GLOBAL DEFAULT 1 main
9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND shared
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND swap
swap是UND 未定义的符号
链接器扫描完所有的输入目标文件之后,所有这些未定义的符号都应该能够在全局符号表中找到,否则报符号未定义错误。
4.2.4 指令修正方式 objdump -r a.o 重定位表的TYPE字段
? 近址寻址或远址寻址。
? 绝对寻址或相对寻址。
? 寻址长度为8位、16位、32位或64位。
4.3 COMMON块
链接器不支持符号的类型,它只知道一个符号的名字,并不知道类型是否一致
多个同名弱符号,链接后输出文件中大小,以输入文件中最大的那个为准
4.4 C++相关问题
4.4.1 重复代码消除
4.4.2 全局构造与析构
C++的全局对象构造函数也是在这一时期被执行的,我们知道C++的全局对象的构造函数在main之前被执行,C++全局对象的析构函数在main之后被执行。
.init 该段里面保存的是可执行指令,它构成了进程的初始化代码。因此,当一个程序开始运行时,在main函数被调用之前,Glibc的初始化部分安排执行这个段的中的代码。
.fini 该段保存着进程终止代码指令。
C++的全局对象构造和析构函数就由此实现
4.4.3 C++与ABI
2种格式的编译器产生的目标文件编译到一起(Application Binary Interface)
4.5 静态库链接
静态库包含的目标文件 ar -t libc.a
4.6 链接过程控制
GCC内嵌汇编