6. 动态链接【《链接、装载与库》学习笔记】
为什么要动态链接
- 静态链接占据内存和磁盘空间
- 静态链接对程序的更新、部署、发布有麻烦
- 减少物理页面的换入换出,增加CPU缓存的命中率
- 可以在程序运行时动态加载插件(Plugin)
在Linux系统中,ELF动态链接文件被成为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,一般以.so为扩展名。在Windows中动态链接文件被称为动态链接库,一般以.dll为扩展名。
系统开始运行程序之前,首先会把控制权交给动态链接器,由他完成所有的动态链接工作以后把控制权交给程序,然后开始执行。
使用readelf查看共享对象的装载属性:
1 | |
共享对象的最终装载地址在编译时是不确定的。
地址无关代码
静态链接中的重定位叫链接时重定位,动态链接的情况是装载时重定位。在Windows中,又被成为基址重置(Rabasing)。
装载时重定位的缺点:指令部分无法在多个进程之间共享。
地址无关代码(PIC,Position-independent Code):把指令中需要被修改的部分分离出来,跟数据部分放在一起。可以把地址引用分成4类:
- 模块内部的函数调用、跳转:直接相对地址调用即可。
- 模块内部的数据访问:数据相对寻址往往没有相对与当前指令地址(PC)的寻址方式(通过寄存器计算得到地址)
- 模块外部的函数调用、跳转:ELF的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(Global Offset Table, GOT)当代码需要引用全局变量,可以通过GOT相对应得项间接引用。
- 模块外部的数据访问:类似上述GOT方法。
一个以地址无关方式编译的可执行文件被称作地址无关可执行文件(PIE)
如果程序中出现了指针(绝对地址引用)那么编译器和链接器会产生一个重定位表来装载时重定位。
延迟绑定
延迟绑定:当函数第一次被用到时才进行绑定。
ELF使用PLT(Procedure Linkage Table)的方法来实现,把GOT拆分成了两个表叫.got和.got.plt,期中.got保存全局变量引用地址,.got.plt保存函数引用的地址。其中,.got.plt的前三项有如下含义:
.dynamic段的地址,这个段描述本模块动态链接相关信息。- 本模块id
_dl_runtime_resolve()地址
动态链接相关结构
在linux下,动态链接器ld.so其实是一个共享对象,操作系统通过映射的方式把它加载进进程的地址空间中,操作系统在加载完动态链接器后,就把控制权交给动态链接器的入口地址。
interp段
在ELF中有一个专门的interp段,保存可执行文件所需要动态链接器的路径:
1 | |
dynamic段
结构定义在elf.h中。可以看成是动态链接下ELF文件的”文件头”:
1 | |
可以使用如下命令查看依赖的共享库:
1 | |
动态符号表
为了表示动态链接这些模块的发导入导出关系,使用动态符号表,通常叫dynsym,与symtab不同,dynsym只保存与动态链接相关的符号,对于模块内部的符号则不保存
动态链接重定位表
动态链接的文件中也有与静态链接类似的重定位表.rel.dyn,rel.plt,是对数据引用的修正(修正的位置位于.got.plt)
动态链接时进程堆栈初始化信息
辅助信息数组位于环境变量指针的后面。
动态链接的实现
动态链接器自举
具有一定限制条件的启动代码往往被称为自举(Bootstrap)。
动态链接器执行自举代码,找到dynamic段。得到重定位入口,全部重定位。
装载共享对象
动态链接器将可执行文件和链接器本身的符号表合并到一个符号表当中,称为全局符号表。然后链接器寻找可执行文件依赖的共享对象,将其放入装载集合中。
一个热共享对象里的全局符号被另一个共享对象的同名全局符号覆盖的现象称为全局符号介入。而当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后加入的符号被忽略。
重定位和初始化
初始化:.init段内内容,进程退出时执行.finit段内代码。
显式运行时链接
也叫运行时加载,也就是让程序自己在运行时控制加载指定的模块,并且可以在不需要该模块时将其卸载。
dlopen()函数用于打开一个动态库,并将其加载到进程的地址空间dlsym()函数负责找到文件handle所需要的符号。dlerror()判断上一次调用是否成功dlclose()把一个已经加载的模块卸载