根据前面知道的目标文件的各个段,
现在想把多个目标文件链接到一起,为什么不简单就把各个目标的各个段相加合起来?
1. 输出文件将会有很多零散的段
2.由于每个段都需要和有一定的地址和空间对齐,所以这样做也很造成很多内部碎片
很自然的我们想起 相似的段进行合并。
关于2步链接:
1. 空间与地址分配
2. 符号解析与重定位。
关于 内存地址 都是指的VMA, 虽然很多地方LMA 都是等于 VMA的,但是有些特殊的嵌入式系统中,特别是那些程序存放在ROM 的系统中的。
在链接之前,目标文件中的所有段的VMA 都是0, 这个可以通过命令objdump -h a.o进行观察,因为这时候VMA还没有被分配,所以他们默认都为0,等到链接过之后,可执行文件ab VMA 已经被分配了, objdump -h ab。
在linux下,ELF可执行文件默认地址从地址0x08048000开始的。
重定位:
我们可以先观察下 链接前的目标文件
objdump -d a.o
我们会发现,对于a.c里面的2个引用 "shared","swap",由于编译器不知道这2个外部引用定义在其他的目标文件中,所以会给个暂时的地址给他们, 如0x00000000,或者是类似的0xFCFFFFFF,这里有个指令: 近址相对位移调用指令,
e8 fc ff ff ff 后面四位就是相对于调用指令的下一个指令的偏移量。
然后我们在看看 最后出现的可执行的文件 ab
objdump -d ab
经过了修正,shared, swap 都有自己真的调用地址。
实现这个功能就是重定位,首先每个目标文件都有一张重定位表,
objdump -r a.o
从里面可以看到
在a.o中所有引用的外部符号的地址。接下来就是链接需要做的 指令修正:
有2中修正:
1.绝对寻址修正: S+A,
2.相对寻址修正:S+A-P
这个符号运用什么修正,取决于我们上面 他在重定位表中的信息。
绝对寻址修正:以以前a.o为例子,
S是符号shared的实际地址,为0x3000
A是被修正位置的值,即0x0000
所以最后这个重定位入口修正后地址为0x3000+0x0000=0x3000
相对寻址修正:
S是符号swap的实际地址,0x20000
A是被修正位置的值 0xFCFFFFFF
P是被修正的位置,0x1000+0x27.
重复代码消除,
比如说最典型的模板,如果不消除因为模板造成的空间浪费,甚至地址较易出错,
编译器一般会给模板单独做一个段出来,比如tmp.int段,tmp.double段。
全局析构和全局构造
其实在main函数被调用之前,为了程序能够顺利执行,要先初始化进程执行环境,如堆分配初始化,线程子系统等,C++全局构造函数就在这里执行,
所以main函数不是第一个执行的函数,linux下一般程序的入口是"_start"
全局析构 就是做些清理的工作。
ABI 主要是为了能让2进制文件可以复用,但是这个很难,很多因素决定了不能。
内置类型,
堆栈的分布方式
函数调用方式
......
C++比起C语言来说,它的2进制兼容得更不好。
静态库简单得堪称一组目标文件的集合,
ar -t libc.a
大家可以看libc.a 包括的很多.o文件。
下面讲的是链接过程中的控制:
一个没有用标准库实现的hello world,由于程序是晚上写,没有带到公司,时间原因就不写了。
主要是用汇编来调用linux系统调用。
运用ld 链接脚本把 我们觉得可以合并的段合并成一个,或者discard掉某些段。
接着是ld链接脚本的相关语法,如果有兴趣的可以去试一下。
BFD库,主要是抽象出目标文件的模式然后 使他能够支持更多的目标文件格式。