######## 该系列博文为书籍《程序员的自我修养》的笔记 ##########
【说明】
在前面我们说到,重定位是静态链接的核心,那么这一节就重点介绍重定位。关于动态链接,其实动态链接比静态链接复杂得多,但是百变不离其宗,动态链接搞来搞去还是要弄符号重定位,只不过形式有点不一样,还有就是可能是在运行时进行的链接,这是后话。待复习到后面的时候就晓得啦。重点还是理解重定位
【一个简单的实例】
后面就以这个例子来解释
a.c
extern int shared;
int main()
{
int a = 100;
swap(&a, &shared);
}
b.c
int shared = 1;
void swap(int *a, int *b)
{
*a ^= *b ^= *a ^= *b;
}
【重定位】
看看a.o 的反汇编
1)注意第0x18 地址的 movl 指令,,这里就是对于shared 地址的引用,我们看到编译器直接用0 来表示
这是因为它发现shared是外部符号,在链接之前是不知道其地址的,所以,要引用它的地址是没意义的,
所以它就先放个0,具体数据由链接器来搞定。
2)第0x26地址的call 指令,发现数据部分是 0XFFFFFC 也就是 -4的补码形式,那不就是跳转到地址0X27嘛
但是0X27并不是swap 函数的入口啊!所以和上边一样一样的,这个值也是没什么意义的,具体的数值还是得链接器来
3)还记得吗?上一节提到过链接器在合并了段之后就可以知道符号的真正地址了。
【重定位表】
还记得重定位表吗?那个 ".rel.text"!!该它上场了!先来一睹其内容
1)看到熟悉的字眼了不是吗!shared, swap 没错!这两个当然在重定位表中!
2)关键看看另外两个属性,OFFSET 表示“重定位入口”的位置,也就是相对于其对应的段的偏移
比如那个swap 的OFFSET 是0X27,又这个重定位表是对应.text 段的,也就是说明要修改的位置在这个段的0X27偏移处
那如果我调用了2次swap呢,那就会出现2个swap项,他们对应的OFFSET不一样!
也就是说OFFSET告诉链接器需要修改的位置,而VALUE告诉它修改成对应符号名链接过后正确的值
这个值去哪找?合并后不是有一个所有目标文件的符号表共同组成的符号表吗!就在这里找!
3)那个TYPE是什么呢?它就是要指示修改用得方法,不同的符号有不同的修改方法!
【指令的修改方式】
有一张表说明了类型的作用
1)根据上边算算shared 被链接后,原来是0的那个位置应该是多少?
显然shared 应该按第一种算法,假设shared 的实际地址在链接后变成了0x3000(就是S)
我们看到需要修正的位置(0X18处)是0(就是A),0X3000 + 0 = 0X3000,所以0X18的0就要写成3000(这就完成了重定位,就这么简单!)
2)再看看swap 为什么要减去P呢,那是因为这是相对寻址!也就是说原来的0X27那个位置不是要记录swap的值啊,而是要记录偏移值
, 假设swap 实际地址是0X2000,而刚才那个位置的地址是0X1027 ,那就要重写成0X2000+(-4)-(0X1000 + 0X27) = 0XFD5
原因是CALL指令后面的数据应该是所要调用的函数相对于下一条指令的偏移,算算看!
0XFD5 + 0X1027 + 4 = 0X2000 刚好是swap的地址,CALL正常执行了!
【COMMON】
额外的介绍一下COMMON块,还记得我们说未初始化的全局变量会被放在.BSS段吗,,其实在目标文件中不是这样的。为什么呢?又要回忆了,记得强符号和弱符号吗,我们说过,如果有2个弱符号同名是不会出错的,而是用比较大的那个作为其大小,又,未初始化的全局变量会被当做弱符号!!所以啊,我单个的目标文件又怎么知道在我被链接到可执行文件中的时候,是不是有个比我大的又和这个全局变量同名的外部符号出现呢,即,单个目标文件根本就无法确定未初始化的全局变量到底最终会被定义成多大!所以就需要标记成SHN_COMMON 类型,让链接器处理!