理解PLT表和GOT表

1 简介

  现代操作系统都是通过库来进行代码复用,降低开发成本提升系统整体效率。而库主要分为两种,一种是静态库,比如windows的.lib文件,macos的.a,linux的.a,另一种是动态库,比如windows的dll文件,macos的.dylib,linux的so。静态库本身就是中间产物的ar打包link阶段会参与直接的产物生成,而动态库本身已经是完整的二进制文件,link阶段只会进行符号定位。
  传统意义上认为静态链接的函数加载和执行效率要高于动态链接,这是由于静态链接在编译-链接阶段就能够确定函数的库入口地址。而动态链接并不是所有场景下都能够提前知道入口地址,可能只有需要加载的时候才需要确定。为了实现这一点,和提升加载效率,便诞生了PLT和GOT表。

2 PLT表和GOT表

2.1 GOT表

  GOT(Global Offset Table,全局偏移表)是为了实现地址无关代码而引入的一个偏移表格。函数调用或者数据访问时先访问GOT表,再通过该表中对应表项的偏移在动态库映射内存中找到具体的函数地址和数据地址。

  为什么需要使用GOT表进行重定位?

  1. 动态库需要生成地址无关代码方便动态库加载时定位函数地址或者数据地址,否则动态库的动态共享的优势不再存在,因此需要生成地址无关代码;
  2. GOT表格存储在数据区而不属于代码段,这样可以保证各个进程各自持有一份各自的GOT表根据自己的内存映射地址进行调整。

  GOT表需要考虑哪些内容?

  1. 模块间数据和函数地址访问。模块内不需要考虑,模块内使用模块内相对偏移即可。
  2. 全局数据,比如extern表示的数据。

2.2 PLT表

  PLT(Procedure Link Table,过程绑定表)是为了实现延迟绑定的地址表格。由于动态链接是在运行期链接并且进行重定位,本来直接访问的内存可能变成间接访问,会导致性能降低。ELF采用延迟绑定来缓解性能问题,其假设就是动态库中并不是所有的函数与数据都会用到,类似copy-on-write,仅仅在第一次符号被使用时才进行相关的重定位工作,避免对一些不必要的符号的重定位。
  ELF使用PLT(Procedure Linkage Table)实现延迟绑定。在进行重定位时每个符号需要了解符号在那个模块(模块ID)以及符号。当调用外部模块中的函数时,PLT为每个外部函数符号添加了PLT项,然后通过PLT项跳转到GOT表再到最终的函数地址。也就是说第一次调用会间接调用,之后可以直接通过PLT确认调用地址调用。

  PLT解决了哪些性能问题?

  1. 符号解析。动态库加载时不需要加载所有符号,只需要加载部分能够大幅度降低加载耗时;
  2. 避免重复解析。当外部调用动态库内函数或者访问数据地址时需要搜索符号表访问找到对应的项,对于比较大的动态库这个过程比较耗时。通过延迟加载只会在第一次比较耗时,之后不会重复解析;

3 深入理解GOT和PLT

  我们简单做个试验研究下PLT和GOT。下面是一段简单的代码,我们将其编译成动态库libadd.so

#include <cstdio>
#include <cmath>static int myadd(const int a, const int b){return a + b;
}int myabs(const int a){return std::abs(a);
}void test(const int a, const int b){printf("%d %d", myadd(a, b), myabs(a));
}

  下面是生成的动态库的反汇编:

0000000000000630 <_Z5myabsi>:630:	55                   	push   %rbp631:	48 89 e5             	mov    %rsp,%rbp634:	89 7d fc             	mov    %edi,-0x4(%rbp)637:	8b 45 fc             	mov    -0x4(%rbp),%eax63a:	89 c1                	mov    %eax,%ecx63c:	f7 d9                	neg    %ecx63e:	0f 49 c1             	cmovns %ecx,%eax641:	5d                   	pop    %rbp642:	c3                   	retq   643:	66 66 66 66 2e 0f 1f 	data16 data16 data16 nopw %cs:0x0(%rax,%rax,1)64a:	84 00 00 00 00 00 0000000000000650 <_Z4testii>:650:	55                   	push   %rbp651:	48 89 e5             	mov    %rsp,%rbp654:	48 83 ec 10          	sub    $0x10,%rsp658:	89 7d fc             	mov    %edi,-0x4(%rbp)65b:	89 75 f8             	mov    %esi,-0x8(%rbp)65e:	8b 7d fc             	mov    -0x4(%rbp),%edi661:	8b 75 f8             	mov    -0x8(%rbp),%esi664:	e8 27 00 00 00       	callq  690 <_ZL5myaddii>669:	89 45 f4             	mov    %eax,-0xc(%rbp)66c:	8b 7d fc             	mov    -0x4(%rbp),%edi66f:	e8 bc fe ff ff       	callq  530 <_Z5myabsi@plt>674:	8b 75 f4             	mov    -0xc(%rbp),%esi677:	89 c2                	mov    %eax,%edx679:	48 8d 3d 2d 00 00 00 	lea    0x2d(%rip),%rdi        # 6ad <_fini+0x9>680:	b0 00                	mov    $0x0,%al682:	e8 99 fe ff ff       	callq  520 <printf@plt>687:	48 83 c4 10          	add    $0x10,%rsp68b:	5d                   	pop    %rbp68c:	c3                   	retq   68d:	0f 1f 00             	nopl   (%rax)0000000000000690 <_ZL5myaddii>:690:	55                   	push   %rbp691:	48 89 e5             	mov    %rsp,%rbp694:	89 7d fc             	mov    %edi,-0x4(%rbp)697:	89 75 f8             	mov    %esi,-0x8(%rbp)69a:	8b 45 fc             	mov    -0x4(%rbp),%eax69d:	03 45 f8             	add    -0x8(%rbp),%eax6a0:	5d                   	pop    %rbp6a1:	c3                   	retq   

  从上面能够看到对于内部函数的调用直接使用的内部偏移,比如myadd2中调用myadd就是callq 690 <_ZL5myaddii>。而调用printfmyabs就是callq 520 <printf@plt>callq 530 <_Z5myabsi@plt>
  下来我们分析下这个跳转指令。e8表示偏移跳转,后面跟的就是跳转地址偏移,即0xfffffebc,实际跳转地址便是off + rip=0xfffffebc + 674=0x530。(需要注意的是执行 callq 指令之前,RIP 指向 callq 指令的下一条指令,因此RIP是674)。

66f:	e8 bc fe ff ff       	callq  530 <_Z5myabsi@plt>

  接下来我们找到0x530的地址能够看到该地址又跳转到了510即plt表项,最终跳转到0x200af2(%rip)从注释中能够看到是GOT的表项。

0000000000000510 <.plt>:510:	ff 35 f2 0a 20 00    	pushq  0x200af2(%rip)        # 201008 <_GLOBAL_OFFSET_TABLE_+0x8>516:	ff 25 f4 0a 20 00    	jmpq   *0x200af4(%rip)        # 201010 <_GLOBAL_OFFSET_TABLE_+0x10>51c:	0f 1f 40 00          	nopl   0x0(%rax)0000000000000520 <printf@plt>:520:	ff 25 f2 0a 20 00    	jmpq   *0x200af2(%rip)        # 201018 <printf@GLIBC_2.2.5>526:	68 00 00 00 00       	pushq  $0x052b:	e9 e0 ff ff ff       	jmpq   510 <.plt>0000000000000530 <_Z5myabsi@plt>:530:	ff 25 ea 0a 20 00    	jmpq   *0x200aea(%rip)        # 201020 <_Z5myabsi@@Base+0x2009f0>536:	68 01 00 00 00       	pushq  $0x153b:	e9 d0 ff ff ff       	jmpq   510 <.plt>

  接下来要查看GOT需要运行时查看,我们用GDB调试即可。首先在调用myabs的地方断点,单步进入,可以看到当前的代码:

(gdb) x /10i $pc
=> 0x7fffff1f0530 <_Z5myabsi@plt>:      jmpq   *0x200aea(%rip)        # 0x7fffff3f10200x7fffff1f0536 <_Z5myabsi@plt+6>:    pushq  $0x10x7fffff1f053b <_Z5myabsi@plt+11>:   jmpq   0x7fffff1f05100x7fffff1f0540 <__cxa_finalize@plt>: jmpq   *0x200a9a(%rip)        # 0x7fffff3f0fe00x7fffff1f0546 <__cxa_finalize@plt+6>:       xchg   %ax,%ax

  从上面的代码中能够看到需要跳转的地址是RIP+off=0x7fffff1f0536+0x200aea=0x7fffff3f1020。从下面的内容可以看到这个地址存储的是当前指令下一条执行的地址,即0x7fffff1f0536,也就是说这不是真正的函数地址还没有重定位。而上面的push $0x1就是预期这个符号在plt中的槽位编号。

(gdb) x /gx 0x7fffff3f1020
0x7fffff3f1020: 0x00007fffff1f0536
(gdb) x /gx 0x00007fffff1f0536
0x7fffff1f0536 <_Z5myabsi@plt+6>:       0xffd0e90000000168

  我们再单步几次就能看到基本能够确认这个过程是在进行符号解析:

(gdb) si
_dl_runtime_resolve_xsavec () at ../sysdeps/x86_64/dl-trampoline.h:71
71      ../sysdeps/x86_64/dl-trampoline.h: No such file or directory.
(gdb) x /3i $pc
=> 0x7fffff4178f0 <_dl_runtime_resolve_xsavec>: push   %rbx0x7fffff4178f1 <_dl_runtime_resolve_xsavec+1>:       mov    %rsp,%rbx0x7fffff4178f4 <_dl_runtime_resolve_xsavec+4>:       and    $0xffffffffffffffc0,%rsp

  退出当前函数,我们再看PLT表中的表项,可以看到已经被修改为_Z5myabsi的函数地址了。

(gdb) disass '_Z5myabsi@plt'
Dump of assembler code for function _Z5myabsi@plt:0x00007fffff1f0530 <+0>:     jmpq   *0x200aea(%rip)        # 0x7fffff3f10200x00007fffff1f0536 <+6>:     pushq  $0x10x00007fffff1f053b <+11>:    jmpq   0x7fffff1f0510
End of assembler dump.
(gdb) x /gx 0x7fffff3f1020
0x7fffff3f1020: 0x00007fffff1f0630
(gdb) x /gx 0x00007fffff1f0630
0x7fffff1f0630 <_Z5myabsi>:     0x8bfc7d89e5894855

4 总结

  PLT 和 GOT 是现代动态链接的核心机制,通过延迟绑定和地址无关性,提升了动态库的加载效率和灵活性。这些机制确保了代码复用及共享的优势,同时优化了性能。

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

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

相关文章

【java学习笔记】@Autowired注解 使用方法和作用 | 配合@Component注解使用 | IOC控制反转

原本在类中&#xff0c;要用什么对象&#xff0c;就直接new一个对象。这种原始的方式 是由应用本身去控制实例的。 用了Autowired注解后&#xff0c;就相当于把实例&#xff08;对象&#xff09;的控制权 交给外部容器来统一管理&#xff08;降低耦合&#xff09;。&#xff08…

LabVIEW无线齿轮监测系统

本案例介绍了基于LabVIEW的无线齿轮监测系统设计。该系统利用LabVIEW编程语言和改进的天牛须算法优化支持向量机&#xff0c;实现了无线齿轮故障监测。通过LabVIEW软件和相关硬件&#xff0c;可以实现对齿轮箱振动信号的采集、传输和故障识别&#xff0c;集远程采集、数据库存储…

SpringBoot+Vue的理解(含axios/ajax)-前后端交互前端篇

文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue&#xff08;前端&#xff09;axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 关于地址栏url和axios请求不一致VueJSPS…

jQuery小游戏(一)

jQuery小游戏&#xff08;一&#xff09; 嘻嘻&#xff0c;今天我们来写个jquery小游戏吧 首先&#xff0c;我们准备一下写小游戏需要准备的佩饰&#xff0c;如果&#xff1a;图片、音乐、搞怪的小表情 这里我准备了一些游戏中需要涉及到的图片 游戏中使用到的方法 eval() 函…

H3CNE-28-VRRP

虚拟网关冗余协议&#xff0c;Virtual Router Redundancy Protocotol 三层网关冗余技术对用户网关做冗余 VRRP配置示例 接口IP配置&#xff0c;略。 R1&#xff1a; int g0/0vrrp vrid 1 virtual 192.168.1.254vrrp vrid 1 priority 105 # 1-254,越大越优先R2&#xff1a; …

私有包上传maven私有仓库nexus-2.9.2

一、上传 二、获取相应文件 三、最后修改自己的pom文件

Alfresco Content Services dockerCompose自动化部署详尽操作

Alfresco Content Services docker社区部署文档 Alfresco Content Services简介 官方说明书 https://support.hyland.com/r/Alfresco/Alfresco-Content-Services-Community-Edition/23.4/Alfresco-Content-Services-Community-Edition/Using/Content/Folder-rules/Defining-…

rust feature h和 workspace相关知识 (十一)

feature 相关作用和描述 在 Rust 中&#xff0c;features&#xff08;特性&#xff09; 是一种控制可选功能和依赖的机制。它允许你在编译时根据不同的需求启用或禁用某些功能&#xff0c;优化构建&#xff0c;甚至改变代码的行为。Rust 的特性使得你可以轻松地为库提供不同的…

【Python-办公自动化】实现自动化输出json数据类型的分析报告和正逆转换

分析报告 import json from pprint import pprint, PrettyPrinterdef analyze_energy_data(file_path):"""能源数据分析与结构查看函数参数:file_path (str): JSON文件路径功能:1. 加载并解析JSON数据2. 显示数据结构概览3. 交互式结构探索"""…

麒麟操作系统服务架构保姆级教程(十四)iptables防火墙四表五链和防火墙应用案例

如果你想拥有你从未拥有过的东西&#xff0c;那么你必须去做你从未做过的事情 防火墙在运维工作中有着不可或缺的重要性。首先&#xff0c;它是保障网络安全的关键防线&#xff0c;通过设置访问控制规则&#xff0c;可精准过滤非法网络流量&#xff0c;有效阻挡外部黑客攻击、恶…

力扣25.k个一组翻转链表

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。你不能只是单纯的改变节点内部的值&…

Midjourney基础-常用修饰词+权重的用法大全

用好修饰词很关键 Midjourney要用除了掌握好提示词的写法&#xff0c;按照上一篇《做Midjourney最好图文教程-提示词公式以及高级参数讲解》画面主体 场景氛围 主体行为 构图方式 艺术风格 图像质量。 要画出有质感的内容我们必须要掌握好“修饰词”&#xff0c;这些修饰…

钉钉群机器人设置——python版本

钉钉群机器人设置——python版本 应用场景钉钉界面操作程序开发效果展示 应用场景 由于工作需要&#xff0c;很多项目执行程序后出现报错信息无法第一时间收到&#xff0c;因此实时预警对于监控程序还是有必要。&#xff08;仅个人观点&#xff09; 参考文档及博客&#xff1a…

doris:异常数据处理

在导入过程中&#xff0c;源数据列与目标列的数据类型可能存在不一致的情况。导入过程会对这些类型不一致的数据进行转换&#xff0c;但在转换过程中可能会出现字段类型不匹配、字段超长、精度不匹配等问题&#xff0c;从而导致转换失败。 为了处理这些异常情况&#xff0c;Do…

小白爬虫冒险之反“反爬”:无限debugger、禁用开发者工具、干扰控制台...(持续更新)

背景浅谈 小白踏足JS逆向领域也有一年了&#xff0c;对于逆向这个需求呢主要要求就是让我们去破解**“反爬机制”**&#xff0c;即反“反爬”&#xff0c;脚本处理层面一般都是decipher网站对request设置的cipher&#xff0c;比如破解一个DES/AES加密拿到key。这篇文章先不去谈…

【Elasticsearch】中数据流需要配置索引模板吗?

是的&#xff0c;数据流需要配置索引模板。在Elasticsearch中&#xff0c;数据流&#xff08;Data Streams&#xff09;是一种用于处理时间序列数据的高级结构&#xff0c;它背后由多个隐藏的索引组成&#xff0c;这些索引被称为后备索引&#xff08;Backing Indices&#xff0…

babylon.js-3:了解STL网格模型

网格模型上色 本篇文章主要介绍如何在 BabylonJS 中实现STL网格模型上色。 文章目录 网格模型上色运用场景概要延申正文加载器库的支持认识 OBJ 和 STL 文件GUI 色板选择器网格模型异步加载加载动画网格模型上色官方即将弃用 ImportMesh 而推荐使用 ImportMeshAsync 说明OBJ …

大数据治理实战:架构、方法与最佳实践

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 1. 引言 大数据治理是确保数据质量、合规性和安全性的重要手段&#xff0c;尤其在数据驱动决策和人工智能应用日益普及的背景下&…

【时时三省】(C语言基础)文件的随机读写

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 fseek 根据文件指针的位置和偏移量来定位文件指针 示例&#xff1a; 这个输出的就是ade seek&#xff3f;cur的意思是从当前偏移量 2就是从a往后偏移两个就是d 偏移量 SEEK&#xff3f;CUR…

均方误差(MSE)揭秘:预测模型的“真面目”

前言 在这个充满数据的世界里,我们需要各种方式来衡量一个模型的表现,尤其在回归问题中,均方误差(MSE)是我们非常常见的“好朋友”。它就像一位忠诚的侦探,默默为我们揭示预测值与实际值之间的真相。今天,让我们一起进入均方误差的世界,看看它是如何用简单却有效的方式…