got & plt

got plt类似与Windows PE文件中IAT(Import Address Table)。

要使的代码地址无关,基本思想就是把与地址相关的部分放到数据段里面。

ELF的做法是在数据段里面建立一个指向这些变量的指针数组,称为全局偏移表(Global Offset Table,GOT),当代码需要引用该全局变量时,可以通过GOT中相对应的项间接引用。

GOT本身是放在数据段,所以可以在模块加载时被修改。延迟绑定,基本思想就是当函数第一次被用到时才进行绑定

一段非常简单的代码:

#include <stdio.h>
#include <stdlib.h>

int helloWorld(){
    printf("HelloWorld\n");
    return 0;
}

int main(){
    helloWorld();
    return 0;
}

 

 

第一条指令是通过GOT间接跳转的指令。如果连接器在初始化阶段已经初始化该项,并且将puts()的地址填充入该项,那么这个跳转指令的结果就是我们所期望的,跳转到puts(),实现函数正确调用。

但是为了实现延迟绑定,链接器在初始化阶段并没有将puts()的地址填入该项,而是将下一条指令的地址填入到put@got.plt项中。

所以第一条指令的效果是跳转到第二条指令,相当于是没有任何操作的。第二条指令是将一个数字n压入堆栈,这个数字是puts()的符号引用在重定位表".rel.plt"中的下标。

接着又是一条push指令将模块ID压入到堆栈,然后跳转到0xf7ff04f0执行,那么这个地址是什么地址?答案是_dl_runtime_resolve,下文会给出解释。

 

ELF将GOT拆分为两个表,".got"和".got.plt"。其中".got"用来保存全局变量引用的地址,“.got.plt”用来保存函数引用的地址,对于外部函数的引用全部放在".got.plt"中。

 

通过上面两幅图可以看出:R_386_GLOB_DAT是位于.got段的,R_386_JUMP_SLOT位于.got.plt 段。

".got.plt"前三项是有特殊意义的。

第一项保存的是“.dynamic”段的地址;

第二项保存的是本模块的ID,也就是之前看到的push进的那个值;

第三项保存的是_dl_runtime_resolve()函数的地址,之前跳转的地址0xf7ff04f0。

之后就是地址,可以看到都是指向.plt段中,在链接时,.plt段通常和代码段等一起合并成一个可读可执行的Segment。

可以看到puts@plt的地址0x80482f6位于.plt 中。而且puts在got表项中存放的地址0x080482f6就是puts@plt第二条指令的地址。

 

参考资料:

《程序员的自我修养》动态链接

《深入理解计算机系统》第七章,链接

posted on 2016-06-10 11:09  _懒人  阅读(3258)  评论(0编辑  收藏  举报

导航