无论是静态链接还是动态链接,初始都是操作系统读取可执行文件的FILE_HEADER,以检查文件格式、操作权限等属性,然后根据段表获取各个”segment”的VMA虚拟装载位置、文件地址和操作属性RWXP等,再根据相似属性原则相连原则完成装载,而后将控制权交给文件头结构中e_entry
入口地址(ELF程序的入口虚拟地址,可重定位文件不可执行,故而为0,静态链接的可执行文件便是指向运行库main初始化函数,动态链接的可执行文件则是指向动态链接程序的入口地址)。
在Linux下,动态链接器ld.so实际上本身也是一个共享对象.so(但它和别的共享对象不同的是,它不能引用其他共享对象),操作系统将ld.so装载进虚拟进程空间后,将把控制权移交给动态链接器的程序入口。ld.so获取控制权后,首先执行一系列自身的初始化操作,然后将会按照链式法则顺序加载一系列共享对象.so。当所有.so装载完毕后,才将控制权重新交给可执行文件本身。
现在面对需要动态链接的可执行文件,系统该采用的动态链接器在哪?哪种动态链接器?这是由ELF可执行文件决定的。.interp段(interpreter解释器)的内容很简单,保留的就是:该可执行文件所需的动态链接器的路径。
.interp段中的动态链接器路径/lib/ld-linux.so.2.是个 软链接,其实指向的应该是/lib/ld-2.6.1.so,当系统Glibc库升级时,该软连接就是指向新的动态链接器,而不需要修改.interp中的路径名来配合系统的升级。
也可以通过readlf
指令来获取一个可执行文件所需要的动态链接器的路径
$readlf -l a.out | grep interpreter
[Requesting program interpreter: /lib/ld-linux.so.2]
前面的文章说道 外部函数地址GOT子表“.got.plt”的第一项便是指向.dynamic段。类似于.interp这样的段,ELF中还有几个段也是专门用来动态链接的,比如.dynamic段和.dynsym段。
.dynamic段保存了动态链接需要的基本信息:依赖哪些共享对象、动态链接符号表的位置、动态链接重定位表的位置、共享对象初始化代码的地址。.dynamic段中是如下结构体数组,该结构体元素用来定义动态链接所需的各基本信息。
typedef struct{
Elf32_Sword d_tag;
union {
Elf32_Word d_val;
Elf32_Addr d_ptr;
} d_un;
} Elf32_Dyn;
…..
若是想直接查看当前文件依赖于那些共享对象,可用ldd指令
$ldd program1
linux-gate.so.1 => (0xffffe000) //该文件属于内核虚拟共享对象,处在虚拟进程空间的高端,属于内核区,涉及到内核加载
./Lib.so (0xb7f62000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e0d000)
/lib/ld-linux.so.2 (0xb7f66000)
可以看到.dynamic段像以前的FILE_HEADER一样只是提供具体所需对象的索引数组,下面来继续查看一些动态链接所需的关键信息,如动态符号表.dynsym。
为了表示动态链接的模块之间的符号导入导出关系,ELF提供了动态符号表.dynsym,与.symtab不同,前者只保存和动态链接相关的符号,而模块内部符号是不保存的( 专表专用,加速动态链接符号查找过程),故而ELF动态链接模块同时拥有两个表.symtab和.dynsym, .symtab包括所有符号,包括.dynsym中的符号。
动态符号表也需要一些辅助表,如动态符号字符串表.dynstr (dynamic string table)(因为符号名称是非等项的,为了方便dynsym表的管理,显然需要字符串这种不稳定分子转移出来以实现dynsym表项等长),为了加快符号的查找过程,往往还有辅助的符号哈希表.hash。
.so共享对象需要重定位的原因在于导入符号的存在,无论是可执行文件还是.so,一旦依赖于其他共享对象,也就是说有导入符号存在,那么它的代码或数据中就会有对导入符号的引用。在编译时这些导入符号的地址未知,静态链接中,这些未知的地址引用在最终链接时被修正;而在动态链接中,导入符号的地址只有在运行时才确定,所以需要在运行时将这些导入符号的引用修正,即需要重定位。(类似于Windows下的符号导出表和符号导入表)
对于使用PIC技术的可执行文件或共享对象来说,虽然他们的代码段不需要重定位(因为地址无关),但是.data段有可能包含了对绝对地址的引用,因为代码段中绝对地址相关的部分被分离了出来,变成了GOT,而GOT实际上是数据段的一部分。动态链接的文件中,有.rel.dyn和.rel.plt,其中.rel.dyn是对数据引用的修正,修正的位置位于.got以及数据段,而.rel.plt(延迟绑定)是对函数引用的修正,所修正的位置位于.got.plt。
静态链接用到的重定位入口
1.R_386_32 绝对地址修正
2.R_386_PC32下一指令相对地址修正
动态链接用到的重定位入口类型
1.R_386_Relative:基址重置rebasing,基址+偏移地址
2.R_386_GLOB_DAT:对.got表中的数据符号的重定位标志,在.got表中填充该数据符号的绝对地址
3.R_386_JUMP_SLOT:对.got.plt的重定位标志,只需要根据.rel.plt表给出的该函数符号在.got.plt的偏移量找到该函数符号的位置,填写上该函数加载后的地址
重定位表的意义在于提示动态链接器要修正的外部符号信息,可以配合实现PLT等延迟绑定机制,实现动态链接的快速进行。