共享对象在编译时不能假设自己在进程虚拟地址空间中的位置,可执行文件可以确认。
装载时重定位
如果使用静态链接的重定位方法,即模块装载地址确认,对所有绝对地址引用重定位。但是指令部分是多个进程共享的,装载时重定位需要修改指令,所以指令部分不能这么做。而可修改数据部分对于不同进程有多个副本可以这么做。
地址无关代码-fPIC
只用装载时重定位无法做到指令部分在多个进程之间共享。引入地址无关代码,就是把代码中可修改部分提取出来跟数据放在一起,在每个进程中拥有一个副本。因此引入.got(全局偏移表)。程序需要访问变量时,先找到got
然后根据变量中对应的项找到变量的目标地址。
PLT延迟绑定
使用动态链接需要间接寻址,比较慢。所以使用PLT在函数第一次被用到时才进行绑定。每个外部函数在PLT中有个对应的项, bar@plt:
bar@plt:
jmp *(bar@GOT) //got中保存的项
push n
push moduleID
jump _dl_runtime_resolve
如果初始化过,那么第一条指令就是跳转地址。如果不是,第一条指令会跳转到第二条指令。
.rel.plt
n为符号引用在该重定位表下的下标。ELF将GOT拆成两个表.got|.got.plt
.got保存数据(全局变量),.got.plt保存函数引用被剥离出来。
。got.plt
第一项保存.dynamic
段
第二项保存本模块ID
第三项保存_dl_runtime_resolve
地址
PLT本身为地址无关代码,可以和代码段直接合并。
可以不用PIC
,那相当于指令部分不能共享浪费内存。
.dynamic段
.dynamic
段保存了动态链接器所需要的基本信息 例如依赖哪些共享对象 动态链接符号表的位置 动态链接重定位表的位置 共享对象初始化代码的地址 .dynamic
其实就是动态链接的elf文件头
.dynsym
为动态符号表,保存与动态链接相关的符号.symtab
包括全部符号
PIC代码还需要重定位吗,需要
代码不需要了,因为剥离出来到了GOT。除此之外还有绝对地址引用的数据部分也需要重定位。.rel.dyn
是.got
及数据段,是数据部分的重定位.rel.plt
是函数部分的修正
装载共享对象
.dynamic段中有一种类型入口是DT_NEED
即依赖的共享对象。加载进来后,遍历重定位表,将got/plt
需要重定位的位置进行修正。