转载0004 plt表和got表4
前言
这是转载于CSDN的文章,仅用作存储,遵循 CC 4.0 BY-SA版权协议。
聊聊Linux动态链接中的PLT和GOT(4)—— 穿针引线
原作者:海枫
正文
编译时的PLT和GOT关系图
前几篇文章一直在讨论PLT和GOT的结构细节,编译完成之后,PLT和GOT的对应关系是怎么样的呢,下面是编译完成之后,PLT和GOT关系图。

图中重点标注了从调用printf函数语句的汇编指令call puts@plt跳转过程,图中使用编号来表标跳转顺序。
PLT表结构有以下特点:
- PLT表中的第一项为公共表项,剩下的是每个动态库函数为一项(当然每项是由多条指令组成的,jmp *0xXXXXXXXX这条指令是所有plt的开始指令)
- 每项PLT都从对应的GOT表项中读取目标函数地址
GOT表结构有以下特点:
- GOT表中前3个为特殊项,分别用于保存 .dynamic段地址、本镜像的link_map数据结构地址和_dl_runtime_resolve函数地址;但在编译时,无法获取知道link_map地址和_dl_runtime_resolve函数地址,所以编译时填零地址,进程启动时由动态链接器进行填充
- 3个特殊项后面依次是每个动态库函数的GOT表项
如果将PLT和GOT抽象起来描述,可以写成以下的伪代码:
1 | plt[0]: |
进程启动后的GOT表
PLT属于代码段,在进程加载和运行过程都不会发生改变,PLT指向GOT表的关系在编译时已完全确定,唯一能发生变化的是GOT表。
Linux加载进程时,通过execve系统调用进入内核态,将镜像加载到内存,然后返回用户态执行。返回用户态时,它的控制权并不是交给可执行文件,而是给动态链接器去完成一些基础的功能,比如上述的GOT[1],GOT[2]的填写就是这个阶段完成的。下图是动态链接器填完GOT[1],GOT[2]后的GOT图:

估计大家比较好奇的是,动态链接器怎么知道GOT的首地址?这个秘密就藏在ELF的.dynamic段里面,详见下面readelf -d test输出结果中的PLTGOT项:
1 | ivan@ivan:~/test/test$ readelf -d test |
其实.dynamic段还藏着很多其它信息,都是跟动态运行相关的信息,有兴趣的读者可以自行分析,这里不详细介绍。
动态重定位执行过程
Linux 动态链接器提供动态重位功能,所有外部函数只有调用时才做重定位,实现延迟绑定功能。下面是以调用puts函数为例画出了整个动态重定位的执行过程:

在 _dl_runtime_resolve函数内完成puts符号查找后,将该函数地址地址重定位到对应的GOT表项,并调用。
重定位之后的调用
GOT表项已完成重定位的情况下,PLT利用GOT表直接调用到真实的动态库函数,下面puts函数的调用过程:

总结
对于PLT和GOT的原理,一共分享了以下知识点:
- 为什么会有PLT和GOT表,它完成什么功能
- Linux如何通过 PLT和GOT表配合,完成延迟重定位功能
- PLT和GOT的结构是怎么样的,并且介绍每种场景下PLT的执行过程
关于PLT/GOT的基本知识写到这样就有清晰的认识了,但是Linux还有其它场景也会使用PLT/GOT,以后遇到时再展开讨论。
最后,本系列文章所有二进制分析,都是基于以下代码编译出来的可执行文件(32位)进行分析。
1 |
|
具体编译方法参考(转载1)。
