动态链接过程延迟绑定的实现(PLT)

动态链接比静态链接要慢1%~5%,根据动态链接中PIC(与地址无关代码)的原理PIC,可以知道造成该情况的原因如下:

1.动态链接下对于全局和静态数据的访问都要进行复杂的GOT(全局偏移表)定位,然后间接寻址;对于模块间的调用也要先定位GOT,然后再进行跳转

2.动态链接的链接工作是在运行时完成,即程序开始运行时,动态链接器都要进行一次链接工作,而链接工作需要复杂的重定位等工作,减慢了启动速度


针对上述第二个减慢动态链接的原因,提出了延迟绑定(Lazy Binding)的要求:即函数第一次被用到时才进行绑定。通过延迟绑定大大加快了程序的启动速度。而 ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。


PLT:

1.前言:

当在程序运行过程中需要调用动态链接器来为某一个第一次调用的外部函数进行地址绑定时,需要提供给动态链接器的内容有:发生地址绑定需求的地方(文件名)以及需要绑定的函数名,也即是说,假设动态链接器使用某一个函数来进行地址绑定工作,那它的函数原型应该为: lookup(module,function)。

Eg:假设文件 liba.so 中需要调用 libb.so 中的函数bar(),那在第一次调用时,将调用动态链接器中的 lookup 函数,其参数为 lookup(liba.so,bar)。

事实上,在Glibc 中,该 lookup函数的真实名字是:_dl_runtime_resolve()


2.PLT 的简单实现:

原来的做法:调用某一个外部函数时,通过GOT中的相应项进行间接跳转

PLT 的加入:调用函数时,通过一个 PLT项的结构来进行跳转,每一个外部函数在 PLT 中都有一个相应的项


Eg:PLT的简单实现,假设对上面例子中的 bar() 函数,在PLT中存在与其对应的一项 bar@plt。其实现如下:

bar@plt:
jmp *(bar@GOT)         		//如果是第一次链接,该语句的效果只是跳转到下一句指令。否则,将会跳转到 bar()函数对应的位置
push n				//压栈 n,n 是 bar 这个符号在重定位表 .rel.plt 中的下标
push moduleID           	// 压栈当前模块的模块ID,上述例子中的 liba.so
jump _dl_runtime_resolve()   	//跳转到动态链接器中的地址绑定处理函数


3.PLT的真正实现:

ELF将GOT拆为了两个表叫做“.got”,".got.plt"。其中 .got 用来保存全局变量的引用地址,.got.plt 用来保存函数引用的地址,也就是说,所有对于外部函数的引用被分离到了 .got.plt 表中,该表的前三项分别是:

1. .dynamic 段的地址

2. 本模块的 ID

3. _dl_runtime_resolve()的地址

对于上述例子中的bar()函数,真正实现为:

PLT0:
push *(GOT + 4) //压入模块名
jmp *(GOT + 8)  //跳转到 _dl_runtime_resolve()函数

...

bar@plt:
jmp *(bar@GOT)   //仍然首先进行GOT跳转,尝试是否是第一次链接
push n		//压入需要地址绑定的符号在重定位表中的下标
jmp PLT0	//跳转到 PLT0


PLT在ELF文件中以独立的段存放,段名叫 .plt。它本身 是与地址无关的代码(PIC),因此可以和代码段等一起合并成一个可读可执行的节,并装载入内存中。


你可能感兴趣的:(编译链接)