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

news2025/1/11 14:26:30

一、背景

在之前的博客里,我们讲到了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.coloradmin.cn/o/2264205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Burp与其他安全工具联动及代理设置教程

Burp Suite 是一款功能强大的 Web 安全测试工具&#xff0c;其流量拦截和调试功能可以与其他安全工具&#xff08;如 Xray、Yakit、Goby 等&#xff09;实现联动&#xff0c;从而提升渗透测试的效率。本文将详细讲解 Burp 与其他工具联动的原理以及代理设置的操作方法&#xff…

Git配置公钥步骤

GIt公钥的配置去除了git push输入账号密码的过程&#xff0c;简化了push流程。 1.生成SSH公钥和私钥 ssh-keygen -t rsa -b 4096 -C “your_emailexample.com” 遇到的所有选项都按回车按默认处理。获得的公钥私钥路径如下&#xff1a; 公钥路径 : ~/.ssh/id_rsa.pub 私钥路径…

【蓝桥杯选拔赛真题96】Scratch风车旋转 第十五届蓝桥杯scratch图形化编程 少儿编程创意编程选拔赛真题解析

目录 scratch风车旋转 一、题目要求 编程实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、python资料 scratc…

未来 AI 在企业应用中的重心

1. LLM 中精度、参数、数据、性能、以及成本之间的权衡是什么&#xff1f; 在大型语言模型&#xff08;LLM, Large Language Models&#xff09;中&#xff0c;精度、参数数量、训练数据量、性能和成本之间的权衡是一个复杂且多维度的问题。以下是这些因素之间关系的简要分析&…

Docker 安装 禅道-21.2版本-外部数据库模式

Docker 安装系列 1、拉取最新版本&#xff08;zentao 21.2&#xff09; [rootTseng ~]# docker pull hub.zentao.net/app/zentao Using default tag: latest latest: Pulling from app/zentao 55ab1b300d4b: Pull complete 6b5749e5ef1d: Pull complete bdccb03403c1: Pul…

排序算法 (插入,选择,冒泡,希尔,快速,归并,堆排序)

排序:经常在算法题中作为一个前置操作,为了之后的贪心or else做个铺垫,虽然我们经常都只是调用个sort,但是了解一些排序算法可以扩充下知识库 排序的分类: 从存储设备角度&#xff1a; ✓ 内排序&#xff1a;在排序过程中所有数据元素都在内存中&#xff1b; ✓ 外排序&a…

web复习(五)

一、补零 二、打印出五行五列的星星 三、用户输入行数和列数并打印相应行数和列数的⭐ 四、打印倒三角星星&#xff08;第一行一个&#xff0c;第二行两个...&#xff0c;以此类推&#xff09; 五、用户输入秒数&#xff0c;可以自动转换为时分秒 六、随机点名

法规标准-C-NCAP评测标准解析(2024版)

文章目录 什么是C-NCAP&#xff1f;C-NCAP 评测标准C-NCAP评测维度三大维度的评测场景及对应分数评星标准 自动驾驶相关评测场景评测方法及评测标准AEB VRU——评测内容(测什么&#xff1f;)AEB VRU——评测方法(怎么测&#xff1f;)车辆直行与前方纵向行走的行人测试场景&…

XRP价格跌破2.20美元 1.94美元是否下一波牛市的关键支撑?

原文转自&#xff1a;XRP价格跌破2.20美元 1.94美元是否下一波牛市的关键支撑&#xff1f; - 币热网 - 区块链数字货币新闻消息资讯 XRP价格经历剧烈波动后强势反弹&#xff0c;$1.94或成新牛市关键支撑 在过去24小时内&#xff0c;XRP价格经历了一场过山车式的剧烈波动。价…

centos-stream9系统安装docker

如果之前安装过docker需要删除之前的。 sudo dnf -y remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 安装yum-utils工具&#xff1a; dnf -y install yum-utils dnf-plugin…

H264编解码标准码流分析:SPS语法

H264编解码标准 SPS 语法解析 解释:H264编解码标准中的SPS(Sequence Parameter Set,序列参数集)是一组编码视频序列的全局参数,包含了视频编码序列的基本属性和配置信息。分析工具:elecard streamEye、elecard StreamAnalyzer、h264Visa 等elecard StreamAnalyzer 展示形…

使用 AI 辅助开发一个开源 IP 信息查询工具:一

本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 写在前面 在写代码时&#xff0c;总是会遇到一些有趣的机缘巧合。前几天&#xff0c;我在翻看自己之前的开源项目时&#xff0c;又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程&am…

门控循环单元(GRU):深度学习中的序列数据处理利器

目录 ​编辑 引言 GRU的诞生背景 GRU的核心机制 GRU的计算过程 GRU的数学公式 GRU的应用领域 代码示例&#xff1a;PyTorch中的GRU GRU与LSTM的比较 参数比较 GRU的技术发展 BiGRU&#xff08;双向GRU&#xff09; BiGRU的实现示例 GRU与CNN的结合 GRU的应用案例…

Sui 基金会任命 Christian Thompson 为新任负责人

Sui 基金会是专注于推动 Sui 蓬勃发展的生态增长与采用的机构。近日&#xff0c;基金会宣布任命 Christian Thompson 为新任负责人。在 Sui 主网发布的开创性一年里&#xff0c;Sui 凭借其无与伦比的速度、可扩展性和效率&#xff0c;迅速崛起为领先的 Layer 1 区块链之一&…

Vue2五、商品分类:My-Tag表头组件,My-Table整个组件

准备&#xff1a; 安包 npm less less-loader。拆分&#xff1a;一共分成两个组件部分&#xff1a; 1&#xff1a;My-Tag 标签一个组件。2&#xff1a;My-Table 整体一个组件&#xff08;表头不固定&#xff0c;内容不固定&#xff08;插槽&#xff09;&#xff09; 一&…

mysql运维篇笔记——日志,主从复制,分库分表,读写分离

目录 日志 错误日志 二进制日志 查询日志 慢查询日志 主从复制 概念&#xff1a; 优点&#xff1a; 原理&#xff1a; 搭建&#xff1a; 1&#xff0c;服务器准备 2&#xff0c;主库配置 3&#xff0c;从库配置 4&#xff0c;测试 分库分表&#xff1a; 介绍 问题分析 中心思想…

【JavaEE初阶】线程 和 thread

本节⽬标 认识多线程 掌握多线程程序的编写 掌握多线程的状态 一. 认识线程&#xff08;Thread&#xff09; 1概念 1) 线程是什么 ⼀个线程就是⼀个 "执⾏流". 每个线程之间都可以按照顺序执⾏⾃⼰的代码. 多个线程之间 "同时" 执⾏着多份代码. 还…

设计模式期末复习

一、设计模式的概念以及分类 二、设计模式的主题和意图 设计模式的主题是关于软件设计中反复出现的问题以及相应的解决方案。这些主题是基于长期实践经验的总结&#xff0c;旨在提供一套可复用的设计思路和框架&#xff0c;以应对软件开发中的复杂性和变化性。 三、面向对象程…

【小白51单片机专用教程】protues仿真AT89C51入门

课程特点 无需开发板0基础教学软件硬件双修辅助入门 本课程面对纯小白&#xff0c;因此会对各个新出现的知识点在实例基础上进行详细讲解&#xff0c;有相关知识的可以直接跳过。课程涉及protues基本操作、原理图设计、数电模电、kell使用、C语言基本内容&#xff0c;所有涉及…

MFC用List Control 和Picture控件实现界面切换效果

添加List Control 和Picture控件 添加 3个子窗体 把子窗体边框设置为None, 样式设为Child 声明 CListCtrl m_listPageForm;void ShowForm(int nIndex);void CreatFormList();void CMFCApplication3Dlg::DoDataExchange(CDataExchange* pDX) {CDialogEx::DoDataExchange(pDX);DD…