PLT学习

I. 动态链接中延迟绑定(Lazy binding)的基本思想和方法
基本思想:函数第一次用到时才进行绑定(符号查找,重定位等),如果不用则不进行绑定。
方法:使用PLT(Procedure Linkage Table)的方法来实现。它使用了一些精巧的指令序列。

II. 理解PLT方法涉及的几个对象

1.调用外部函数的代码
代码里调用外部函数时,转向去调用PLT部分的代码。
2.GOT(global offset table)
保存外部函数的地址(当然也包括外部变量),初始加载时,每个函数的地址不是准确的地址,而是这个函数对应PLT项的Push指令的地址。第一次调用函数后填入真正的地址。
3.PLT
指令序列的集合,存放在虚拟内存空间的代码区域。分两大部分:PLT0, PLTn。PLT0的指令作用是压模块标志进栈和调用__dll_runtime_resolve函数。
4.Rel.PLT
PLT的重定位表。保存需要重定位函数的需要的信息,函数符号是函数__dll_runtime_resolve需要的两个参数(哪个动态链接库,哪个外部函数)中的一个。

III. 一个实例
1)
程序dynlib.c:
extern int foo;
extern int f();

static int g(){
    return f();
}

int function(void) {
    int k = g();
    int c = foo;
    return (c+k);
}

编译成共享动态库:
gcc -fIPC -shared dynlib.so dynlib.c

2)
函数g的代码:
000006d0 <g>:
6d0:   55                        push   %ebp
6d1:   89 e5                   mov    %esp,%ebp
6d3:   53                        push   %ebx
6d4:   83 ec 04                sub    $0x4,%esp
6d7:   e8 00 00 00 00          call   6dc <g+0xc>
6dc:   5b                         pop    %ebx
6dd:   81 c3 a4 11 00 00       add    $0x11a4,%ebx
6e3:   e8 6c fe ff ff          call   554 <_init+0x28>(运行PLT里f函数对应项的指令去获取f的地址)
6e8:   89 c0                   mov    %eax,%eax
6ea:   89 c0                   mov    %eax,%eax
6ec:   8b 5d fc                mov    0xfffffffc(%ebp),%ebx
6ef:   c9                      leave
6f0:   c3                      ret
6f1:   8d 76 00                lea    0x0(%esi),%esi

3)
PLT的内容,它是一些指令序列。
00000544 <.plt>:
544:   ff b3 04 00 00 00       pushl  0x4(%ebx)  (push library ID)
54a:   ff a3 08 00 00 00       jmp    *0x8(%ebx) (jump to function  _dll_runtime_resolve())

550:   00 00                   add    %al,(%eax)
552:   00 00                   add    %al,(%eax)
554:   ff a3 0c 00 00 00       jmp    *0xc(%ebx) 
55a:   68 00 00 00 00          push   $0x0
55f:   e9 e0 ff ff ff          jmp    544 <_init+0x18>

564:   ff a3 10 00 00 00       jmp    *0x10(%ebx)
56a:   68 08 00 00 00          push   $0x8
56f:   e9 d0 ff ff ff          jmp    544 <_init+0x18>
574:   ff a3 14 00 00 00       jmp    *0x14(%ebx)
57a:   68 10 00 00 00          push   $0x10
57f:   e9 c0 ff ff ff          jmp    544 <_init+0x18>
584:   ff a3 18 00 00 00       jmp    *0x18(%ebx)
58a:   68 18 00 00 00          push   $0x18
58f:   e9 b0 ff ff ff          jmp    544 <_init+0x18>

用(*0xc(%ebx) )获取GOT表里函数f对应保存的值,应该是f的地址值,但使用延迟绑定技术后,初始值时这个函数对应PLT项的push指令值,在本例中就是55a。第一次运行函数时,其实jump *0xc(%ebx),就跳转到55a,这条指令的效果就是跳转到下一条指令,相当于什么也没做。
接下来55f的指令就跳转到PL0的指令地址去,544指令的目的是压模块ID入栈,就是这个动态库是哪个动态库。54a指令去调用函数_dll_runtime_resolve去解析函数f的地址,填入GOT中f对应的项中,并且执行f。
第二次执行函数f时,还会跳转到指令554去,但这时 *0xc(%ebx)的值就是真正函数f的地址值了,这时554的效果就是转到函数f的地址去执行f了。

查看几个指令“55a,56a,57a,58a”,push指令后的整数是“0x0,0x8,0x10,0x18”,为什么每两个数之间差8?
push之后的整数标准这个外部函数在.rel.plt中偏移量。.rel.plt是一个结构数组。结构是:
typedef struct{
    Elf32_Addr r_offset;
    Elf32_Word r_info;
}Elf32_Rel;
sizeof(Elf32_Rel)=8。第一个结构偏移是0,第二个地址偏移就是0x8,第三个就是0x10,所以每两个整数之间差8。


4)
GOT的内容。
Hex dump of section '.got':
  0x00001880 0000055a(加载时,初始值是PLT中函数f对应项的push指令地址,本例中就是55a;第一次运行函数f后就写入函数f的地址)           
                    00000000(_dll_runtime_resolve地址) 
                    00000000(加载时写入库的名字) 
                    000017b0 ............Z...
  0x00001890 00000000 0000058a 0000057a 0000056a j...z...........
  0x000018a0 00000000 00000000 00000000 00000000 ................

5)
Rel.plt内容
Relocation section '.rel.plt' at offset 0x50c contains 4 entries:
Offset     Info    Type            Symbol's Value  Symbol's Name
0000188c  00001607 R_386_JUMP_SLOT       00000000  f
00001890  00001707 R_386_JUMP_SLOT       00000000  __register_frame_info
00001894  00001907 R_386_JUMP_SLOT       00000000  __deregister_frame_info
00001898  00001d07 R_386_JUMP_SLOT       00000000  __cxa_finalize
保存了Plt需要重定位的函数信息。PLT中push指令后的整数就是外部函数在.rel.plt中对应项的偏移量。

IV参考资料
《程序员的自我修养--链接、装载与库》作者:俞甲子,石凡,潘爱民
《漫谈兼容内核》系列,作者:毛德操

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值