动态库加载和-fPIC
如果多个程序使用同一个动态库,动态库指令上面的跳转就会出现问题。所以我们需要把需要跳转的指令单独拿出来,跟数据部分放在一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本。这种方案就是目前被称为地址无关代码(PIC,Position-independent Code)的技术。
在这里我们把共享对象模块中的地址引用按照是否为跨模块分为俩类:模块内部引用和模块外部引用;按照不同的引用方式分为指令引用和数据访问。
类型一 模块内部调用或跳转
因为被调用的函数和调用者都处于同一个模块,它们之间的相对位置是固定的,所以这种情况比较简单。对于现代的系统来说,模块内部的跳转,函数调用都可以是相对地址调用,或者是基于寄存器的相对调用,所以对于这种指令是不需要重定位的。
类型二 模块内部数据访问
指令中明显不能拥有数据的绝对地址,那么唯一的办法就是相对寻址。我们知道,一个模块前面一般是若干个页的代码,后面紧跟着若干个页的数据,这些页之间的相对位置是固定的,也就是是,任何一条指令加上固定的偏移量就可以访问模块内部数据了。现在的体系结构中,数据的相对寻址往往没有相对与当前地址(PC)的寻址方式,所以ELF用了一个很巧妙的办法来得到当前的PC值,然后再加上一个偏移量就可以达到访问相对变量的目的了。
类型三 模块间数据访问
模块间的数据访问目标地址要等装载时才决定。我们前面提到的代码地址无关,基本的思想就是把地址相关的部分放到数据段里面,很明显,这些其他模块的全局变量的地址是跟模块装载地址有关的。ELF的做法是在数据段里面建立一个指向这些变量的指针数组,也被称为全局偏移表(Global Offset Table, GOT)。当代码需要引用该全局变量时,可以通过GOT中相对应的项简介引用。
类型四 模块间调用、跳转
对于模块间调用和跳转,我们可以采用类型三的方法来解决。与上面的类型有所不同的是,GOT中相应的项保存的是目标函数的地址,当模块需要调用目标函数时,可以通过GOT中的项简介跳转。
-fpic和-fPIC的区别:
功能上一样,“-fPIC”产生的代码大,兼容性好。“-fpic”产生的代码小,在某些平台上有一些限制,比如全局符号的数量或者代码的长度等。