内核执行时动态的vmlinux的反汇编解析方法及static_branch_likely机制

一、背景

在之前的博客里,我们讲到了tracepoint(内核tracepoint的注册回调及添加的方法_tracepoint 自定义回调-CSDN博客)和kprobe(获取任意一个进程的共享内存的fd对应的资源,增加引用,实现数据的接管——包含非export的内核函数的模块内使用-CSDN博客 里 第三章)和static_call(内核调度抢占模式——voluntary和full对比-CSDN博客 里 3.1.1 一节),它们都会在内核运行期间,动态修改内存里的代码段的内容,来实现高性能的分支跳转。这篇博客里,我们会以介绍static_branch_likely机制为手段,来去反汇编内核执行期间时的动态的vmlinux内容,来分析和确认内核的动态分支跳转是走的哪一个。

我们在介绍static_branch_likely的机制时,用的是export的symbol的local_clock的实现里的关键函数local_clock_noinstr来做为例子。关于local_clock及sched_clock等会在后面的章节来详细做对比和介绍。这篇博客只聚焦标题里描述的动态vmlinux反汇编及static_branch_likely机制这两个部分。

我们在第二章里会先介绍vmlinux的反汇编方法及执行期间的动态vmlinux的反汇编方法,然后再第三章里使用第二章里的工具来介绍和验证static_branch_likely机制

二、静态vmlinux的反汇编方法及执行期间的动态vmlinux的反汇编方法

静态vmlinux的反汇编方法在之前的博客里多次有提及,要objdump整个vmlinux会相当耗时,在 2.1 里会提及效率更高的指定区域的反汇编方法,并在 2.2 里介绍如何反汇编执行期间的动态vmlinux。

2.1 静态vmlinux的反汇编方法及指定区域的反汇编方法

在内核编译时,一般在代码的目录下就会生成一个全符号的vmlinux(当然前期是你没有以strip方式去编译),而内核在实际运行是,用的使用vmlinux的压缩文件vmlinuz,两者大小相差有快30倍:

我们在objdump时,要用原始的vmlinux文件来进行反汇编:

2.1.1 静态vmlinux的反汇编方法及输出产物介绍

下面这句是反汇编整个vmlinux文件,这句指令会非常非常耗时,cpu性能较好的情况下也会运行数个小时,输出的可人眼阅读的反汇编后的嵌入源码与对应汇编及二进制.text的文件如命令里设的就是vmlinux.txt:

objdump -S vmlinux > vmlinux.txt

vmlinux.txt的内容形如:

我们以local_clock_noinstr这个函数为例,可以从下图中看到,反汇编出来的上图里左边框出的地址,并不是实际内核执行期的函数地址,而是编译器在编译时指定的一段连续虚拟地址空间里的地址,它是一个临时的一段虚拟的地址,当然由于代码段肯定是4k对齐的,或者说vmlinux的实际运行首地址肯定也是对齐的一个比较大的数值的,所以反汇编出来的最后n个bit是和实际运行期间的函数的虚拟地址的最后n个bit是一样的:

2.1.2 指定区域进行objdump的方法

由于objdump整个vmlinux非常耗时,我们有时候只关心某个区域里的反汇编情况,而同一个机器上同一套编译环境上相近代码的两次编译,其函数地址段往往是一样的,这个例子里,我们看到

local_clock_noinstr在整个vmlinux的反汇编产物里是位于ffffffff820c9910开始,到ffffffff820c99df结束:

可以用如下方式进行局部的反汇编:

objdump -S vmlinux --start-address=0xffffffff820c9910 --stop-address=0xffffffff820c99df > vmlinux_test.txt

下图里可以看到,局部反汇编出来的内容是和全部反汇编出来的内容在这一部分上是一致的:

甚至如上图左边的局部反汇编产物里红色小框里的内容,局部反汇编从局部代码的理解和阅读上可能会更加清晰细节更多。

最重要的,局部反汇编的运行时间如果地址范围不大的话,是非常短的,可以提高效率。

2.2 动态vmlinux(执行期间的vmlinux)的反汇编方法

这里说的动态,就是说在内核启动以后,会根据实际的运行情况,由于tracepoint/krobe/static_call/static_branch_likely等动态修改代码段实现高性能分支跳转的这些内核机制,会导致代码段内容发生变更。也就是说,2.1 里描述的静态反汇编vmlinux输出的文件,和系统当前正在运行时的vmlinux的情况会有不同。

为了获取到动态执行期间的vmlinux的实际的运行情况,我们可以用如下的反汇编方法,我们以具体函数local_clock_noinstr来举例。

2.2.1 先通过cat /proc/kallsyms | grep xx获取到内核函数xx的内核虚拟地址首地址

cat /proc/kallsyms | grep local_clock_noinstr

如上图,我们得到local_clock_noinstr函数对应的内核虚拟地址首地址是0xffffffffa9ac9910

2.2.2 编写一个ko来获取这个函数对应的地址段的内容

下面这个代码还是比较简单的,根据insmod时传入的address和size参数来决定dump哪段内存:

如果传入第三个参数filedir,则会把dump到的内存内容以二进制形式输出到文件里去:

为了方便使用,在insmod执行直接进行指定内存段的内容的获取,并输出到dmesg里,如果传入filedir则也同时输出到设置的文件里,insmod执行完后用EINVAL返回失败,这样不用rmmod直接可以再次insmod来读取下一个内存段(下面指令里的128字节,我是随手指定了一个size,不用太在意这个细节):

insmod testgetkmem.ko address=0xffffffffa9ac9910 size=128 filedir="output.txt"

可以通过读取dmesg里的内容获取内存段内容:

上面指令指定了filedir是output.txt,所以也可以看output.txt里的内容来获取内存段内容,但是由于是二进制,所以直接cat是得到的乱码:

可以用vscode里的hex editor工具,或者linux上直接用hexedit工具来查看:

完整代码:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/mm.h>
#include <linux/uaccess.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xin Zhao");
MODULE_DESCRIPTION("A kernel module to read memory and print it as hex dump.");
MODULE_VERSION("1.0");// 定义模块参数
static unsigned long address = 0;  // 虚拟地址
static size_t size = 0;             // 打印的字节数
static char* filedir = NULL;        // 输出文件目录module_param(address, ulong, S_IRUGO);
MODULE_PARM_DESC(address, "Virtual address to read from");
module_param(size, ulong, S_IRUGO);
MODULE_PARM_DESC(size, "Number of bytes to read");
module_param(filedir, charp, S_IRUGO);
MODULE_PARM_DESC(filedir, "Directory to write output file");struct file* _file;static int __init getkmem_init(void) {if (size == 0) {printk(KERN_ERR "[getkmem] Size must be greater than 0.\n");return -EINVAL;}// 检查地址的有效性(这里可以添加更复杂的检查)if (!address) {printk(KERN_ERR "[getkmem] Invalid address provided.\n");return -EINVAL;}// 打印内存内容printk(KERN_INFO "[getkmem] Reading memory from address: %lx, size: %zu\n", address, size);print_hex_dump(KERN_INFO, "[getkmem] ", DUMP_PREFIX_ADDRESS, 16, 1,(void *)address, size, false);if (filedir) {loff_t pos = 0;_file = filp_open(filedir, O_WRONLY | O_CREAT | O_TRUNC, 0644);kernel_write(_file, address, size, &pos);filp_close(_file, NULL);}return -EINVAL;
}static void __exit getkmem_exit(void) {printk(KERN_INFO "[getkmem] Module exiting.\n");
}module_init(getkmem_init);
module_exit(getkmem_exit);

2.2.3 找到动态获取到的代码段里的内容和静态的代码段内容不一致的内容

按照下面local_clock_noinstr反汇编得到的内容里的二进制代码段内容:

使用hexedit打开原始的vmlinux二进制文件,搜索上图中的二进制代码部分:

输入/后输入下图中红色框里的内容(红色框里的内容和上图中的红色框里内容一致):

可以搜到如下内容,且只能搜到一处,说明一定是这个地方:

关于搜索功能,hexedit没有vscode里的hex editor好用,vscode里的hex editor可以快速提示匹配的内容有多少个:

搜的代码段二进制是:554889e5415453eb26

如下图,我们发现local_clock_noinstr函数的静态反汇编得到的代码段二进制部分和实际运行期间时的代码对应地址段的二进制部分不一致,如下图,EB26变成了6690。关于为什么会有这样的变化,我们在第三章里会介绍说明。

2.2.4 复制一份vmlinux出来,修改动态执行期间变化了的部分

我们拷贝一份vmlinux出来,按照 2.2.3 里发现的不一样的地方修改掉:

用hexedit进行修改:

ctrl+x以后按y进行保存

2.2.5 使用 2.1.2 一节里提到的指定区域objdump的方法导出动态执行期间的某个函数的反汇编代码

使用如下命令进行局部反汇编,导出到vmlinux_test_1.txt输出文件里:

objdump -S vmlinux_test --start-address=0xffffffff820c9910 --stop-address=0xffffffff820c99df > vmlinux_test_1.txt

可以看到local_clock_noinstr函数的动态执行期间的反汇编内容如下:

可以从上图中看到修改部分的6690代码段对应的汇编代码是一条无实际作用的空转指令。在下面第三章里会介绍,这其实就是static_branch_likely机制所需要的一句可用于替换别的指令的“占坑”指令。

三、内核static_branch_likely机制

在上面的 2.2.5 里可以看到local_clock_noinstr函数在动态执行期间,把原来静态vmlinux里的原来的:

替换成了一条无实际作用的纯“占坑”指令:

3.1 编译生成的vmlinux会按照static_branch对应的变量默认的值来生成初始的指令代码

还是以local_clock_noinstr函数来分析,我们看编译生成的vmlinux里的local_clock_noinstr函数的跳转执行情况:

clock.c里的这个local_clock_noinstr函数如下实现:

__sched_clock_stable的默认值是false的(x86是默认打开CONFIG_HAVE_UNSTABLE_SCHED_CLOCK的):

所以,在__sched_clock_stable的默认值false的配置下,local_clock_noinstr的第一段分叉逻辑走不到。

再看第二段分叉逻辑的sched_clock_running变量,它默认值也是false:

但是它是!来判断,所以,按照默认值这段分支逻辑能走到:

这个分析和vmlinux的编译产出的反汇编汇编指令逻辑一致:

local_clock_noinstr先是如下图的jmp跳到了ffffffff820c993f:

如下图,再由ffffffff820c993f跳到了ffffffff820c99c5,而ffffffff820c99c5即执行return sched_clock_noinstr();和代码里的第二段分支逻辑匹配。

初始代码根据key的默认值生成对应汇编代码的细节:

3.2 static_branch_likely机制通过static_branch_enable来

而local_clock_noinstr函数在执行期间,__sched_clock_stable值发生了改变,从而导致local_clock_noinstr所执行的分支段发生了变化。我们通过编写了一个test_local_clock函数,export了symbol,在模块里执行来确认了这个分支运行情况:

这个逻辑从通过 2.2.5 里导出的local_clock_noinstr函数的动态反汇编内容里也可以得到是执行的第一个分支代码段:

事实上,它最终是通过static_branch_enable来标记的,关于这个__sched_clock_stable标记的调用链:

sched_clock_init_late->__set_sched_clock_stable->static_branch_enable(&__sched_clock_stable);

接下来,我们看一下static_branch_enable是如何最终改变分支的:

static_branch_enable是调用的static_key_enable进行的key的设置:

在jump_label.h里进行的定义:

因为我们CONFIG_JUMP_LABEL是打开的:

所以static_key_enable函数的实现是在jump_label.c里实现的:

static_key_enable_cpuslocked的实现也在jump_label.c里,调用的jump_label_update进行的分支跳转相关指令代码的动态更新:

空转会根据指令的大小进行相应的替换,对于static_branch_likely机制的jmp指令,空转指令的大小是2,对应于如下的汇编实现就是:

所以就是6690作为汇编的空转指令,与 2.2.5 一节里导出的local_clock_noinstr的执行期间的变化部分的6690代码二进制一致。

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

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

相关文章

Unity 3D饼状图效果

一. 效果展示 二.基础类 using System.Collections; using System.Collections.Generic; using UnityEngine;public class DrawCylinder : MonoBehaviour {// 网格渲染器MeshRenderer meshRenderer;// 网格过滤器MeshFilter meshFilter;// 用来存放顶点数据List<Vector3>…

精通 Numpy 数组:详解数据类型查看、转换与索引要点

1、查看数据类型 通过访问数组的dtype属性时会创建一个表示数据类型的对象&#xff0c; 这个对象其实就是numpy.dtype类型的对象。 如果要想获取数组中元素数据类型的名称&#xff0c;则需要先通过数组访问dtype属性得到numpy. dtype类型的对象&#xff0c;再通过该对象访问n…

分布式系统架构5:限流设计模式

这是小卷对分布式系统架构学习的第5篇文章&#xff0c;今天来学习限流器和限流设计模式 1.为什么要限流&#xff1f; 任何一个系统的运算、存储、网络资源都不是无限的&#xff0c;当系统资源不足以支撑外部超过预期的突发流量时&#xff0c;就应该要有取舍&#xff0c;建立面…

山景BP1048蓝牙音频任务后台运行设置方法

1、 问题 山景BP1048提供的蓝牙音箱SDK蓝牙音频是不能在后台运行的。例如&#xff1a;现在进入U盘模式播放蓝牙音乐&#xff0c;那么此时蓝牙就会关闭。 但是在特定场景下&#xff0c;需要蓝牙保持连接。 2、解决办法 打开sys_param.c文件 #include <string.h> #incl…

【AI知识】为什么激活值过大/过小,初始权重过大/过小,可能导致梯度爆炸/消失?具体例子举例

1.梯度爆炸的公式计算 计算一个简单的两层神经网络的前向传播和反向传播过程。在这里&#xff0c;我们故意选择过大的初始权重值&#xff0c;查看梯度如何爆炸。 总结&#xff1a; 梯度值很大&#xff0c;这是梯度爆炸的一个典型例子。此时&#xff0c;如果我们使用梯度下降进…

在福昕(pdf)阅读器中导航到上次阅读页面的方法

文章目录 在福昕(pdf)阅读器中导航到上次阅读页面的方法概述笔记用书签的方法来导航用导航按钮的方法来导航 备注END 在福昕(pdf)阅读器中导航到上次阅读页面的方法 概述 喜欢用福昕(pdf)阅读器来看pdf文件。 但是有个小问题困扰了我好久。 e.g. 300页的pdf看了一半&#xff…

CCF-GESP 等级考试 2023年9月认证C++一级真题解析

2023年9月真题 一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 正确答案&#xff1a;C 解析&#xff1a;考察知识点&#xff1a;计算机基础 本题属于考察计算机基础知识中的存储设备问题&#xff1b;内存是一种存储设备&#xff0c;也可以考虑关联内存中的“存…

重拾设计模式--建造者模式

文章目录 建造者模式&#xff08;Builder Pattern&#xff09;概述建造者模式UML图作用&#xff1a;建造者模式的结构产品&#xff08;Product&#xff09;&#xff1a;抽象建造者&#xff08;Builder&#xff09;&#xff1a;具体建造者&#xff08;Concrete Builder&#xff…

关于使用拓扑排序算法实现解析勾稽关系优先级的研究和实现

1. 勾稽关系 勾稽关系&#xff08;Reconciliation Relationship&#xff09;是一个财务术语&#xff0c;指的是在会计和审计中&#xff0c;不同会计报表或报表项目之间存在的逻辑对应关系。这种关系可以用来验证会计数据的准确性和完整性。勾稽关系通常体现在以下几个方面&…

电商项目-网站首页高可用(二)

一、LUA基本语法 lua有交互式编程和脚本式编程。 交互式编程就是直接输入语法&#xff0c;就能执行。 脚本式编程需要编写脚本文件&#xff0c;然后再执行。 一般采用脚本式编程。&#xff08;例如&#xff1a;编写一个hello.lua的文件&#xff0c;输入文件内容&#xff0c;并执…

flink实现复杂kafka数据读取

接上文&#xff1a;一文说清flink从编码到部署上线 环境说明&#xff1a;MySQL&#xff1a;5.7&#xff1b;flink&#xff1a;1.14.0&#xff1b;hadoop&#xff1a;3.0.0&#xff1b;操作系统&#xff1a;CentOS 7.6&#xff1b;JDK&#xff1a;1.8.0_401。 常见的文章中&…

第十五届蓝桥杯Scratch01月stema选拔赛—排序

排序 具体要求&#xff1a; 1). 点击绿旗&#xff0c;在舞台上出现4张点数不同的扑克牌&#xff0c;牌上的点数是随机的&#xff08;4-9点&#xff09;&#xff0c;如图所示&#xff1b; 完整题目可点击下方链接查看&#xff1a; 排序_scratch_嗨信奥-玩嗨信息奥林匹克竞赛-…

图形学笔记 - 5. 光线追踪2 - 加速结构

目录 使用AABB加速光线追踪 Uniform Spatial Partitions (Grids) 均匀空间划分 空间划分 KD树预处理 KD-Tree数据结构 遍历kd树 对象划分 & Bounding Volume Hierarchy 层次包围盒 BVH BVH遍历 空间划分与物体划分呢 GTC news: DLSS、RTXGI 实时光线追踪 使用AAB…

计算机毕业设计原创定制(免费送源码):NodeJS+MVVM+MySQL 樱花在线视频网站

目 录 摘要 1 1 绪论 1 1.1研究背景 1 1.2系统设计思想 1 1.3B/S体系工作原理 1 1.4node.js主要功能 2 1.5论文结构与章节安排 3 2 樱花在线视频网站分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 …

SpringBoot 启动类 SpringApplication 二 run方法

配置 在Program arguments配置2个参数&#xff1a;--server.port8081 --spring.profiles.activedev。 run方法 run方法执行结束代表SpringBoot启动完成&#xff0c;即完成加载bean。 // ConfigurableApplicationContext 是IOC容器 public ConfigurableApplicationContext ru…

如何调大unity软件的字体

一、解决的问题&#xff1a; unity软件的字体太小&#xff0c;怎么调大点&#xff1f;二、解决方法&#xff1a; 1.操作步骤&#xff1a; 打开Unity编辑器> Edit>preferences> UI Scaling>Use custom scaling value&#xff08;取消勾选“使用默认桌面设置”&…

SYD881X RTC定时器事件在调用timeAppClockSet后会出现比较大的延迟

RTC定时器事件在调用timeAppClockSet后会出现比较大的延迟 这里RTC做了两个定时器一个是12秒,一个是185秒: #define RTCEVT_NUM ((uint8_t) 0x02)//当前定时器事件数#define RTCEVT_12S ((uint32_t) 0x0000002)//定时器1s事件 /*整分钟定时器事件&#xff0c;因为其余的…

内置函数.

日期函数 current_date/time() 日期/时间 获得年月日&#xff1a; 获得时分秒&#xff1a; 获得时间戳&#xff1a;日期时间 now()函数 体会date(datetime)的用法&#xff1a;只显示日期 在日期的基础上加日期&#xff1a;按照日历自动计算 关键字为 intervalinterval 后的数值…

PHP 微信棋牌开发全解析:高级教程

PHP - 多维数组详解 多维数组是 PHP 中一种强大的数据结构&#xff0c;指的是一个数组的元素中可以包含一个或多个数组。它常用于存储复杂的嵌套数据&#xff0c;如表格数据或多层次关系的数据结构。 注释&#xff1a; 数组的维度表示您需要指定索引的层级数&#xff0c;以访问…

【Java】递归算法

递归的本质&#xff1a; 方法调用自身。 案例1. 斐波那契数列 1 1 2 3 5 8 13 21 .. f(n)f(n-1)f(n-2) 方法的返回值&#xff1a; 只要涉及到加减乘除&#xff0c;就是int,其他的就是void。 案例2. 青蛙跳台 青蛙一次可以跳一级台阶&#xff0c;也可以跳两级台阶&#xff…