Linux 系统中,ELF动态链接文件被称为 动态共享对象(DSO,Dynamic Shared Object),简称共享对象
文件拓展名为“.so”
动态链接下 一个程序可以被分成若干个文件:程序的主要部分 - 可执行文件 和 程序所依赖的共享对象(一个或多个.so文件),它们都可称作为程序的模块。
动态链接文件(共享对象)的装载地址为0x00000000;这并非工作时的实际地址,实际地址由装载器根据当前进程地址空间的空闲情况来动态分配一块足够大的虚拟地址空间给共享对象。
装载时重定位
基本思路:在链接时对所有的绝对地址的引用不作重定位,而把这一步放在装载时完成。一旦模块装载完成,既目标地址确定,那么系统将对程序中的所有绝对地址的引用进行重定位。
静态链接时的重定位称为 链接时重定位(Link Time Relocation)
动态链接时的重定位称为 装载时重定位(Load Time Relocation) gcc -shared
然而光有装载时重定位也不能解决所有问题,因为对于动态链接文件来讲,它时可以分为可修改数据部分和不可修改数据部分,如果只有装载重定位,那每个程序必须都有一个共享对象的副本,这样会很浪费内存
这样就引入来下一个话题
地址无关代码 (gcc -shared -fPIC)
基本思想: 把指令中需要被修改的部分分离出来,跟数据部分放在一起,这样指令部分就保持不变了,而数据部分为每个进程都有一个副本,
这就是地址无关代码(PIC, Position-independent Code)技术
共享对象模块内的地址引用分为四种情况:
1. 模块内部调用或跳转:
它们之间位置固定,使用相对地址调用。
2. 模块内部数据访问
同样使用相对寻址(使用当前(指令地址)PC + 偏移量)
3. 模块与模块之间的数据访问(重点)
模块之间的数据访问,其目标地址需要等到装载后才能确定。
这里的基本思想时将这部分与地址相关的指令的当前地址放入到数据段里面。
ELF 的做法是在数据段中建立了一个 指向这些变量的指针数组 ,也被称为 全局偏移表(GOT, Global Offset Table),当代码需要引用该全局变量时,通过GOT间接引用,
即GOT中记录着该外部函数真正的地址,装载器在做动态链接时,会查找每个外部符号的地址,然后填充到GOT的对应的项中。
GOT 如何做到与指令无关的呢?(在.so 文件中对应 .got 段)
1. 模块在编译期可以确定模块内部变量相对与当前指令的偏移,同样在编译期也可以确定GOT相对于当前指令的偏移。确定GOT的位置和确定内部变量的位置方法上时一样的,通过
得到PC值然后加上一个偏移量即可得到GOT的位置。当然GOT中的每个地址对应于哪个符号(变量,函数都是符号)由编译期决定。
4. 模块间跳用和跳转
此时与3.中方法类似,只是对应的GOT的位置保存的时目标函数的地址,通过GOT实现间接跳转
地址无关小结:
各种引用方式 | ||
指令跳转和调用 | 数据访问 | |
模块内部 | 相对跳转调用 | 相对访问 |
模块外部 | GOT间接跳转访问 | GOT间接跳转访问 |