动态链接下,无论时可执行文件还是共享对象,一旦对其他共享对象有依赖,也就是所有导入的符号时,那么代码或数据中就会有对于导入符号的引用。而在编译时期这些导入符号的确切地址时未知的。只有在运行期才能确定真正确切的地址
静态编译下,这些未知的地址会被编译器一一修正。
对于动态链接来说,共享文件有两种编译方式(gcc -shared 和 gcc -fPIC -shared)
如果不使用PIC模式编译,那么装载时肯定是要重定位的,而且时每个进程都有一个副本(相对比较占用内存)
如果使用PIC模式编译,将会在编译期生成地址无关代码(PIC Position-Independent Code),则代码段可以实现多程序共享,而仅数据段部分会在每个程序中有一个副本(节省内存)
对于这两种模式来说都是要重定位的,当相对PIC模式编译的模块仅需要对数据段进行重定位(因为代码段中的绝对地址引用部分被分离到了GOT中,而GOT是数据段的一部分;数据段中也可能包含绝对地址的引用,正好重定位数据段)
静态链接中,目标文件里面包含有用于重定位的表:代码段重定位表“.rel.text”;数据段重定位表“.rel.data”。
动态链接中,目标文件的重定位表:“.rel.dyn”对数据引用的修正,修正的位置位于“.got”和数据段;“.rel.plt”对函数引用的修正,修正位置位于“.got.plt”。
可以通过readelf 查看一个动态链接文件的重定位表
readelf -r XXX.so
这里可以看到几种重定位入口类型:
R_386_RELATIVE R_386_GLOB_DAT 和 R_386_JUMP_SLOT
R_386_GLOB_DAT(.rel.dyn中针对.got) 和 R_386_JUMP_SLOT(.rel.plt中针对.got.plt)表示被修正的位置只需要直接将符号的地址填入。
而在重定位表中的列Offset 表明了当前符号在“.got" 或“.got.plt”中的偏移,可以根据该值在两个GOT表中寻找对应的位置,填入(连接器在全局符号表中查找)真实的外部符号地址。
R_386_RELATIVE 是基址重置。有些共享对象的数据段是无法做到地址无关的,比如:
static int a;
static int *p = &a;
由于共享对象编译时,基址是从0开始的,所以a的地址(在未重定位时)是相对与起始地址0的偏移(假设为B),则此时p的值为B;
而当共享对象装载到指定进程中的地址C时,则变量a的地址将编程B+C,即p的值需要加上装载的地址B。
R_386_RELATIVE 类型就是专门用来重定位指针变量p这种类型的。