一个程序的前世今生(四)——延迟绑定和GOT与PLT
简介:
上一章描述了库文件存在静态和动态的区别,在现代操作系统中由于很多基础库已经存在且复用程度较高,所以使用动态链接库的场景较多。在大多数程序中使用了动态链接技术,使得编译出来的程序占用空间变小,一些公共的库函数如glibc在可执行文件中并没有对应的函数实现。但是动态绑定导致了程序运行时加载时间较长,因此产生了延迟绑定技术在程序运行再根据GOT和PLT查找动态链接库中的实际实现。当然,可以使用gcc链接选项-z,now取消延迟绑定。
一: GOT表
什么是 GOT 表?
每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。
GOT 表在 ELF 文件中分为两个部分:
GOT 表的作用是什么?
把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld
)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld
。
怎么查看 GOT 表?
在 Linux 系统里,我们可以通过 objdump
命令来查看程序的 GOT 表,具体的指令如下:objdump -R file_name
在 Linux 的实现中,.got.plt 的前三项的具体的含义如下
- GOT[0],.dynamic 的地址。
- GOT[1],指向内部类型为 link_map 的指针,只会在动态装载器中使用,包含了进行符号解析需要的当前 ELF对象的信息。每个 link_map 都是一条双向链表的一个节点,而这个链表保存了所有加载的 ELF 对象的信息。
- GOT[2],指向动态装载器中 _dl_runtime_resolve 函数的指针。
.got.plt 后面的项则是程序中不同 .so 中函数的引用地址。下面给出一个相应的关系。
二: PLT表
什么是 PLT 表?
过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目。
当 main() 函数开始,会请求 plt 中这个函数的对应 GOT 地址,如果第一次调用那么 GOT 会重定位到 plt,并向栈中压入一个偏移,程序的执行回到 _init() 函数,rtld得以调用就可以定位 printf 的符号地址,第二次运行程序再次调用这个函数时程序跳入 plt,对应的 GOT 入口点就是真实的函数入口地址。
动态连接器并不会把动态库函数在编译的时候就包含到 ELF 文件中,仅仅是在这个 ELF 被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在 ELF 文件中的 GOT 中保留一个调用地址。
三:延迟绑定原理