延迟绑定技术原理

动态链接比静态链接灵活,但牺牲了性能,据统计ELF程序在静态链接下比动态库快大约1%~5%。

主要原因是,动态链接下对于全局和静态数据的访问都要进行复杂的GOT定位,然后间接寻址,对于模块间的调用也要先定位GOT,然后进行间接跳转。
另外,动态链接的链接过程是在运行时完成的,动态链接器会寻找并转载所需要的对象,然后进行符号查找地址重定位等工作。

延迟绑定的实现步骤如下:

建立一个 GOT.PLT 表,该表用来放全局函数的实际地址,但最开始时,该里面放的不是真实的地址而是一个跳转,接下来会讲。
对每一个全局函数,链接器生成一个与之相对应的影子函数,如 fun@plt。
所有对 fun 的调用,都换成对 fun@plt 的调用,每个fun@plt 长成如下样子:

fun@plt:
jmp *(fun@got.plt)
push index
jmp _init

其中第一条指令直接从 got.plt 中去拿真实的函数地址,如果已经之前已经发生过调用,got.plt 就已经保存了真实的地址,如果是第一次调用,则 got.plt 中放的是 fun@plt 中的第二条指令,这就使得当执行第一次调用时,fun@plt中的第一条指令其实什么事也没做,直接继续往下执行,第二条指令的作用是把当前要调用的函数在 got.plt 中的编号作为参数传给 _init(),而 _init() 这个函数则用于把 fun 进行重定位,然后把结果写入到 got.plt 相应的地方,最后直接跳过去该函数。

仍然是使用前面的例子,我们看看 g_func2 是怎样调用 g_func 的:

0000052f <g_func2>:
 52f:   55                      push   %ebp
 530:   89 e5                   mov    %esp,%ebp
 532:   53                      push   %ebx
 533:   83 ec 14                sub    $0x14,%esp
 536:   e8 00 00 00 00          call   53b <g_func2+0xc>
 53b:   5b                      pop    %ebx
 53c:   81 c3 91 11 00 00       add    $0x1191,%ebx
 542:   c7 45 f8 02 00 00 00    movl   $0x2,0xfffffff8(%ebp) // a = 2
 549:   83 ec 0c                sub    $0xc,%esp
 54c:   6a 03                   push   $0x3 // push argument 3 for g_func.
 54e:   e8 d5 fe ff ff          call   428 <g_func@plt>
 553:   83 c4 10                add    $0x10,%esp
 556:   89 45 f4                mov    %eax,0xfffffff4(%ebp)
 559:   8b 45 f4                mov    0xfffffff4(%ebp),%eax
 55c:   03 45 f8                add    0xfffffff8(%ebp),%eax
 55f:   8b 5d fc                mov    0xfffffffc(%ebp),%ebx
 562:   c9                      leave  
 563:   c3                      ret 

如上汇编,指令 536, 53b, 53c, 用于计算 got.plt 的具体位置,计算方式与前面对数据的访问原理是一样的,经计算此时, %ebx = 0x53b + 0x1191 = 0x16cc, 注意指令 54e, 该指令调用了函数 g_func@plt:

00000428 <g_func@plt>:
 428:   ff a3 0c 00 00 00       jmp    *0xc(%ebx)
 42e:   68 00 00 00 00          push   $0x0
 433:   e9 e0 ff ff ff          jmp    418 <_init+0x18>

注意到此时, %ebx 中放的是 got.plt 的地址,g_func@plt 的第一条指令用于获取 got.plt 中 func 的具体地址, func 放在 0xc + %ebx = 0xc + 0x16cc = 0x16d8, 这个地址里放的是什么呢?我们查一下重定位表:

-bash-3.00$ objdump -R liba.so

liba.so:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
000016e0 R_386_RELATIVE    *ABS*
000016e4 R_386_RELATIVE    *ABS*
000016bc R_386_GLOB_DAT    g_share
000016c0 R_386_GLOB_DAT    __cxa_finalize
000016c4 R_386_GLOB_DAT    _Jv_RegisterClasses
000016c8 R_386_GLOB_DAT    __gmon_start__
000016d8 R_386_JUMP_SLOT   g_func
000016dc R_386_JUMP_SLOT   __cxa_finalize

可见,该地址里放的就是 g_func 的具体地址,那此时 0x16d8 放的是真正的地址了吗?我们再看看 got.plt:

Contents of section .got.plt:
 16cc fc150000 00000000 00000000 2e040000  ................
 16dc 3e040000 

16d8 处的内容是: 2e040000, 小端序,换回整形就是 0x000042e, 该地址就是 fun@plt 的第二条指令!

有钱的捧个钱场
0%