jemalloc的malloc案例来分析GOT表和PLT表有关流程

一、背景

在之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 里,我们分析了在preload jemalloc的库之后,main之前的一次malloc分配(分配72704字节)的源头原因并做了jemalloc的初始化流程的分析,这篇博客里,我们针对之前博客里 2.4 一节里提到的“最终执行malloc@plt时,是如何找到我们jemalloc的malloc函数”的问题做展开,主要涉及到GOT和PLT和glibc里的_dl_runtime_resolve汇编逻辑和_dl_fixup逻辑。

所以,这篇博客,我们追踪逻辑用的程序和之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 里的一样,分析的是preload jemalloc库后,main之前的malloc 72704的这次调用逻辑里的细节,只是之前的博客里是直接跳过了分析malloc@plt去找jemalloc的malloc函数这个细节逻辑,在这篇博客里展开分析。

我们在下面的第二章里从之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 里 2.4 一节里的malloc@plt处开始跟一下相关的调用关系,及全局的一些状态,里面也涉及到了%rip的相关细节及实验。在第三章里,我们展开分析GOT和PLT表相关的glibc里的_dl_runtime_resolve汇编逻辑和_dl_fixup逻辑的细节。

二、从malloc@plt处开始展开分析

这一章是从下图里的call malloc@plt开始分析,但是如下面截图,下图截图是已经走过了call malloc@plt这句了,但是我们需要从call malloc@plt这句开始处来分析,如果要这样分析,那么断点设在jemalloc的malloc函数的入口处就不够了:

我们需要在前面就断点下来,一步步进入执行。

我们在 2.1 里讲如何设置断点并可以单步去调试main之前的这次malloc 72704的逻辑。在 2.2 里我们讲单步到malloc@plt里后看到的%rip寄存器的细节。然后,我们在 2.3 里比较一下没有执行过malloc和执行过一次malloc之后的跳转表的数值变化情况。最后在 2.4 里单步跟踪一下glibc里相关的逻辑的执行情况。

2.1 单步去调试main之前的这次malloc里的malloc@plt等跳转逻辑

我们在dl-init.c里下图的位置增加断点后,程序运行起来之后,点继续(Run)点9次之后(因为下图里的dl_init_t的循环走了9次以后,第10次是执行的libstdc++.so里的异常用的pool的初始化逻辑,相关细节见之前的博客 跟踪jemalloc 5.3.0的第一次malloc的源头原因及jemalloc相关初始化细节拓展-CSDN博客 ):

就到反汇编窗口里单步执行汇编,走到了malloc@plt如下图:

如上图,这句跳转用到了%rip寄存器。上图中的bnd前缀暂且可以忽略,bnd的实际含义我们当前并不需要了解。我在下面 2.2 一节里先阐述一下rip寄存器,另外,我们强调一下我们看到的汇编一般都是用的AT&T格式,也就是src在前,dst在后。

2.2 x86的rip寄存器

如下图,%rip是指向到了运行的指令的地址上:

rip寄存器,主要是方便寻址,因为无论是so库里还是bin里,最终运行到的虚拟地址在编译器编译时并不知道,但是指令与指令的地址差,也就是相对偏移基本上都是能确定的,所以在汇编里经常使用%rip加一个相对偏移来进行寻址。

往下执行一句汇编以后,%rip寄存器跟着变化,指向到了当前新的执行的指令所在地址上:

2.2.1 调试过程中看到的rip寄存器的值有时候并没有按照预期跟着代码执行而变化

在继续调试的过程中,我们发现%rip寄存器的在执行了call xxx之后,没有跟着变,但是,这仅仅是一个显示上的问题,实际%rip寄存器的值是跟着变的。

下面的容易让人产生%rip寄存器值没有跟着变的误导的截图:

下图是执行了call的截图:

%rip寄存器并没有和当前正在执行的指令的地址的值一致:

而是指向到了调用的call xxx的指令的下一条指令上:

2.2.2 事实上,rip寄存器真实数值一值是跟着变的

在调试过程中,上面的观测情况会让人产生误导,事实上,%rip是跟着变的,我们可以通过反汇编的内容来佐证,来看下面的截图:

我们先看第一条movaps %xmm0,0x17d9d2(%rip)指令:

这条指令的右边的提示:是调试器编译器给我们算出的实际的0x17d9d2(%rip)的地址,是0x7ffff7228300,我们用0x7ffff7228300减去0x17d9d2,得到的是:

恰好就是下一条指令的地址0x00007ffff70aa92e。

再看下一条指令movaps %xmm0,0x17d9db(%rip):

一样的,也是下一条指令的地址:

另外,我们写了一个程序来验证%rip是否是跟着变,下图是main里进行了一级调用,即调用call后,打印出%rip的值的程序代码,无论是gdb显示的%rip的值还是打印出来的%rip的值都是跟着变的:

但是要注意的是,gdb断点下来的位置是执行箭头所指的指令前那一刻的状态,如下图,就是在执行“lea    0x0(%rip),%rax”汇编指令去捞%rip寄存器的值的时刻:

所以,这里有一个关于%rip值的观测和实际引用的数值上的偏差,我们要观测到在实际引用时引用到的%rip的数值,得是断点断在或者执行下一条指令前的时刻才能观测到真正引用%rip的%rip的数值

如下图可以看到%rip确实是跟着变的,指向的是下一条指令的地址:

复现用的程序代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <time.h>
#include <string.h>int test1111()
{unsigned long rip_value;__asm__ volatile ("lea (%%rip), %0"  // 使用 LEA 指令获取 RIP 的值: "=r" (rip_value) // 输出到 rip_value 变量);printf("RIP value: %lx\n", rip_value);printf("sizeof(unsigned)=%d\n", sizeof(unsigned));__asm__ volatile ("lea (%%rip), %0"  // 使用 LEA 指令获取 RIP 的值: "=r" (rip_value) // 输出到 rip_value 变量);printf("RIP value: %lx\n", rip_value);
}int main()
{test1111();return 1;
}

2.3 对比没有执行过malloc和执行了一次malloc后的跳转表的数值变化

扫清了bnd jmp *0x18762d(%rip)里的rip寄存器的细节后,我们继续单步跟踪。

我们看一下在执行malloc@plt里的bnd jmp *0x18762d(%rip)        # 0x7ffff7226708 <malloc@got.plt>指令之前,跳转的位置相关的数值:

跳转到了这里:

这时候,可能还没有什么感觉,我们来对比一下,执行了一次malloc时之后,是否还跳转到这里,以及相关地址上的数值是否有变化。

看一下下一次malloc时是否还是跳转到这里:

调试发现,下一次malloc就到了main之后了,我们程序里是下图这里:

进入反汇编窗口进行单步执行来跟踪,由下图的malloc@plt的位置里:

直接跳转到了jemalloc的malloc函数里了:

为什么呢?我们来看一下malloc@plt里bnd jmp *0x18762d(%rip)        # 0x7ffff7226708这句里,要跳转到的0x7ffff7226708地址所存放的位置上的数值(因为bnd jmp 后面是有一个*的),对于第一次malloc执行时,0x7ffff7226708地址上的值如下,是0x7ffff709ae10:

从上图可以看到跳转到去执行的地址确实是0x7ffff709ae10。

下图是执行到jemalloc的malloc函数里时,0x7ffff7226708地址上的值,红色表示发生变化了,变成了0x7ffff7859815:

而0x7ffff7859815正时jemalloc的malloc的函数入口:

所以,在第一次执行malloc函数时,系统其实也就是glibc变动了跳转的位置,变动成了jemalloc里的malloc的函数首地址。

2.4 跟踪一下glibc里是如何变动这个跳转表的数值的

我们来看一下glibc是如何进行这个变动操作的,来单步执行,看第一次malloc时跳转到的0x7ffff709ae10地址后具体做了什么:

0x7ffff709ae10地址执行了以后,马上就跳转到了0x7ffff709a020地址,

然后又马上跳转到了0x7ffff7226010地址上的数值指向的地址:

0x7ffff7226010地址上的值是0x7ffff7fd8d30:

而0x7ffff7fd8d30地址其实就是_dl_runtime_resolve_xsavec:

关于_dl_runtime_resolve_xsavec我们在第三章里展开。

三、关于_dl_runtime_resolve和_dl_fixup函数

我们继续第二章里的分析。

3.1 第二章最后看到的_dl_runtime_resolve_xsavec函数即源码里的_dl_runtime_resolve

_dl_runtime_resolve_xsavec函数其实就是对应的源码里的_dl_runtime_resolve:

而_dl_runtime_resolve是arch相关的,我们是x86_64平台,所以就是glibc代码里的sysdeps/x86_64/dl-trampoline.h里的_dl_runtime_resolve汇编执行体,我们可以通过搜索jmp *%r11来百分百确定是用sysdeps/x86_64/dl-trampoline.h里的实现:

执行的是如下这段源码里的汇编:

3.2 _dl_runtime_resolve汇编代码里调用了_dl_fixup函数

_dl_runtime_resolve的汇编代码里,去掉cfi开头的语句(指令和函数检测有关,GNU Profiler)后,剩下了逻辑里就是保存寄存器的值到栈中:

然后调用_dl_fixup函数,第一个入参是link_map,第二个入参是reloc_index,这个是GOT表中关于PLT重定位的索引值,即PLT表中的第几项:

3.3 _dl_fixup函数的分析

_dl_fixup函数是在glibc里的elf/dl-runtime.c里的。

进入到_dl_fixup函数以后,从入参的link_map里可以看到,库路径就是libstdc++这个库的路径:

因为调试的这次调用malloc的源头是在glibc里的异常用pool构造函数里的malloc触发的。

3.3.1 从传入的link_map里获取so的符号表symtab和字符串表strtab和相关符号的elf条目的指针reloc

_dl_fixup函数先从link_map里获取so的符号表symtab和字符串表strtab:

然后通过传入的reloc_arg这个plt表的序号找到相关的符号的elf条目的指针reloc:

该PLTREL有两个成员:

3.3.2 通过reloc的r_offset计算得到要修改的函数所要改的地址rel_addr

r_offset加上link_map的l_addr得到的就是要修改的函数所要改的地址,即该函数对应GOT表的地址:

这个rel_addr是在后面会传给elf_machine_fixup_plt进行修正:

我们看一下上面传入给elf_machine_fixup_plt函数的value变量是怎么获取到要修改成的函数目标地址的。

3.3.3 传入给elf_machine_fixup_plt函数的value是怎么计算得到的

这里说的value变量,也就是要修改成的函数目标地址。

回到刚才说的得到的“plt表的序号找到相关的符号的elf条目的指针”reloc变量,通过reloc变量也就是PLTREL里的r_info作为symbol的index去到symtab这个符号表里去找条目,得到我们关心的这条条目即sym变量:

根据sym里的st_other变量:

判断其符号的可见性,是否是STV_DEFAULT也就是0:

如果不是0,则直接用link_map里的l_addr这个装载地址加上sym里的st_value,得到value

(link_map里的l_addr是指elf里的地址实际模块所装载到的虚拟地址之间的差值)

(sym里的st_value是指符号地址相对于模块基址的偏移值)

上图中的DL_FIXUP_MAKE_VALUE宏即:

如果sym的st_other不是0的话,就表示传入的link_map和sym都已经是目标函数的信息了,因此可以直接计算目标函数的地址。

如果sym的st_other是0的话,一般都是0,则用_dl_lookup_symbol_x来算出lookup_t也就是link_map*类型的result,再用result来用DL_FIXUP_MAKE_VALUE宏来计算得到最终的value:

3.3.4 _dl_lookup_symbol_x函数

_dl_lookup_symbol_x函数遍历所有的scope,通过do_lookup_x函数在每个scope中查找符号,将查找的结果记录在current_value中:

在do_lookup_x函数里,获取scope下的link_map个数r_nlist和数组r_list:

遍历所有的link_map,通过check_match函数比较符号表中的函数名symtab[symidx]和待查找的函数名undef_name是否一致:

如果相等,就找到了该符号并跳转到found_it语句,否则返回null:

如果找到的是弱符号STB_WEAK,则保存第一次找到的结果,然后继续查找,如果后面没有找到可以覆盖该结果的,则返回这第一次保存的结果:

如果找到的是全局符号STB_GLOBAL,则直接返回该结果:

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

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

相关文章

Centos Ollama + Deepseek-r1+Chatbox运行环境搭建

Centos Ollama Deepseek-r1Chatbox运行环境搭建 内容介绍下载ollama在Ollama运行DeepSeek-r1模型使用chatbox连接ollama api 内容介绍 你好&#xff01; 这篇文章简单讲述一下如何在linux环境搭建 Ollama Deepseek-r1。并在本地安装的Chatbox中进行远程调用 下载ollama 登…

使用sunshine和moonlight串流时的音频输出问题

设备&#xff1a;电脑和平板串流&#xff0c;把平板当副屏使用 1.如果启用安装steam音频驱动程序&#xff0c;则平板有声&#xff0c;电脑无声&#xff0c;在moonlight端可以设置平板和电脑同时发声&#xff0c;但是有点卡 2.只想电脑发声&#xff0c;平板无声 禁用安装steam…

微信小程序案例2——天气微信小程序(学会绑定数据)

文章目录 一、项目步骤1 创建一个weather项目2 进入index.wxml、index.js、index.wxss文件,清空所有内容,进入App.json,修改导航栏标题为“中国天气网”。3进入index.wxml,进行当天天气情况的界面布局,包括温度、最低温、最高温、天气情况、城市、星期、风行情况,代码如下…

如何在WPS和Word/Excel中直接使用DeepSeek功能

以下是将DeepSeek功能集成到WPS中的详细步骤&#xff0c;无需本地部署模型&#xff0c;直接通过官网连接使用&#xff1a;1. 下载并安装OfficeAI插件 &#xff08;1&#xff09;访问OfficeAI插件下载地址&#xff1a;OfficeAI助手 - 免费办公智能AI助手, AI写作&#xff0c;下载…

数字电路-基础逻辑门实验

基础逻辑门是数字电路设计的核心元件&#xff0c;它们执行的是基本的逻辑运算。通过这些基本运算&#xff0c;可以构建出更为复杂的逻辑功能。常见的基础逻辑门包括与门&#xff08;AND&#xff09;、或门&#xff08;OR&#xff09;、非门&#xff08;NOT&#xff09;、异或门…

哪吒闹海!SCI算法+分解组合+四模型原创对比首发!SGMD-FATA-Transformer-LSTM多变量时序预测

哪吒闹海&#xff01;SCI算法分解组合四模型原创对比首发&#xff01;SGMD-FATA-Transformer-LSTM多变量时序预测 目录 哪吒闹海&#xff01;SCI算法分解组合四模型原创对比首发&#xff01;SGMD-FATA-Transformer-LSTM多变量时序预测效果一览基本介绍程序设计参考资料 效果一览…

C++,STL 迭代器简介:概念、分类、操作

文章目录 引言一、迭代器的基本概念1.1 什么是迭代器?1.2 迭代器的意义二、迭代器的分类2.1 示意图:迭代器能力层级2.2 示例:不同迭代器的操作三、迭代器的常用操作3.1 基本操作3.2 随机访问迭代器专用操作示例代码:随机访问迭代器四、迭代器的通用用法4.1 遍历容器4.2 配合…

EasyExcel 导出合并层级单元格

EasyExcel 导出合并层级单元格 一、案例 案例一 1.相同订单号单元格进行合并 合并结果 案例二 1.相同订单号的单元格进行合并2.相同订单号的总数和总金额进行合并 合并结果 案例三 1.相同订单号的单元格进行合并2.相同订单号的商品分类进行合并3.相同订单号的总数和总金额…

常用的python库-安装与使用

常用的python库函数 yield关键字openslide库openslide对象的常用属性 cv2库numpy库ASAP库-multiresolutionimageinterface库ASAP库的安装ASAP库的使用 concurrent.futures.ThreadPoolExecutorxml.etree.ElementTree库skimage库PIL.Image库 PIL.Image.Imagedetectron2库数据增强…

C++基础系列【8】如何解决编译器报的错误

博主介绍&#xff1a;程序喵大人 35- 资深C/C/Rust/Android/iOS客户端开发10年大厂工作经验嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手《C20高级编程》《C23高级编程》等多本书籍著译者更多原创精品文章&#xff0c;首发gzh&#xff0c;见文末&#x1f447;&#x1f…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<8>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 今天我们复习前面学习的指针知识 目录 关于指针数组和数组指针的区别指针数组&#xff08;Array of Poi…

UE5.5 PCGFrameWork--GPU CustomHLSL

在上一篇UE5.5 PCGFrameWork使用入门-CSDN博客 大致介绍了UE5 PCG框架的基本使用. 本篇探索PCGFrame的高级应用--GPU点云。也就是利用GPU HLSL编程对点云进行操纵&#xff0c;可以大幅度提升点云生成效率。 目前在UE5 PCG框架中&#xff0c;点云GPU的应用大致分为三类: Point…

Games202 Lecture11 LTC | Disney principled BRDF | NPR

Shading with microfacet BRDFs under polygonal lighting -Linearly Transformed Cosines(LTC)Real-Time PBR Materials cont. -Disney principled BRDFNon-photorealistic rendering(NPR) Linearly Transformed Cosines(LTC) lobe花瓣 BRDF的2d形状 基本思路: 任意BRDF变…

Flink 内存模型各部分大小计算公式

Flink 的运行平台 如果 Flink 是运行在 yarn 或者 standalone 模式的话&#xff0c;其实都是运行在 JVM 的基础上的&#xff0c;所以首先 Flink 组件运行所需要给 JVM 本身要耗费的内存大小。无论是 JobManager 或者 TaskManager &#xff0c;他们 JVM 内存的大小都是一样的&a…

学习数据结构(8)双向链表

1.双向链表的实现 双向链表一般指带头双向循环链表 &#xff08;1&#xff09;双向链表的声明 &#xff08;2&#xff09;双向链表的打印 &#xff08;3&#xff09;向内存申请节点 &#xff08;4&#xff09;初始化双向链表 或 &#xff08;5&#xff09;尾部插入 &#xf…

【漫话机器学习系列】088.常见的输出层激活函数(Common Output Layer Activation Functions)

在神经网络中&#xff0c;输出层&#xff08;Output Layer&#xff09; 的激活函数&#xff08;Activation Function&#xff09;直接决定了模型的输出形式&#xff0c;并影响损失函数的选择及训练效果。不同的任务类型&#xff08;如分类或回归&#xff09;需要使用不同的激活…

Python 鼠标轨迹 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

工业相机在工业生产制造过程中的视觉检测技术应用

随着技术不断发展以及工业4.0时代的到来&#xff0c;利用工业相机进行视觉检测技术已经成为制造业不可或缺的一部分。通过结合先进的计算机视觉、AI算法和自动化设备&#xff0c;工业视觉检测为生产线质量控制和效率提升提供了革命性的解决方案。 一、什么是工业视觉检测技术 …

了解网络层

目录 一、IP协议 二、地址管理 IP地址 概念 作用 格式 网段划分 三、路由选择 网络层要做的事情主要是两个方面&#xff1a; 地址管理&#xff1a;制定一系列的规则&#xff0c;通过地址&#xff0c;描述出网络上一个设备的位置。路由选择&#xff1a;网络环境比较复杂…

NO.11十六届蓝桥杯备战|if-else语句|嵌套if|悬空else|练习4道(C++)

if-else语句 if语句 if语句的语法形式如下&#xff1a; if ( 表达式 ) 语句;表达式成⽴&#xff08;为真&#xff09;&#xff0c;则语句执⾏&#xff0c;表达式不成⽴&#xff08;为假&#xff09;&#xff0c;则语句不执⾏ 0为假&#xff0c;⾮0表⽰真&#xff0c;也就是…