静态链接的深入探究
静态链接的深入探究
首先使用gcc
编译两个目标文件,生成a.o、b.o :
gcc -c a.c b.c
链接器对目标文件的链接过程中对输入的多个目标文件加工后合并成一个输出文件,合并的过程是将输入文件相同性质的段合并到一起,比如将所有输入文件的”.text”合并到输出文件的”.text”段。
链接器的空间分配策略第一步:空间与地址分配;第二步:符号解析与重定位; 将a.o
与b.o
链接起来:
ld a.o b.o -e main -ab
- -e main 表示将 main 函数作为程序入口,ld链接器默认的程序入口为
_start
; - -o ab 表示链接输出文件名为
ab
,默认为a.out
;
VMA 表示 Virtual Memory Address,即虚拟地址;LMA 表示 Load Memory Address,即加载地址;
符号地址的确定:链接器在完成扫描与空间分配把各个段的虚拟地址确定后,还须要给每个符号加上一个偏移量,使它们也能够调整到正确的虚拟地址。
重定位(地址修正):在完成空间和址址的分配后,所有符号的虚拟地址确定了,链接器就进入符号解析与重定位的步骤,此时链接器就可以根据符号的地址对每个需要重定位的指令进行地址修正。然而这个修正的过程是根据一个叫重定位表来做的,它里面保存了与得定位相关的信息。
重定位表也叫重定位段,代码段”.text”如有要重定位的地方,那么就会有一个叫”.rel.text”的段保存了代码的重定位表;同样”.data”段对应的是”.rel.data”。
使用objdump -r a.o
可以用来查看a.o
里面要重定位的地方,即a.o
所有引用到外部符号的地址。
符号解析:确定符号的目标地址;
指定修正方式:绝对寻址修正、相对寻址修正。区别是绝对寻址修正后的地址为该符号的实际地址;相对寻址修改后的地址为符号距离被修正位置的地址差。
COMMON块:当不同的目标文件需要的COMMON块空间大小不一致时,以最大的那块为准,现代的链接器在处理弱符号时,采用的就是与COMMON块一样的机制。
重复代码消除: 比如拿C++模板来说,每个模板的实例代码都单独存放在一个段里,一般做法是将段的命名为.gnu.linkonce.<name>
的形式,这个name
是该模板函数实例的修饰后名称,这样链接的时候就可以区分相同的模板实例段了,将名称得复的段丢弃掉就行了。这种消除重复代码的做法对模板如此,对外部内联函数和虚函数表、默认构造函数、默认拷贝构造函数和赋值操作符的做法也类似。
函数级别的链接:就是把所有的函数像模板函数一样单独保存到一个段里面,当链接器须要用到某个函数时,就将它合并到输出文件中,对于那些没有用的函数则抛弃掉。GCC编译器的这种机制是它提供了两个选项分别是:-ffunction-sections
和-fdata-sections
,其作用分别是将每个函数或变量分别保持到独立的段中。
全局构造与析构:.init
段里保存的进程初始化代码指令,在main函数被调用之前执行;.fini
段里保存着进程终止代码指令,在main函数返回后会被执行。C++的全局构造与析构就是利用这两个特性实现的。
C++与ABI:ABI(Application Binary Interface)是指二进制层面的接口,ABI的兼容程度比API要更严格,因为相同的API在被调用的过程中其内存布局、参数、堆栈分布的细节、调用的指定在不同的机器上是不一样的,除硬件平台外,编程语言、编译器、链接器和操作系统的差异也会造成ABI的不兼容。ABI的不兼容就会导致各个目标文件之间无法相互链接。
静态库:可以简单看成是一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件。一种语言的开发环境往往会附带有语言库,这些库是对操作系统的API的包装。
使用ar
工具可以查看静态库文件包含了哪些目标文件,如:ar -t libc.a
,使用-x
参数可解压所有目标让你说的,如:ar -x libc.a
;Visual C++使用lib
可以提取、列举.lib文件中的内容,如:lib /LIST libcmt.lib
。
GCC的-fno-builtin
可关闭 gcc 的内置函数优化选项,如:gcc -c -fno-builtin hello.c
可防止GCC把只使用了一个字符串的printf
优化替换puts
函数;-verbose
可以将整个编译链接过程的中间步骤也打印出来:gcc -static --verbose -fno-builtin hello.c
。
链接控制脚本: Visual C++是.def文件,ld -verbose
可查看ld默认的链接脚本;默认的ld链接脚本放在/usr/lib/ldscripts/
下;使用-T
参数指定自定义脚本,如:ld -T link.script
。
BFD库:因为不同的软件硬件平台都有它独特的目标文件格式,即使是ELF在不同软硬件平台也有着不同的变种,这些差异导致编译器、链接器很难处理不同平台之间的目标文件,所以就有了BFD这个GNU项目,它就是希望通过一种统一的接口来处理不同的目标文件格式。现在很多的编译器、链接器、调试器以及binutils的其他工作都通过BFD库来处理目标文件,这样做的好处理是把编译器、链接器同具体的目标文件格式隔离开来,一旦要支持一种新的目标文件格式,只要在BFD里添加一种格式支持就可以了。