理解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表进行重定位?
- 动态库需要生成地址无关代码方便动态库加载时定位函数地址或者数据地址,否则动态库的动态共享的优势不再存在,因此需要生成地址无关代码;
- GOT表格存储在数据区而不属于代码段,这样可以保证各个进程各自持有一份各自的GOT表根据自己的内存映射地址进行调整。
GOT表需要考虑哪些内容?
- 模块间数据和函数地址访问。模块内不需要考虑,模块内使用模块内相对偏移即可。
- 全局数据,比如
extern
表示的数据。
2.2 PLT表
PLT(Procedure Link Table,过程绑定表)是为了实现延迟绑定的地址表格。由于动态链接是在运行期链接并且进行重定位,本来直接访问的内存可能变成间接访问,会导致性能降低。ELF采用延迟绑定来缓解性能问题,其假设就是动态库中并不是所有的函数与数据都会用到,类似copy-on-write,仅仅在第一次符号被使用时才进行相关的重定位工作,避免对一些不必要的符号的重定位。
ELF使用PLT(Procedure Linkage Table)实现延迟绑定。在进行重定位时每个符号需要了解符号在那个模块(模块ID)以及符号。当调用外部模块中的函数时,PLT为每个外部函数符号添加了PLT项,然后通过PLT项跳转到GOT表再到最终的函数地址。也就是说第一次调用会间接调用,之后可以直接通过PLT确认调用地址调用。
PLT解决了哪些性能问题?
- 符号解析。动态库加载时不需要加载所有符号,只需要加载部分能够大幅度降低加载耗时;
- 避免重复解析。当外部调用动态库内函数或者访问数据地址时需要搜索符号表访问找到对应的项,对于比较大的动态库这个过程比较耗时。通过延迟加载只会在第一次比较耗时,之后不会重复解析;
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>
。而调用printf
和myabs
就是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 简介 现代操作系统都是通过库来进行代码复用,降低开发成本提升系统整体效率。而库主要分为两种,一种是静态库,比如windows的.lib文件,macos的.a,linux的.a,另一种是动态库,比如windows的dll文…...

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

【原创改进】SCI级改进算法,一种多策略改进Alpha进化算法(IAE)
目录 1.前言2.CEC2017指标3.效果展示4.探索开发比5.定性分析6.附件材料7.代码获取 1.前言 本期推出一期原创改进——一种多策略改进Alpha进化算法(IAE)~ 选择CEC2017测试集低维(30dim)和高维(100dim)进行测…...
如何把一个python文件打包成一步一步安装的可执行程序
将一个 Python 文件打包成可执行程序(如 .exe 文件),并实现一步一步的安装过程,通常需要以下步骤: 1. 将 Python 文件打包成可执行文件 使用工具将 Python 脚本打包成可执行文件(如 .exe)。常用…...

防火墙安全策略部署
目录: 一、实验拓扑: 二、实验要求: 三、需求分析: 四、详细设计: 五、实验步骤: 1.进行vlan划分: 2.IP配置: 3.云端服务配置: 4.划分子网: 5.防火墙…...
c++ map/multimap容器 学习笔记
1 map的基本概念 简介: map中所有的元素都是pair pair中第一个元素是key(键),第二个元素是value(值) 所有元素都会根据元素的键值自动排序。本质: map/multimap 属于关联式容器,底…...

【解决方案】MuMu模拟器移植系统进度条卡住98%无法打开
之前在Vmware虚拟机里配置了mumu模拟器,现在想要移植到宿主机中 1、虚拟机中的MuMu模拟器12-1是目标系统,对应的目录如下 C:\Program Files\Netease\MuMu Player 12\vms\MuMuPlayer-12.0-1 2、Vmware-虚拟机-设置-选项,启用共享文件夹 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
桥接(Bridging)和NAT(网络地址转换,Network Address Translation)是网络中的两种不同技术,主要用于数据包的处理和转发。以下是它们的主要区别: 1. 工作原理 桥接: 桥接工作在数据链…...

人工智能 - 1
深度强化学习(Deep Reinforcement Learning) 图神经网络(Graph Neural Networks, GNNs) Transformer 一种深度学习模型 大语言模型(Large Language Models, LLMs) 人工智能 • 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)
题目来源 信息学奥赛一本通(C版)在线评测系统 题目描述 1144:单词翻转 时间限制: 1000 ms 内存限制: 65536 KB 提交数:60098 通过数: 26099 【题目描述】 输入一个句子(一行),将句子中的每一个单词翻转后输出。 【输入…...

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

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

DeepSeek回答人不会干出超出视角之外的事
我本身是有着深度思考习惯的重度患者,当我遇到一个AI会深度思考的时候,我觉得找到了一个同类,是不是可以学习周伯通的左右手互博大法?下面我们拿着我的一点思考,让DeepSeek来再深度思考挖掘。 人不会干出超出视角之外的…...
前端知识速记—JS篇:null 与 undefined
前端知识速记—JS篇:null 与 undefined 什么是 null 和 undefined? 1. undefined 的含义 undefined 是 JavaScript 中默认的值,表示某个变量已被声明但尚未被赋值。当尝试访问一个未初始化的变量、函数没有返回值时,都会得到 u…...

Hive:静态分区(分区语法,多级分区,分区的查看修改增加删除)
hive在建表时引入了partition概念。即在建表时,将整个表存储在不同的子目录中,每一个子目录对应一个分区。在查询时,我们就可以指定分区查询,避免了hive做全表扫描,从而提高查询率。 oracle和Hive分区的区别 orcale在…...

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

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

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

C# 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...