对elf文件连接的理解(一)-重定位概述

来源:互联网 发布:java 时间控件 编辑:程序博客网 时间:2024/05/24 20:06

一.首先分析一下为什么需要重定位(暂时不考虑fpic代码,动态链接库等复杂情况)

这里举一个简单的例子,如下a.c中代码如下

int a ;int test(){    a = 1;    return 0;}
编译生成目标文件a.o,再经过与带有main函数的模块连接之后生成可执行文件,则可运行了;但是在这里要考虑一个问题,当编译a=1这一赋值语句时要知道a的地址,才能编译出相应的代码.但是在链接之前,明显a的地址是不能确定的.所以编译器在编译该段代码时会针对a=1这一对a发生引用的地方记录一个重定位信息(包括其在当前section中的offset,关联的符号,重定位类型).这样在链接时,首先算出确定所有符号的地址,然后根据重定位信息找到要修改相应的位置,再根据已经确定的符号地址算出实际要填入的值,然后修改对应位置.

如果没有重定位过程,则再编译a.c到a.o的过程中,a变量的地址可能是随机指定的,如0.可是当a.o与其他模块链接后,最终a的地址可能被定义到0x100.则在生成的可执行文件中,若不对对应位置进行修改,则明显会出错.

二.哪些地方会发生重定位,各有什么特点

在代码中,甚至在数据中(例如a=&b),都有可能需要重定位.下面分析一下各种情况.

1.在代码段中,例如上面的test函数引用变量a,这里需要生成重定位记录,以便链接器链接后来修改.具体还有几种情况

1.1 对函数,变量的引用(注意这里是对函数符号的引用,如a = (int)printf)

1.1.1 对静态函数或变量的引用

1.1.2 对全局函数或变量的引用

1.2对函数的调用(体会与引用的区别,若是引用,则要重定为函数的绝对地址,若是调用则不一定,与平台有关,在x86下是e8+偏移值,所以改成的不是绝对地址)

1.2.1 对全局函数的调用

1.2.2 对静态函数的调用

2.在数据段中,分为两种情况

2.1 对静态函数或变量的引用

2.2 对全局函数或变量的引用

三.重定位的时机

其实重定位的实际不止在ld链接时,还发生在加载器(ld-linux.so加载相应模块时.所以一个是链接时,一个是加载时.

.为什么要区别不同的重定位类型

1.为什么要区分区域,分成代码区和数据区重定位

首先,代码区重定位可以转化为数据区重定位,通过在数据区添加一个变量和函数的地址存储区域.将每个代码区重定位转移为数据区重定位(后面再具体分析),这样有什么好处呢.这里得提到动态库,大家知道动态库的好处是可以在物理内存中只有一份拷贝,然后映射到不同的进程的地址空间,可是.so文件如果存在重定位项,则加载器在加载.so文件到每个进程空间时会发生重定位(这里的重定位时机在加载时),这时便要修改.so文件的很多地方,包括代码段和数据段,这样麻烦就来了,因为存在写时复制,会为每个进程产生一个拷贝,这样.so库的优点就荡然无存了,所以这时候在数据区开出一个固定的区域,将重定位项集中到该区域,则问题便解决了.这有就是fpic代码的实现方法.


0 0
原创粉丝点击