延迟绑定(PLT)

ELF动态连接时,会PLT(procesure linkage table)的方式来进程链接其他模块的函数。就是不会把所有的函数都链接好,而是在第一次去调用的时候去连接

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
        puts("Hello World\n");
        exit(0);
}

我的ubantu是64位的,要编译32为的可执行文件,要加参数-m32, 但是还要安装支持32位的插件

sudo apt-get install build-essential module-assistant 
sudo apt-get install gcc-multilib g++-multilib

在通过下面的命令来进行编译,即可生成32位的可以行文档

gcc -m32 -no-pie -g -o plt plt.c

通过objdump 命令可以查看到plt的段信息

kayshi@ubuntu:~/code/compile/dynamic_link$ objdump -h plt

 11 .plt          00000040  080482f0  080482f0  000002f0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt.got      00000008  08048330  08048330  00000330  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 ..............

 20 .dynamic      000000e8  08049f14  08049f14  00000f14  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000004  08049ffc  08049ffc  00000ffc  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got.plt      00000018  0804a000  0804a000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  1. 使用gdb plt来查看内部是怎么链接的
kayshi@ubuntu:~/code/compile/dynamic_link$ gdb plt

(gdb) disass main //使用这个命令可以打印main函数的指令的位置和汇编的信息
Dump of assembler code for function main:
   0x08048456 <+0>:	lea    0x4(%esp),%ecx
   0x0804845a <+4>:	and    $0xfffffff0,%esp
   0x0804845d <+7>:	pushl  -0x4(%ecx)
   0x08048460 <+10>:	push   %ebp
   0x08048461 <+11>:	mov    %esp,%ebp
   0x08048463 <+13>:	push   %ebx
   0x08048464 <+14>:	push   %ecx
   0x08048465 <+15>:	call   0x8048390 <__x86.get_pc_thunk.bx>
   0x0804846a <+20>:	add    $0x1b96,%ebx
   0x08048470 <+26>:	sub    $0xc,%esp
   0x08048473 <+29>:	lea    -0x1af0(%ebx),%eax
   0x08048479 <+35>:	push   %eax
   0x0804847a <+36>:	call   0x8048300 <puts@plt> //这里是调用put函数的位置
   0x0804847f <+41>:	add    $0x10,%esp
   0x08048482 <+44>:	sub    $0xc,%esp
   0x08048485 <+47>:	push   $0x0
   0x08048487 <+49>:	call   0x8048310 <exit@plt>
End of assembler dump.
(gdb) b *0x0804847a //在调用put函数的地址前加断点,
Breakpoint 1 at 0x804847a: file plt.c, line 6.
(gdb) r //运行
Starting program: /home/kayshi/code/compile/dynamic_link/plt 

Breakpoint 1, 0x0804847a in main (argc=1, argv=0xffffd5b4) at plt.c:6
6		puts("Hello World\n");
(gdb) 

通过上面可以看出put函数是链接函数,名字被修改成了puts@plt 位置在0x8048300。对比上面objdump 显示的段信息可以得知,这个地址位于.plt段的内部。

  1. 通过si进行单步执行

可以通过x/4i $pc 查看当前pc的位置和该位置下面的地址及汇编信息,4表示显示4条

(gdb) si
0x08048300 in puts@plt ()
(gdb) x/4i $pc
=> 0x8048300 <puts@plt>:	jmp    *0x804a00c
   0x8048306 <puts@plt+6>:	push   $0x0
   0x804830b <puts@plt+11>:	jmp    0x80482f0
   0x8048310 <exit@plt>:	jmp    *0x804a010
(gdb) 

这里可以得知 puts@plt会跳到*0x804a00c内部的地址区执行,带星号表示会到这个地址内部的地址去执行,类似指针。

  1. 产看一下0x804a00c放的是什么地址 x/wr 0x804a00c
(gdb) x/wr 0x804a00c
0x804a00c:	0x08048306
(gdb) 

放的是0x08048306,这里说明0x804a00c里面的地址还没有执行put的的地址,而是0x08048306,也就是上面命0x08048300下面的地址。如果链接已经完成的话0x804a00c会放put的地址,现在只是继续向下执行。

  1. 继续输入si执行
(gdb) si
0x08048306 in puts@plt ()
(gdb) x/4i $pc
=> 0x8048306 <puts@plt+6>:	push   $0x0  //执行push
   0x804830b <puts@plt+11>:	jmp    0x80482f0
   0x8048310 <exit@plt>:	jmp    *0x804a010
   0x8048316 <exit@plt+6>:	push   $0x8
(gdb) si
0x0804830b in puts@plt ()
(gdb) x/4i $pc
=> 0x804830b <puts@plt+11>:	jmp    0x80482f0 //跳到这个地址,.plt的开头地址
   0x8048310 <exit@plt>:	jmp    *0x804a010
   0x8048316 <exit@plt+6>:	push   $0x8
   0x804831b <exit@plt+11>:	jmp    0x80482f0
(gdb) si
0x080482f0 in ?? ()
(gdb) x/4i $pc
=> 0x80482f0:	pushl  0x804a004 //内部是0xf7ffd940
   0x80482f6:	jmp    *0x804a008
   0x80482fc:	add    %al,(%eax)
   0x80482fe:	add    %al,(%eax)
(gdb) si
0x080482f6 in ?? ()
(gdb) x/4i $pc
=> 0x80482f6:	jmp    *0x804a008  //跳到这里地址内部的地址执行
   0x80482fc:	add    %al,(%eax)
   0x80482fe:	add    %al,(%eax)
   0x8048300 <puts@plt>:	jmp    *0x804a00c
(gdb) x/wr 0x804a008  //查看0x804a008  放的是0xf7fead90,这个是链接器ld的地址
0x804a008:	0xf7fead90
(gdb) si
0xf7fead90 in ?? () from /lib/ld-linux.so.2 //进入链接器执行
(gdb) x/4i $pc
=> 0xf7fead90:	push   %eax
   0xf7fead91:	push   %ecx
   0xf7fead92:	push   %edx
   0xf7fead93:	mov    0x10(%esp),%edx
(gdb) 

通过这可以发现本来在在.plt的300 -30b这段地址执行,后来跳到2f0这个地址去执行了,这个地址是.plt的首地址,在开始这个位置存在调用链接器ld的指令。就是本来在这个的段的中间执行,现在pc又指向这个段的开始执行,那么随着指针向下移动。这个0x8048300 最开始执行puts@plt函数的地址,还会再次执行。就是在这个过程程puts@plt地址会指向真正的put函数。 因为当指针再次指向0x8048300 之前 ,调用的链接器ld的地址,就是上面的0x804a008 这个地址内部的地址,链接器ld会把puts@plt原本指向0x804a00c这个地址内部的地址,由原来的0x08048306变成put函数的首地址。

  1. 多次输入ni快速执行,跳过ld 的过程
(gdb) ni
0xf7feadab in ?? () from /lib/ld-linux.so.2 //ld链接完成了
(gdb) x/4i $pc
=> 0xf7feadab:	ret    $0xc
   0xf7feadae:	xchg   %ax,%ax
   0xf7feadb0:	push   %esp
   0xf7feadb1:	addl   $0x8,(%esp)
(gdb) ni
0xf7e47360 in puts () from /lib32/libc.so.6 //0xf7e47360 是libc.so下的put函数的地址位置
(gdb) x/4i $pc
=> 0xf7e47360 <puts>:	push   %ebp
   0xf7e47361 <puts+1>:	mov    %esp,%ebp
   0xf7e47363 <puts+3>:	push   %edi
   0xf7e47364 <puts+4>:	push   %es
  1. 查看一次0x804a00c的地址是否更改
(gdb) x/wr 0x804a00c
0x804a00c:	0xf7e47360
(gdb) 

到这里ld链接器,真正的吧puts@plt链接到了put函数上.

puts@plt -> 0x804a00c->0x08048306
变成了
puts@plt -> 0x804a00c->0xf7e47360
接下来就是执行put函数了,如果这个进程再次使用put函数,就可以直接跳到put函数指定的位置执行了。因为地址确定了。
这是动态链接的延迟绑定(PLT)

总结:通过上面的执行过程可以发现。需要动态链接的函数,都会以别名的方式放在.plt这个段(例如puts@plt),他们指向的位置是.plat.got 这里,如果地址确定好了,就直接执行函数,如果没有确定好。就会回到.plt的开头位置去调用链接器ld来把地址确定好之后。就可以执行具体的函数了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值