编译链接之----地址无关代码(PIC)总结

2019-04-15 13:09发布

动态链接的进程空间分布:动态链接库的最终装载地址在编译的时候并不能确定,而是在装载时,装载器根据当前进程地址空间的空闲情况,动态的分配一块足够大小的地址空间给动态链接库。 这样对地址的引用就可以采用地址重定位的方式来解决了。就向静态库那样在静态链接时通过修改指令进行静态重定位,我们的动态链接很容易也想到用装载重定位来进行动态重定位,这样就不用管它到底是装载到哪个地址啦。因为我们的指令地址和数据相对于加载的地址是不变的,我们只需要通过模块基地址+相对地址很容易的去访问我们想要访问的数据或跳转到的指令地址。也许你在为你的这一伟大思想而兴奋时,你是否还记起了动态链接库的一个很大的优势:节省内存空间。 当你重定位时需要修改相对应的指令而使得指令不能为每个进程共享。这样就使得那一大优势没有了。此时我们需要一种更好的方法解决这种对绝对地址的重定位问题,遭到代码地址无关。 PIC技术:把指令中那些需要被修改的部分分离出来和数据部分放在一起,这样指令部分就可以保持不变了,而数据部分每个进程都有一个副本。 先来分析各种地址引用方式: 第一种:模块内部的函数调用; 第二种:模块内部的数据访问; 第三种:模块间的函数调用; 第四种:模块间的数据访问; 也许 第一种和第二种 你已经知道了怎么解决。这个不需要进行重定位,那个只是一个相对地址引用。而模块间的访问就要麻烦一点啦。因为访问模块间的数据要等到装载才能决定,我们要使的代码地址无关,基本思想就是把和地址相关的部分放到数据段里面,很明显,这些其他模块的变量地址是和模块装载地址有关的。 那么第三种和第四种怎么解决呢?ELF的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(GOT)。 当指令中需要访问外部变量时,会先找到GOT,然后根据GOT中变量所对应的项找到变量的目标地址。链接器在装载模块时会查找每个变量所在的地址,然后填充GOT中每个项。由于GOT是放在数据段的,所以每个进程可以有一个独立的副本。 PIC技术还没有完。上面所说的都是在共享库中的情况,但是在可执行文件还是这样吗?程序主模块的代码并不是地址无关代码,也就是说代码不会使用类似PIC的机制。它访问外部全局变量的方式和普通数据访问方式是一样的。所以变量的地址在链接的过程中就要定下来。为了能使链接过程正常进行,链接器会在创建可执行文件时在它的.BSS段创建一个变量的副本。那么问题就很明显了,现在变量定义在共享对象中,而在可执行文件的.BSS段也有一个副本,这样在运行过程中肯定是不可行的。 为了解决这个问题,那就是将所有的使用这个变量的指令都指向可执行文件中的那个副本。ELF共享库在编译时就默认把定义在模块内部的全局变量当作定义在其他模块的全局变量。当共享库在装载时,如果全局变量在可执行文件中拥有副本,那么动态链接器就会把GOT中的相应地址指向该副本,这样该变量在运行时实际上只有一个实例。如果该变量在 共享模块中被初始化,那么动态链接器还需要将该值复制给程序主模块中的变量副本。