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