实例了解GOT,PLT和动态链接

news2024/11/17 1:50:46

深入了解GOT,PLT和动态链接

我们使用一个简单的例子来了解动态链接库的链接过程,以及在这个过程中使用到的GOT和PLT的作用是什么。

文件准备

代码结构如下所示:

[root@localhost test]# tree .
.
├── main.c
└── symbol.c

symbol.c的内容如下:

// symbol.c
int my_var = 42;
int my_func(int a, int b) {
    return a + b;
}

使用如下脚本进行编译:

gcc -g -m32 -masm=intel -shared -fPIC symbol.c -o libsymbol.so

如果编译不成功,使用如下命令安装32位的版本的c/c++库。

yum install glibc-devel.i686
yum install libstdc++-devel.i686

另一个文件是main.c, 调用该动态链接库,代码如下:

// main.c
int var = 10;
extern int my_var;
extern int my_func(int, int);

int main() {
    int a, b;
    a = var;
    b = my_var;
    return my_func(a, b);
}

使用下面的代码进行编译。

# 位置相关
gcc -g -m32 -masm=intel -L. -lsymbol -no-pie -fno-pic main.c libsymbol.so -o main
# 位置无关
gcc -g -m32 -masm=intel -L. -lsymbol main.c libsymbol.so -o main_pi

当目录中生成了main、main_pi、libsymbol.so时,准备工作结束。

动态链接分析

我们使用readelf -S main |egrep '.plt|.got'查看可执行文件中的plt和got相关的段。

[Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
[12] .plt              PROGBITS        080483d0 0003d0 000030 04  AX  0   0 16
[21] .got              PROGBITS        08049ff4 000ff4 00000c 04  WA  0   0  4
[22] .got.plt          PROGBITS        0804a000 001000 000014 04  WA  0   0  4

这里需要对.got.plt的地址0804a000有个印象。

进入gdb调用该程序:

(gdb) disass main
Dump of assembler code for function main:
   0x0804853d <+0>:     lea    0x4(%esp),%ecx
   0x08048541 <+4>:     and    $0xfffffff0,%esp
   0x08048544 <+7>:     pushl  -0x4(%ecx)
   0x08048547 <+10>:    push   %ebp
   0x08048548 <+11>:    mov    %esp,%ebp
   0x0804854a <+13>:    push   %ecx
   0x0804854b <+14>:    sub    $0x14,%esp
   0x0804854e <+17>:    mov    0x804a018,%eax
   0x08048553 <+22>:    mov    %eax,-0xc(%ebp)
   0x08048556 <+25>:    mov    0x804a01c,%eax
   0x0804855b <+30>:    mov    %eax,-0x10(%ebp)
   0x0804855e <+33>:    sub    $0x8,%esp
   0x08048561 <+36>:    pushl  -0x10(%ebp)
   0x08048564 <+39>:    pushl  -0xc(%ebp)
   0x08048567 <+42>:    call   0x80483e0 <my_func@plt>
   0x0804856c <+47>:    add    $0x10,%esp
   0x0804856f <+50>:    mov    -0x4(%ebp),%ecx
   0x08048572 <+53>:    leave
   0x08048573 <+54>:    lea    -0x4(%ecx),%esp
   0x08048576 <+57>:    ret
End of assembler dump.

我们在call 0x80483e0 <my_func@plt>这一句上下一个断点。

(gdb) b *0x08048567
Breakpoint 1 at 0x8048567: file main.c, line 9.

使用run运行程序到断点处。

(gdb) r
Starting program: /home/work/cpp_proj/test/main
Missing separate debuginfos, use: yum debuginfo-install glibc-2.28-164.el8.i686

Breakpoint 1, 0x08048567 in main () at main.c:9
9           return my_func(a, b);

查看一下main,确实停在了断点处:

(gdb) disass main
Dump of assembler code for function main:
   0x0804853d <+0>:     lea    0x4(%esp),%ecx
   0x08048541 <+4>:     and    $0xfffffff0,%esp
   0x08048544 <+7>:     pushl  -0x4(%ecx)
   0x08048547 <+10>:    push   %ebp
   0x08048548 <+11>:    mov    %esp,%ebp
   0x0804854a <+13>:    push   %ecx
   0x0804854b <+14>:    sub    $0x14,%esp
   0x0804854e <+17>:    mov    0x804a018,%eax
   0x08048553 <+22>:    mov    %eax,-0xc(%ebp)
   0x08048556 <+25>:    mov    0x804a01c,%eax
   0x0804855b <+30>:    mov    %eax,-0x10(%ebp)
   0x0804855e <+33>:    sub    $0x8,%esp
   0x08048561 <+36>:    pushl  -0x10(%ebp)
   0x08048564 <+39>:    pushl  -0xc(%ebp)
=> 0x08048567 <+42>:    call   0x80483e0 <my_func@plt>
   0x0804856c <+47>:    add    $0x10,%esp
   0x0804856f <+50>:    mov    -0x4(%ebp),%ecx
   0x08048572 <+53>:    leave
   0x08048573 <+54>:    lea    -0x4(%ecx),%esp
   0x08048576 <+57>:    ret

使用单步,进入my_func@plt中

(gdb) si
0x080483e0 in my_func@plt ()

查看my_func@plt的内容:

(gdb) x/3i $pc
=> 0x80483e0 <my_func@plt>:     jmp    *0x804a00c
   0x80483e6 <my_func@plt+6>:   push   $0x0
   0x80483eb <my_func@plt+11>:  jmp    0x80483d0

第一步是一个地址跳转,我们查看一下0x804a00c的内容

(gdb) x/4xw 0x804a00c
0x804a00c <my_func@got.plt>:    0x080483e6      0xf7e2a0f0      0x00000000      0x0000000a

我们发现0x804a00c处的内容是0x080483e6,即jmp *0x804a00c的下一行,即跳转到下一行执行。

接着是执行push $0x0,接着又跳转到0x80483d0执行。

我们查看jmp 0x80483d0处的内容:

(gdb) x/2i 0x80483d0
   0x80483d0:   pushl  0x804a004
   0x80483d6:   jmp    *0x804a008

我们看到后面会跳转到0x804a008处执行,在这之前我们提到过0x804a000是.got.plt的地址。

.plt.got表项前三个位置, 分别是:

  • got[0]: 本ELF动态段(.dynamic段)的装载地址
  • got1: 本ELF的link_map数据结构描述符地址
  • got2: _dl_runtime_resolve函数的地址

0x804a004则是调用该函数的参数, 且值为got1, 即本ELF的link_map的地址。

0x804a008正好是第三项got2, 即_dl_runtime_resolve函数的地址。

因此jmp *0x804a008作用是跳转到_dl_runtime_resolve执行加载。

下面我们打印一下,验证一下分析。首先0x804a008处存储的是0xf7fe5090。

(gdb) x/4xw 0x804a000
0x804a000:      0x08049f04      0xf7ffd9a0      0xf7fe5090      0x080483e6

打印0xf7fe5090处的内容,确实是进入了_dl_runtime_resolve中。

(gdb) x/12i 0xf7fe5090
   0xf7fe5090 <_dl_runtime_resolve>:    endbr32
   0xf7fe5094 <_dl_runtime_resolve+4>:  push   %eax
   0xf7fe5095 <_dl_runtime_resolve+5>:  push   %ecx
   0xf7fe5096 <_dl_runtime_resolve+6>:  push   %edx
   0xf7fe5097 <_dl_runtime_resolve+7>:  mov    0x10(%esp),%edx
   0xf7fe509b <_dl_runtime_resolve+11>: mov    0xc(%esp),%eax
   0xf7fe509f <_dl_runtime_resolve+15>: call   0xf7fdec10 <_dl_fixup>
   0xf7fe50a4 <_dl_runtime_resolve+20>: pop    %edx
   0xf7fe50a5 <_dl_runtime_resolve+21>: mov    (%esp),%ecx
   0xf7fe50a8 <_dl_runtime_resolve+24>: mov    %eax,(%esp)
   0xf7fe50ab <_dl_runtime_resolve+27>: mov    0x4(%esp),%eax
   0xf7fe50af <_dl_runtime_resolve+31>: ret    $0xc

_dl_runtime_resolve实际上做了两件事:

  • 解析出my_func的地址并将值填入.got.plt中
  • 跳转执行真正的my_func函数.

验证前后过程,确实将0x804a00c处的值修改成了my_func的值。

我们可以在0x80483d6: jmp *0x804a008语句上下一个断点,打印0x804a00c前后的值的变化,可以看到确实发生了变化。可以看到, 在_dl_runtime_resolve之前, 0x804a00c地址的值为0x080483e6,即下一条指令。而运行之后, 该地址的值变为0xf7fb845d, 正是my_func的加载地址!

也就是说, my_func函数的地址是在第一次调用时, 才通过连接器动态解析并加载到.got.plt中的. 而这个过程, 也称之为延时加载或者惰性加载

(gdb) x/xw 0x804a00c
0x804a00c <my_func@got.plt>:    0x080483e6
(gdb) x/xw 0x804a00c
0x804a00c <my_func@got.plt>:    0xf7fb845d

最后打印一下0xf7fb845d的值,看看是不是my_func。

(gdb) x/4i 0xf7fb845d
   0xf7fb845d <my_func>:        push   %ebp
   0xf7fb845e <my_func+1>:      mov    %esp,%ebp
   0xf7fb8460 <my_func+3>:      call   0xf7fb8474 <__x86.get_pc_thunk.ax>
   0xf7fb8465 <my_func+8>:      add    $0x1b9b,%eax

整个过程可以参考下图,对于my_func第一次执行和后续执行,行为是不一样的。

动态链接的过程

这个过程是不是似曾相识,通常我们在写后台的接口时,当查询完数据后,通常会将数据以插入到redis中,以便下一次访问时可以快速访问到。这里也是这样的机制。

而对于.plt段,就类似于一个后台查询接口,对于.got段,就类似于数据库,对于.plt.got段,就类似于redis缓存。

总结

动态库的加载过程相比于静态库是非常复杂的,其中使用到了.got,.plt和.plt.got段。对于这三个段,可以将其与我们熟悉的CRUD接口进行类比,.plt段,就类似于一个后台查询接口,.got段,就类似于数据库, plt.got段,就类似于redis缓存,是给.plt段查询做的缓存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/452191.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊布隆过滤器

目录 一、什么是布隆过滤器&#xff1f; 二、使用场景 三、原理 四、使用 4.1、Guava实现 4.2、Redisson实现 一、什么是布隆过滤器&#xff1f; 布隆过滤器&#xff08;英语&#xff1a;Bloom Filter&#xff09;是1970年由布隆提出的&#xff0c;是一种数据结构。它实际…

推式配货(Push)、拉式配货(Pull)和配送需求计划(DRP)的区别

随着电子商务的迅猛发展&#xff0c;物流配送服务已然成为企业竞争最为核心的环节&#xff0c;一个全面、完善的物流配送方案&#xff0c;能够帮助企业满足客户交期、节约运输和库存成本&#xff0c;促进各环节沟通&#xff0c;提高生产稳定性。同时&#xff0c;物流配送的许多…

如何在Java中高效地实现数字的反转和字符串的拼接?

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

2023爱分析·工业互联网解决方案-设备后市场服务市场厂商评估报告

01 研究范围定义 “十四五”期间工信部等发布《“十四五”智能制造发展规划》&#xff0c;强调智能制造是我国制造强国主攻方向&#xff0c;加快推动智能制造发展&#xff0c;深化智能推广应用&#xff0c;开拓制造业数字化转型升级新路径&#xff0c;智能制造市场发展潜力进…

Pyqt应用相关之与Excel的联接

利用所学相关Pyqt实现对于Excel文件内部数据的处理&#xff0c;首先需要获取Excel的数据&#xff0c;在获取后进行保存处理即可完成相应的操作。 def save_data_btn_click(self):dir self.save_dir_text.text().strip()self.data_frame_group.to_excel(dir output.xlsx,sheet…

IMX6ULLPRO交叉编译第一个APP和第一个led驱动

目录 配置交叉编译工具链 永久生效 测试交叉编译工具链 第一个交叉编译程序 开发板运行 LED第一个驱动 开发板下操作 执行测试程序 配置交叉编译工具链 交叉编译工具链用来在 Ubuntu 主机上编译应用程序&#xff0c;而这些应用程序是在 ARM 等其他平台上运行…

GPT对SaaS领域有什么影响?

GPT火了&#xff0c;Chat GPT真的火了。 突然之间&#xff0c;所有人都在讨论AI&#xff0c;最初的访客是程序员、工程师、AI从业者&#xff0c;从早高峰写字楼电梯里讨论声&#xff0c;到村里大爷们的饭后谈资&#xff0c;路过的狗子都要和它讨论两句GPT的程度。 革命的前夜…

图像配准(匹配)与变化检测

文章目录 简介A Survey on Deep Learning-Based Change Detection from High-Resolution Remote Sensing Images 2022变化检测的基本框架基于神经网络的变化检测特征抽取变化检测的粒度场景级的变化检测&#xff08;SLCD&#xff09;目标级别的变化检测 Deep Learning-Based Ch…

全志v851s uart3 设置成普通串口收发

本文转载自&#xff1a;https://bbs.aw-ol.com/topic/3281/ 由于UART0 被设定为系统dubug 输出&#xff08;简单来说就是将ttyS0 设定为console&#xff09;&#xff0c;所以使用UART3 作为普通的串口&#xff0c;进行与别的设备通信。 1. 查看硬件电路图SCH_Schematic1_2022…

安全防御 --- APT、密码学

APT 深度包检测技术&#xff1a;将应用层内容展开进行分析&#xff0c;根据不同的设定从而做出不同的安全产品。 深度流检测技术&#xff1a;与APS画像类似。会记录正常流量行为&#xff0c;也会将某些应用的行为画像描述出来。也可将加密流量进行判断&#xff0c;并执行相应措…

揭秘移动云大会展区前沿科技

2023年4月25日-26日 我们苏州金鸡湖国际会议中心见&#xff01; 1场重磅主论坛、10场分论坛、2600㎡展区 数字中国新未来 尽在2023移动云大会 2023移动云大会设有中国移动和合作伙伴两大展区&#xff0c;联合40余家优质合作伙伴&#xff0c;全方位展示移动云在自主能力、行…

实验05:算法设计策略的比较与选择

1.实验目的&#xff1a; 比较同一问题&#xff0c;采用不同策略设计不同算法&#xff0c;分析和比较算法的性能。 2.实验内容&#xff1a; 自学第10章的10.1.1至10.1.3, 总结分析编程实现简单算法、分治法和动态规划算法的理论复杂度&#xff0c;编程实现这些算法。 3.实验…

C语言复习之顺序表(十五)

&#x1f4d6;作者介绍&#xff1a;22级树莓人&#xff08;计算机专业&#xff09;&#xff0c;热爱编程&#xff1c;目前在c阶段>——目标C、Windows&#xff0c;MySQL&#xff0c;Qt&#xff0c;数据结构与算法&#xff0c;Linux&#xff0c;多线程&#xff0c;会持续分享…

docker容器内的应用利用k8s configmap做配置中心

ConfigMap 能带来什么好处&#xff1f; 传统的应用服务都有自己的配置文件&#xff0c;各自配置文件存储在服务所在节点。如果配置出现变更&#xff0c;就需要对应节点的配置文件。Kubernetes 利用了 Volume 功能&#xff0c;完整设计了一套配置中心&#xff0c;其核心对象就是…

基于Java+Spring+vue+element实现旅游信息管理平台系统

基于JavaSpringvueelement实现旅游信息管理平台系统 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式 文…

基于模型预测(MPC)的四轮转向车辆轨迹规划(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 在轨迹跟踪应用领域&#xff0c;通常 MPC 建模可根据机器人的控制方式选择基于运动学运动状态方程建模或者基于动力学运动状态…

深入探讨车载CAN协议的工作原理和应用场景

CAN概述 CAN&#xff08;Controller Area Network&#xff09;总线协议是一种数据通信协议&#xff0c;最初是由Bosch公司开发&#xff0c;用于汽车领域中的内部通讯。 CAN总线协议是一种串行通信协议&#xff0c;支持多主机和多从机之间的通讯&#xff0c;可以在不同的控制单…

典型的高可用设计(一):MinIO

为了更好的了解高可用设计&#xff0c;将各类常用服务关于高可用的设计原理汇总到一起&#xff0c;通过横向对比的方式去发现这些典型设计的共同之处和差异点。 一、部署方式 MinIO 有单机单硬盘、单机多硬盘、多机多硬盘三种部署模式。单机单硬盘存在单点风险&#xff0c;数据…

ElasticSearch 部署及安装ik分词器

ansiable playbook链接&#xff1a; https://download.csdn.net/download/weixin_43798031/87719490 需要注意的点&#xff1a;公司es集群现以三个角色部署分别为 Gateway、Master、Data 简单的理解可以理解为在每台机器上部署了三个es&#xff0c;以端口和配置文件来区分这三…