linux动态链接

关于动态链接与静态链接简单理解:如果我的文章引用了别人的一部分文字,在我发布文章的时候把别人的段落复制到我的文章里面就属于静态连接,而做一个超链接让你们自己去看就属于动态链接了

PLT&GOT

在介绍PLT和GOT表之前,先列举一个例子

#include <stdio.h>
void print_banner()
{
    printf("Welcome to World of PLT and GOT\n");
}
int main(void)
{
    print_banner();
    return 0;
}

编译

gcc -Wall -g -o test.o -c test.c -m32

链接

gcc -o test test.o -m32

通过编译与链接,可以得到一个test.o与可执行文件test
通过objdump -d test.o可以查看文件的反汇编代码
在这里插入图片描述print_banner函数调用了printf函数,但是printf函数位于glibc动态库中,在编译和链接阶段,没有办法确定地址。只用在进程运行时,才可以确定地址,所以先用fc ff ff ff填充,也就是有符号数 -4 代替。
用重定位向来描述:这个地址在链接时要修正,它的修正值是根据printf地址(更确切的叫法应该是符号,链接器眼中只有符号,没有所谓的函数和变量)来修正,它的修正方式按相对引用方式。

在程序运行时,重定位,不能修改代码段的数据,只能修改数据段内的数据,所以要如何确定printf的真正地址呢?
运用的是链接时重定位,但是连接过程无法修改编译过程生成的汇编指令,而编译器也无法知道printf函数是在glibc运行库还是在其他.o中
所以链接器生成一段额外的小代码片段,通过这段代码来获取printf函数地址,并完成对它的调用

.text
...

// 调用printf的call指令
call printf_stub
...

printf_stub:
    mov rax, [printf函数的储存地址] // 获取printf重定位之后的地址
    jmp rax // 跳过去执行printf函数

.data
...
printf函数的储存地址:
  这里储存printf函数重定位后的地址

动态链接每个函数实际总结有了两点:

  • 1、需要存放外部函数地址的数据段
  • 2、获取数据段存放函数地址的一小段额外代码

所以就引出了GOT和PLT表
其中用来需要存放外部函数地址的数据段的数据表称为全局偏移表GOT, Global Offset Table),用来存放那一小段额外代码的数据表称为程序链接表PLT,Procedure Link Table)

可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址
在这里插入图片描述

也可以说PLT表中的每一项的数据内容都是对应的GOT表中一项的地址这个是固定不变的
在这里插入图片描述

延迟绑定

通过上边的过程,可以发现通过真个过程比较麻烦,所以Linux引入了延迟绑定

延迟绑定(Lazy Binding)的要求:即函数第一次被用到时才进行绑定。通过延迟绑定大大加快了程序的启动速度。而 ELF 则使用了PLT(Procedure Linkage Table,过程链接表)的技术来实现延迟绑定。

可以使用类似的代码来实现

//一开始没有重定位的时候将 printf@got 填成 lookup_printf 的地址
void printf@plt()
{
address_good:
    jmp *printf@got   
lookup_printf:
    调用重定位函数查找 printf 地址,并写到 printf@got
	goto address_good;//再返回去执行address_good
}

在链接成可执行文件test时,链接器将printf@got表项的内容填写lookup_printf标签的地址。

解释一下这段代码的流程,刚开始,printf@got 是 lookup_printf 函数的地址,这个函数用来寻找 printf() 的地址,然后写入 printf@got,lookup_printf 执行完成后会返回到 address_good,这样再 jmp 的话就可以直接跳到printf 来执行了

也就是说这样的机制的话如果不知道 printf 的地址,就去找一下,知道的话就直接去 jmp 执行 printf 了

参考中说明利用下面的命令可以反编译生成汇编代码,但我的没有显示,就借用一下大佬的图片吧

objdump -d test > test.asm

在这里插入图片描述
将第一项plt表改为了 common@plt ,因为objdump -d输出结果会使用错误的符号名

在这里插入图片描述
通过gdn的查看,我么可以明显看出,地址跳转到顺序
xxx@plt -> xxx@got -> xxx@plt -> 公共@plt -> _dl_runtime_resolve

我们可以看到它push了一个参数,而这个参数就相当于函数的ID

push   $0x0    //将数据压到栈上,作为将要执行的函数的参数
jmp    0x80482d0   //去到了第一个表项

补充:

ELF将GOT拆为了两个表叫做“.got”,".got.plt"。其中 .got 用来保存全局变量的引用地址,.got.plt 用来保存函数引用的地址,也就是说,所有对于外部函数的引用被分离到了 .got.plt 表中
在 i386 架构下,除了每个函数占用一个 GOT 表项外,GOT 表项还保留了3个公共表项,也即 got 的前3项,分别保存:
got [0]: 本 ELF 动态段 (.dynamic 段)的装载地址
got [1]:本 ELF 的 link_map 数据结构描述符地址
got [2]:_dl_runtime_resolve 函数的地址
动态链接器在加载完 ELF 之后,都会将这3地址写到 GOT 表的前3项

参考中所说的大佬的流程图
第一次调用:
在这里插入图片描述
再次调用:

在这里插入图片描述

参考链接1
参考链接2
参考链接3
参考链接4
参考链接4

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值