读书-程序员的自我修养-链接、封装与库(18:第七章:动态链接(2)延迟绑定 PLT)
1. 动态链接性能低的两个原因
- 对于全局和静态地数据访问都要进行复杂的GOT定位,然后间接寻址;
对于模块间的调用也要先进行GOT,然后再进行间接跳转; - 动态链接的链接工作在运行时完成,即程序开始执行时,动态连接器都要进行一次链接工作;
动态连接器会寻找并装载所需要的共享对象,然后进行符号查找地址重定位等工作。
上面两个原因导致动态链接比静态链接慢的原因。
2. 延迟绑定的实现
2.1 延迟绑定出现原因
在动态链接下,程序模块之间包含大量的函数引用,其中全局变量较少,
因为大量的全局变量会导致模块间耦合度变大,所以程序在开始执行之前,
动态链接库会耗费不少时间进行模块间的函数引用的符号查找及重定位。
因为很多函数在程序中有时候并不会用到,比如一些错误函数和一些功能函数,
如果一开始就把所有的函数都链接好实际上是一种浪费。
2.2 延迟绑定思想-第一次用到时才进行绑定
Lazying Binding
由于上面的一开始就把所有的函数都链接好实际上是一种浪费,所以ELF采用了一种叫做延迟绑定的做法。
基本思想就是当函数第一次用到时才进行绑定,如果没有用到则不绑定。
所以程序开始时,模块间的函数调用都没有进行绑定,而是用到时候才由动态连接器来负责绑定。
2.3 延迟绑定的优点
大大加快程序启动的速度
特别有利于一些有大量函数引用和大量模块的程序。
2.4 延迟绑定原理
ELF 使用 PLT(Procedure Linkage Table)的方法来实现,
这种方法使用一些很精巧的指令序列来完成。
2.4.1 增加中间层间接跳转
当我们调用某个外部模块的函数时,如果按照通常的做法应该是通过GOT中相应的项进行间接跳转。
PLT为了实现延迟绑定,在这个过程中间又增加了一层间接跳转。
函数并不直接通过GOT跳转,而是通过一个叫做PLT项的结构进行跳转。
每个外部函数在PLT中都有个相应的项。
2.4.2 例子说明PLT原理
-
我们以 bar()函数为例说明,看看 [email protected] 的实现
[email protected]
jmp *([email protected])
push n
push moduleID
jump _dl_runtime_resolve -
解释:
总结:
- 先将模块函数的决议符号下标压入堆栈,
- 再将模块ID压入堆栈
- 然后调用动态链接器完成符号解析和重定位
- 再将真正的函数地址填入其中
- 当再次调用的时候,就能够直接跳转了
2.5 .plt 段
PLT在 ELF文件中以独立的段存放,段名通常叫做 .plt。
它本身是一些地址无关的代码,所以可以跟代码段等一起合并成同一个可读可执行的Segment被载入内存。