Linux 下动态链接时函数的延时绑定分析

定义: 将函数地址的解析推迟到函数调用的时候。

实现方式:通过全局偏移量表【GOT】和过程链接表【PLT】之间的交互来完成。对于每一个调用了定义在共享库中函数的目标模块,那么它就会维持一个自己的GOT和PLT。其中GOT属于数据段的一部分、PLT属于代码段的一部分。

相关数据结构

  • 过程链接表【PLT】:该结构是一个数组,每一个数据成员占用16字节的大小。雁过留声,每一个在共享库中定义的函数都可以在该数组中找到身影。我们下来看一下具体的数据成员所代表的含义:

    • PLT[0]: 是一个特殊的条目,它存在的目的主要是为了跳转到动态链接器中,实现对所调用函数在GOT表中的地址的修改。这个我们稍后会详细谈到。
    • PLT[1]:存放的是系统的启动函数,完成对启动环境的初始化然后调用main函数并处理其返回值。
    • 之后的成员存放的是用户提供的具体函数。
  • 全局偏移量表【GOT】:该结构的存在形式也是一个数组,不同的是每一个条目所占用的大小是8字节,其中PLT表中的数据存放的是相对应的GOT表项的地址。最后对函数实际地址的查找也是通过PLT表的过渡最终由该表最实现的。当函数第一次调用时,GOT表项中的数据指向对应PLT表中的下一条指令。同样的,我们看一下它的成员所代表的含义:

    • GOT[0]与GOT[1]:存放的是函数地址解析时动态链接器需要的有关信息。
    • GOT[2]:存放的是动态链接器模块(Linux下是ld-linux.so)的入口地址
    • GOT[3]:系统的启动函数
    • 之后的数据成员存放的是与PLT对应的用户所提供的函数

用到的程序:
main 函数:

#include "sum.h"

int main (void) {
    int ret = sum(1,2);
    printf("ret:%d\n",ret);
    return 0;
}

子函数sum.h:

#ifndef _SUM_H_
#define _SUM_H_
#include <stdio.h>
int sum (int a,int b);
#endif

子函数sum.c

#include "sum.h"

int sum (int a,int b) {
    return a+b;
}

介绍完相关的数据结构后,我们接下来看一下具体的函数地址解析过程:
我们直接看一下可执行程序的反汇编代码:
在这里插入图片描述
我们可以看到sum@plt这个标志,其中@plt函数是编译器自己加的,我们可以看看里面的代码。
在这里插入图片描述
我们可以通过objdump -R 查看一个可执行程序的GOT地址
在这里插入图片描述
写在前面:
程序调用会首先进入相应的PLT条目,接着会执行图五中的三行代码,#后面所跟的十六进制数是该函数在GOT中对应的表项所在的地址,这几行代码分别完成的功能是

  • 跳转:根据图四图五我们可以判断目前该函数即sum在PLT表中对应的表项为PLT[2],地址0x4020所对应的GOT表项为GOT[4],也就是该函数在GOT表中所存放的地址。此时,程序由PLT[2] -> GOT[4]。目前GOT[4]中保存的数据为对应的PLT[2]中指令的下一条:即后面紧跟的压栈指令
  • 压栈:将调用函数sum的ID(0x1)压入栈中
  • 跳转到PLT[0]所在的位置。

接下来我们看一下跳转到PLT[0]后编译器所做的事情。
在这里插入图片描述

  • 我们可以看到首先将动态链接器的一个参数即GOT[1]压栈
  • 跳转至GOT[2]即动态链接器的入口地址。此时动态链接器利用已经压入栈的两个参数生成该函数(sum)的实际运行地址,然后利用该地址重写GOT[4],并将程序的控制权交给所调用函数。

  • 当后续目标模块中调用函数sum时,该函数还是首先会进入PLT[2]条目,然后跳转到GOT[4],不同的是,此时程序间接跳转会将控制直接转移到sum调用。至此,函数地址的解析便告一段落。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值