当前位置: 首页 > news >正文

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

相关文章:

理解PLT表和GOT表

1 简介 现代操作系统都是通过库来进行代码复用&#xff0c;降低开发成本提升系统整体效率。而库主要分为两种&#xff0c;一种是静态库&#xff0c;比如windows的.lib文件&#xff0c;macos的.a&#xff0c;linux的.a&#xff0c;另一种是动态库&#xff0c;比如windows的dll文…...

6 年没回老家过年了

今天是 2025 年的第一天&#xff0c;我们一家三口去了地坛庙会玩了会儿。 不是说过年的北京是空城吗&#xff1f;我愣是没抢到大年初一的门票&#xff0c;只好在咸鱼上溢价 40 买了两张票。 坐了一个小时的地坛终于到了&#xff0c;谁知迎来的是人山人海&#xff0c;同时小白牙…...

【原创改进】SCI级改进算法,一种多策略改进Alpha进化算法(IAE)

目录 1.前言2.CEC2017指标3.效果展示4.探索开发比5.定性分析6.附件材料7.代码获取 1.前言 本期推出一期原创改进——一种多策略改进Alpha进化算法&#xff08;IAE&#xff09;~ 选择CEC2017测试集低维&#xff08;30dim&#xff09;和高维&#xff08;100dim&#xff09;进行测…...

如何把一个python文件打包成一步一步安装的可执行程序

将一个 Python 文件打包成可执行程序&#xff08;如 .exe 文件&#xff09;&#xff0c;并实现一步一步的安装过程&#xff0c;通常需要以下步骤&#xff1a; 1. 将 Python 文件打包成可执行文件 使用工具将 Python 脚本打包成可执行文件&#xff08;如 .exe&#xff09;。常用…...

防火墙安全策略部署

目录&#xff1a; 一、实验拓扑&#xff1a; 二、实验要求&#xff1a; 三、需求分析&#xff1a; 四、详细设计&#xff1a; 五、实验步骤&#xff1a; 1.进行vlan划分&#xff1a; 2.IP配置&#xff1a; 3.云端服务配置&#xff1a; 4.划分子网&#xff1a; 5.防火墙…...

c++ map/multimap容器 学习笔记

1 map的基本概念 简介&#xff1a; map中所有的元素都是pair pair中第一个元素是key&#xff08;键&#xff09;&#xff0c;第二个元素是value&#xff08;值&#xff09; 所有元素都会根据元素的键值自动排序。本质&#xff1a; map/multimap 属于关联式容器&#xff0c;底…...

【解决方案】MuMu模拟器移植系统进度条卡住98%无法打开

之前在Vmware虚拟机里配置了mumu模拟器&#xff0c;现在想要移植到宿主机中 1、虚拟机中的MuMu模拟器12-1是目标系统&#xff0c;对应的目录如下 C:\Program Files\Netease\MuMu Player 12\vms\MuMuPlayer-12.0-1 2、Vmware-虚拟机-设置-选项&#xff0c;启用共享文件夹 3、复…...

日志收集Day007

1.配置ES集群TLS认证: (1)elk101节点生成证书文件 cd /usr/share/elasticsearch ./bin/elasticsearch-certutil cert -out config/elastic-certificates.p12 -pass "" --days 3650 (2)elk101节点为证书文件修改属主和属组 chown elasticsearch:elasticsearch con…...

虚拟机里网络设置-桥接与NAT

桥接&#xff08;Bridging&#xff09;和NAT&#xff08;网络地址转换&#xff0c;Network Address Translation&#xff09;是网络中的两种不同技术&#xff0c;主要用于数据包的处理和转发。以下是它们的主要区别&#xff1a; 1. 工作原理 桥接&#xff1a; 桥接工作在数据链…...

人工智能 - 1

深度强化学习&#xff08;Deep Reinforcement Learning&#xff09; 图神经网络&#xff08;Graph Neural Networks, GNNs&#xff09; Transformer 一种深度学习模型 大语言模型&#xff08;Large Language Models, LLMs&#xff09; 人工智能 • Marvin Minsky 将其定义…...

小程序-基础加强-自定义组件

前言 这次讲自定义组件 1. 准备今天要用到的项目 2. 初步创建并使用自定义组件 这样就成功在home中引入了test组件 在json中引用了这个组件才能用这个组件 现在我们来实现全局引用组件 在app.json这样使用就可以了 3. 自定义组件的样式 发现页面里面的文本和组件里面的文…...

Kafka 压缩算法详细介绍

文章目录 一 、Kafka 压缩算法概述二、Kafka 压缩的作用2.1 降低网络带宽消耗2.2 提高 Kafka 生产者和消费者吞吐量2.3 减少 Kafka 磁盘存储占用2.4 减少 Kafka Broker 负载2.5 降低跨数据中心同步成本 三、Kafka 压缩的原理3.1 Kafka 压缩的基本原理3.2. Kafka 压缩的工作流程…...

单词翻转(信息学奥赛一本通1144)

题目来源 信息学奥赛一本通&#xff08;C版&#xff09;在线评测系统 题目描述 1144&#xff1a;单词翻转 时间限制: 1000 ms 内存限制: 65536 KB 提交数:60098 通过数: 26099 【题目描述】 输入一个句子(一行)&#xff0c;将句子中的每一个单词翻转后输出。 【输入…...

DeepSeek 模型全览:探索不同类别的模型

DeepSeek 是近年来备受关注的 AI 研究团队&#xff0c;推出了一系列先进的深度学习模型&#xff0c;涵盖了大语言模型&#xff08;LLM&#xff09;、代码生成模型、多模态模型等多个领域。本文将大概介绍 DeepSeek 旗下的不同类别的模型&#xff0c;帮助你更好地理解它们的特点…...

我的2024年年度总结

序言 在前不久&#xff08;应该是上周&#xff09;的博客之星入围赛中铩羽而归了。虽然心中颇为不甘&#xff0c;觉得这一年兢兢业业&#xff0c;每天都在发文章&#xff0c;不应该是这样的结果&#xff08;连前300名都进不了&#xff09;。但人不能总抱怨&#xff0c;总要向前…...

DeepSeek回答人不会干出超出视角之外的事

我本身是有着深度思考习惯的重度患者&#xff0c;当我遇到一个AI会深度思考的时候&#xff0c;我觉得找到了一个同类&#xff0c;是不是可以学习周伯通的左右手互博大法&#xff1f;下面我们拿着我的一点思考&#xff0c;让DeepSeek来再深度思考挖掘。 人不会干出超出视角之外的…...

前端知识速记—JS篇:null 与 undefined

前端知识速记—JS篇&#xff1a;null 与 undefined 什么是 null 和 undefined&#xff1f; 1. undefined 的含义 undefined 是 JavaScript 中默认的值&#xff0c;表示某个变量已被声明但尚未被赋值。当尝试访问一个未初始化的变量、函数没有返回值时&#xff0c;都会得到 u…...

Hive:静态分区(分区语法,多级分区,分区的查看修改增加删除)

hive在建表时引入了partition概念。即在建表时&#xff0c;将整个表存储在不同的子目录中&#xff0c;每一个子目录对应一个分区。在查询时&#xff0c;我们就可以指定分区查询&#xff0c;避免了hive做全表扫描&#xff0c;从而提高查询率。 oracle和Hive分区的区别 orcale在…...

升级到Mac15.1后pod install报错

升级Mac后&#xff0c;Flutter项目里的ios项目运行 pod install报错&#xff0c; 遇到这种问题&#xff0c;不要着急去百度&#xff0c;大概看一下报错信息&#xff0c;每个人遇到的问题都不一样。 别人的解决方法并不一定适合你&#xff1b; 下面是报错信息&#xff1a; #…...

智慧园区管理系统为企业提供高效运作与风险控制的智能化解决方案

内容概要 快鲸智慧园区管理系统&#xff0c;作为一款备受欢迎的智能化管理解决方案&#xff0c;致力于为企业提供高效的运作效率与风险控制优化。具体来说&#xff0c;这套系统非常适用于工业园、产业园、物流园、写字楼及公寓等多种园区和商办场所。它通过数字化与智能化的手…...

装饰模式(Decorator Pattern)重构java邮件发奖系统实战

前言 现在我们有个如下的需求&#xff0c;设计一个邮件发奖的小系统&#xff0c; 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其…...

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

ubuntu搭建nfs服务centos挂载访问

在Ubuntu上设置NFS服务器 在Ubuntu上&#xff0c;你可以使用apt包管理器来安装NFS服务器。打开终端并运行&#xff1a; sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享&#xff0c;例如/shared&#xff1a; sudo mkdir /shared sud…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数&#xff08;函数作为参数、返回值&#xff09; 三、匿名函数与闭包1. 匿名函数&#xff08;Lambda函…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...