对于一个包含共享对象和可执行目标文件的模块来说,共享对象在装载前往往不能确定自己在进程虚拟空间的位置,具体原因有以下两点
-
地址冲突问题
由于共享对象会被很多其他模块共同使用,如果共享对象使用一个固定的装载地址,那么这个地址就不能被其他进程所使用,会造成空间的浪费
-
共享库的更新问题
共享库如果使用规固定地址,那么对共享库升级后共享库中的全局函数和变量地址就不能够改变,否则如果有应用程序在链接时就已经绑定到了这些地址,那么升级后他们必须重新进行链接
因此,共享对象的装载时地址往往会在装载前一刻由操作系统决定,只有当装载后才能确定
装载时重定位
由于共享对象的装载时地址无法事先确定,所以在产生共享对象时,就出现了装载时重定位,即在装载时进行重定位
在Linux中,只使用-shared选项时会默认产生使用装载时重定位的共享对象
3.地址无关码
-
由于装载时重定位的方法需要对指令部分进行修改,所以没有办法做到一份指令同时被多个进程所共享
-
对于共享对象中的可修改数据部分来说使用转载时重定位则没有问题,因为这部分会被每个进程都拷贝一份副本
由于装载时重定位的指令部分无法共享的确定,由此产生了地址无关码
- 可以加载而无需重定位的代码称为地址无关码
- gcc中使用-fPIC选项可以得到使用地址无关码的共享对象
实现方法
-
基本思想
将指令中哪些需要被修改的部分单独提取出来,跟数据部分放在一起,这样的话要修改的部分在每个进程中就会有一个副本,进行重定位就会方便很多
-
模块之间的引用方式
- 模块内部的函数调用、跳转
- 模块内部的数据访问
- 模块外部的函数调用、跳转
- 模块外部的数据访问
模块内部调用、跳转
模块内部的调用者与被调用之间的位置时固定的,所以完全可以使用相对位移,这种代码是地址无关的
模块内部数据访问
模块内部指令与数据段的任何变量也都是一个运行时常量,因此要想访问数据内存的数据,只需要使用指令地址加上偏移即可
模块间数据访问
模块间数据访问往往意味着对全局变量的访问,对于在全局对象中定义的变量来说,这些符号的地址与模块装载地址有关,elf使用GOT(全局偏移表)来对这些变量进行引用
在每个模块的数据段开始处,都有一个GOT表,这个模块是一个指针数组,记录这该模块所引用的所有变量的地址,在加载时,动态链接器会重定位GOT中的每个条目,使他们指向正确的地址模块间的调用、跳转
这一种类型与模块间数据访问类似,也是使用了GOT表,在GOT表中保存了目标函数的地址,通过GOT表进行间接跳转
4.共享模块的全局变量问题
ELF共享库在编译时,默认把所有定义在共享库内部的全局变量都当做定义在其他模块的全局变量
通过GOT实现变量的访问