理解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文…...
总结11..
#include <stdio.h> #include <string.h> #define MAXN 1001 #define MAXM 1000001 int n, m; char maze[MAXN][MAXN]; int block[MAXN][MAXN]; // 标记每个格子所属的连通块编号 int blockSize[MAXN * MAXN]; // 记录每个连通块的大小 int dx[] {0, 0, 1, -1};…...
35.Word:公积金管理中心文员小谢【37】
目录 Word1.docx Word2.docx Word2.docx 注意本套题还是与上一套存在不同之处 Word1.docx 布局样式的应用设计页眉页脚位置在水平/垂直方向上均相对于外边距居中排列:格式→大小对话框→位置→水平/垂直 按下表所列要求将原文中的手动纯文本编号分别替换…...
FinRobot:一个使用大型语言模型的金融应用开源AI代理平台
“FinRobot: An Open-Source AI Agent Platform for Financial Applications using Large Language Models” 论文地址:https://arxiv.org/pdf/2405.14767 Github地址:https://github.com/AI4Finance-Foundation/FinRobot 摘要 在金融领域与AI社区间&a…...
C基础寒假练习(2)
一、输出3-100以内的完美数,(完美数:因子和(因子不包含自身)数本身 #include <stdio.h>// 函数声明 int isPerfectNumber(int num);int main() {printf("3-100以内的完美数有:\n");for (int i 3; i < 100; i){if (isPerfectNumber…...
【网络】应用层协议http
文章目录 1. 关于http协议2. 认识URL3. http协议请求与响应格式3.1 请求3.2 响应 3. http的常见方法4. 状态码4.1 常见状态码4.2 重定向 5. Cookie与Session5.1 Cookie5.1.1 认识Cookie5.1.2 设置Cookie5.1.3 Cookie的生命周期 5.2 Session 6. HTTP版本(了解&#x…...
RabbitMQ深度探索:简单实现 MQ
基于多线程队列实现 MQ : 实现类: public class ThreadMQ {private static LinkedBlockingDeque<JSONObject> broker new LinkedBlockingDeque<JSONObject>();public static void main(String[] args) {//创建生产者线程Thread producer n…...
React+AI 技术栈(2025 版)
文章目录 核心:React TypeScript元框架:Next.js样式设计:Tailwind CSSshadcn/ui客户端状态管理:Zustand服务器状态管理:TanStack Query动画效果:Motion测试工具表格处理:TanStack Table表单处理…...
计算机从何而来?计算技术将向何处发展?
计算机的前生:机械计算工具的演进 算盘是计算机的起点,它其实是一台“机械式半自动化运算器”。打算盘的“口诀”其实就是它的编程语言,算盘珠就是它的存储器。 第二阶段是可以做四则运算的加法器、乘法器。1642年,法国数学家帕斯…...
Docker使用指南(二)——容器相关操作详解(实战案例教学,创建/使用/停止/删除)
目录 1.容器操作相关命令编辑 案例一: 案例二: 容器常用命令总结: 1.查看容器状态: 2.删除容器: 3.进入容器: 二、Docker基本操作——容器篇 1.容器操作相关命令 下面我们用两个案例来具体实操一…...
从通讯工具到 AI 助理,AI手机如何发展?
随着AI进军各行各业,全面AI化时代已经到来。手机,作为现代人类的“数字器官”之一,更是首当其冲地融入了这一变革浪潮之中。 2024年年初,OPPO联合IDC发布了《AI手机白皮书》,公布OPPO已迈向AI手机这一全新阶段。到如今…...
小程序-基础加强
前言 这一节把基础加强讲完 1. 导入需要用到的小程序项目 2. 初步安装和使用vant组件库 这里还可以扫描二维码 其中步骤四没什么用 右键选择最后一个 在开始之前,我们的项目根目录得有package.json 没有的话,我们就初始化一个 但是我们没有npm这个…...
【CSS】谈谈你对BFC的理解
理解 CSS 中的 BFC(块格式化上下文) 在 CSS 中,BFC(Block Formatting Context) 是一个非常重要的概念,它决定了元素如何对其子元素进行定位,以及与其他元素的关系。理解 BFC 对于解决常见的布局…...
kubernetes-部署性能监控平台
在当今快速发展的云计算时代,Kubernetes 已成为容器编排的事实标准。随着越来越多的应用迁移到 Kubernetes 平台上,如何有效地监控集群的健康状态、资源使用情况以及应用性能变得尤为重要。一个完善的监控系统可以帮助我们及时发现问题、优化资源配置&am…...
【Uniapp-Vue3】iconfont图标库的使用
先在iconfont图标库中将需要的图标加入购物车 点击右侧购物车的图标 点击添加至项目,可以选中项目进行加入,也可以点击文件加号创建一个新的项目并添加 加入以后会来到如下界面,点击下载至本地 双击打开下载的.zip文件 将.css和.ttf文件进…...
Linux find 命令 | grep 命令 | 查找 / 列出文件或目录路径 | 示例
注:本文为 “Linux find 命令 | grep 命令使用” 相关文章合辑。 未整理去重。 如何在 Linux 中查找文件 作者: Lewis Cowles 译者: LCTT geekpi | 2018-04-28 07:09 使用简单的命令在 Linux 下基于类型、内容等快速查找文件。 如果你是 W…...
Day 28 卡玛笔记
这是基于代码随想录的每日打卡 77. 组合 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1: 输入:n 4, k 2 输出: [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ]示例 2…...
1.PPT:天河二号介绍【12】
目录 NO1 NO2.3.4.5 NO6.7.8.9 NO1 PPT:新建一个空白演示文档→保存到考生文件夹下:天河二号超级计算机.pptx幻灯片必须选择一种设计主题:设计→主题(随便选中一种)幻灯片的版式:开始→版式&#x…...
AI大模型开发原理篇-4:神经概率语言模型NPLM
神经概率语言模型(NPLM)概述 神经概率语言模型(Neural Probabilistic Language Model, NPLM) 是一种基于神经网络的语言建模方法,它将传统的语言模型和神经网络结合在一起,能够更好地捕捉语言中的复杂规律…...
物联网领域的MQTT协议,优势和应用场景
MQTT(Message Queuing Telemetry Transport)作为轻量级发布/订阅协议,凭借其低带宽消耗、低功耗与高扩展性,已成为物联网通信的事实标准。其核心优势包括:基于TCP/IP的异步通信机制、支持QoS(服务质量&…...
电控---中断
中断 1.处理器系统在执行代码的时候,会从存储器依次取出指令和数据,这种能力需要在处理器里保存一个存储器地址,就是所谓的程序计数器(Program Counter,PC),也叫程序指针 2.当外部中断(Extern …...
动态规划DP 背包问题 多重背包问题(朴素版+二进制优化+单调队列)
概览检索 动态规划DP 概览(点击链接跳转) 动态规划DP 背包问题 概览(点击链接跳转) 多重背包问题1 原题链接 AcWiing 4. 多重背包问题1 题目描述 有 N种物品和一个容量是 V的背包。 第 i 种物品最多有 si件,每件体…...
调试与错误修复:Cursor 如何成为你的编程助手
引言 调试是软件开发过程中最耗时且最具挑战性的环节之一。据统计,开发者平均将 50% 以上的编码时间 用于定位和修复错误。传统调试工具(如断点调试器、日志分析)虽能解决问题,但往往需要开发者手动追溯代码执行流程,…...
PHP 常用函数2025.02
PHP implode() 函数 语法 implode(separator,array) 参数描述separator可选。规定数组元素之间放置的内容。默认是 ""(空字符串)。array必需。要组合为字符串的数组。 技术细节 返回值:返回一个由数组元素组合成的字符串。PHP 版…...
浏览器查询所有的存储信息,以及清除的语法
要在浏览器的控制台中查看所有的存储(例如 localStorage、sessionStorage 和 cookies),你可以使用浏览器开发者工具的 "Application" 标签页。以下是操作步骤: 1. 打开开发者工具 在 Chrome 或 Edge 浏览器中…...
Paimon写入性能
写入性能 Paimon的写入性能与检查点密切相关,因此需要更大的写入吞吐量: 增加检查点间隔,或者仅使用批处理模式。增加写入缓冲区大小。启用写缓冲区溢出。如果您使用固定存储桶模式,请重新调整存储桶数量。 1 并行度 建议sink…...
Golang 并发机制-5:详解syn包同步原语
并发性是现代软件开发的一个基本方面,Go(也称为Golang)为并发编程提供了一组健壮的工具。Go语言中用于管理并发性的重要包之一是“sync”包。在本文中,我们将概述“sync”包,并深入研究其最重要的同步原语之一…...
排序算法与查找算法
1.十大经典排序算法 我们希望数据以一种有序的形式组织起来,无序的数据我们要尽量将其变得有序 一般说来有10种比较经典的排序算法 简单记忆为Miss D----D小姐 时间复杂度 :红色<绿色<蓝色 空间复杂度:圆越大越占空间 稳定性&…...
如何构建ObjC语言编译环境?构建无比简洁的clang编译ObjC环境?Windows搭建Swift语言编译环境?
如何构建ObjC语言编译环境? 除了在线ObjC编译器,本地环境Windows/Mac/Linux均可以搭建ObjC编译环境。 Mac自然不用多说,ObjC是亲儿子。(WSL Ubuntu 22.04) Ubuntu可以安装gobjc/gnustep和gnustep-devel构建编译环境。 sudo apt-get install gobjc gnus…...
数据结构课程设计(三)构建决策树
3 决策树 3.1 需求规格说明 【问题描述】 ID3算法是一种贪心算法,用来构造决策树。ID3算法起源于概念学习系统(CLS),以信息熵的下降速度为选取测试属性的标准,即在每个节点选取还尚未被用来划分的具有最高信息增益的…...
