Linux 调试技术 Kprobe
目录
- 用途:
- 一、技术背景
- 1.1 kprobes的特点与使用限制
- 1.2 kprobe原理
- 二、 基于kprobe探测模块的探测方式
- 2.1、struct kprobe结构体
- 2.2 kprobe API函数
- 2.3 示例代码
- 参考资料:
用途:
判断内核函数是否被调用,获取调用上下文、入参以及返回值。
一、技术背景
如果需要知道内核函数是否被调用、被调用上下文、入参以及返回值,比较简单的方法是加printk,但是效率低。
利用kprobe技术,用户可以自定义自己的回调函数,可以再几乎所有的函数中动态插入探测点。
当内核执行流程执行到指定的探测函数时,会调用该回调函数,用户即可收集所需的信息了,同时内核最后还会回到原本的正常执行流程。如果用户已经收集足够的信息,不再需要继续探测,则同样可以动态的移除探测点。
kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。
首先kprobe是最基本的探测方式,是实现后两种的基础,它可以在任意的位置放置探测点(就连函数内部的某条指令处也可以),它提供了探测点的调用前、调用后和内存访问出错3种回调方式,分别是pre_handler、post_handler和fault_handler,其中pre_handler函数将在被探测指令被执行前回调,post_handler会在被探测指令执行完毕后回调(注意不是被探测函数),fault_handler会在内存访问出错时被调用;jprobe基于kprobe实现,它用于获取被探测函数的入参值;最后kretprobe从名字种就可以看出其用途了,它同样基于kprobe实现,用于获取被探测函数的返回值。
1.1 kprobes的特点与使用限制
1、kprobes允许在同一个被被探测位置注册多个kprobe,但是目前jprobe却不可以;同时也不允许以其他的jprobe回掉函数和kprobe的post_handler回调函数作为被探测点。
2、一般情况下,可以探测内核中的任何函数,包括中断处理函数。不过在kernel/kprobes.c和arch/*/kernel/kprobes.c程序中用于实现kprobes自身的函数是不允许被探测的,另外还有do_page_fault和notifier_call_chain;
3、如果以一个内联函数为探测点,则kprobes可能无法保证对该函数的所有实例都注册探测点。由于gcc可能会自动将某些函数优化为内联函数,因此可能无法达到用户预期的探测效果;
4、一个探测点的回调函数可能会修改被探测函数运行的上下文,例如通过修改内核的数据结构或者保存与struct pt_regs结构体中的触发探测之前寄存器信息。因此kprobes可以被用来安装bug修复代码或者注入故障测试代码;
5、kprobes会避免在处理探测点函数时再次调用另一个探测点的回调函数,例如在printk()函数上注册了探测点,则在它的回调函数中可能再次调用printk函数,此时将不再触发printk探测点的回调,仅仅时增加了kprobe结构体中nmissed字段的数值;
6、在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存;
7、kprobes回调函数的运行期间是关闭内核抢占的,同时也可能在关闭中断的情况下执行,具体要视CPU架构而定。因此不论在何种情况下,在回调函数中不要调用会放弃CPU的函数(如信号量、mutex锁等);
8、kretprobe通过替换返回地址为预定义的trampoline的地址来实现,因此栈回溯和gcc内嵌函数__builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址;
9、如果一个函数的调用此处和返回次数不相等,则在类似这样的函数上注册kretprobe将可能不会达到预期的效果,例如do_exit()函数会存在问题,而do_execve()函数和do_fork()函数不会;
10、如果当在进入和退出一个函数时,CPU运行在非当前任务所有的栈上,那么往该函数上注册kretprobe可能会导致不可预料的后果,因此,kprobes不支持在X86_64的结构下为__switch_to()函数注册kretprobe,将直接返回-EINVAL。
1.2 kprobe原理
kprobe工作具体流程见下图:
1、当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案Jump Optimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令);
2、当CPU流程执行到探测点的断点指令时,就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息;
3、随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行;
4、在单步执行完成后,kprobe执行用户注册的post_handler回调函数;
5、最后,执行流程回到被探测指令之后的正常流程继续执行。
二、 基于kprobe探测模块的探测方式
2.1、struct kprobe结构体
内核提供了struct kprobe表示一个探测点,以及一系列API接口,用户可以通过这些接口实现回调函数并实现struct kprobe结构,然后将它注册到内核的kprobe子系统中来达到探测的目的。
struct kprobe {struct hlist_node hlist;-----------------------------------------------被用于kprobe全局hash,索引值为被探测点的地址。/* list of kprobes for multi-handler support */struct list_head list;-------------------------------------------------用于链接同一被探测点的不同探测kprobe。/*count the number of times this probe was temporarily disarmed */unsigned long nmissed;/* location of the probe point */kprobe_opcode_t *addr;-------------------------------------------------被探测点的地址。/* Allow user to indicate symbol name of the probe point */const char *symbol_name;-----------------------------------------------被探测函数的名称。/* Offset into the symbol */unsigned int offset;---------------------------------------------------被探测点在函数内部的偏移,用于探测函数内核的指令,如果该值为0表示函数的入口。/* Called before addr is executed. */kprobe_pre_handler_t pre_handler;--------------------------------------被探测点指令执行之前调用的回调函数。/* Called after addr is executed, unless... */kprobe_post_handler_t post_handler;------------------------------------被探测点指令执行之后调用的回调函数。kprobe_fault_handler_t fault_handler;----------------------------------在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数。kprobe_break_handler_t break_handler;----------------------------------在执行某一kprobe过程中出发了断点指令后会调用该函数,用于实现jprobe。kprobe_opcode_t opcode;------------------------------------------------保存的被探测点原始指令。struct arch_specific_insn ainsn;---------------------------------------被复制的被探测点的原始指令,用于单步执行,架构强相关。u32 flags;-------------------------------------------------------------状态标记。
};
2.2 kprobe API函数
内核使用kprobe,可以使用register_kprobe()/unregister_kprobe()进行注册/卸载,还可以临时关闭/使能探测点。
int register_kprobe(struct kprobe *p);--------------------------注册kprobe探测点
void unregister_kprobe(struct kprobe *p);-----------------------卸载kprobe探测点
int register_kprobes(struct kprobe **kps, int num);-------------注册多个kprobe探测点
void unregister_kprobes(struct kprobe **kps, int num);----------卸载多个kprobe探测点
int disable_kprobe(struct kprobe *kp);--------------------------暂停指定定kprobe探测点
int enable_kprobe(struct kprobe *kp);---------------------------回复指定kprobe探测点
void dump_kprobe(struct kprobe *kp);----------------------------打印指定kprobe探测点的名称、地址、偏移
2.3 示例代码
下面以内核中samples/kprobes/kprobe_example.c为例,介绍如何使用kprobe进行内核函数探测。
该kprobe实例实现了_do_fork的探测,该函数会在fork系统调用或者kernel_kthread创建内核线程时被调用。
对原%p修改为%pF后,可读性更强。可以显示函数名称以及偏移量。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>#define MAX_SYMBOL_LEN 64
static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
module_param_string(symbol, symbol, sizeof(symbol), 0644);/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {---------------------------------------------------------定义一个实例kp并初始化symbol_name为"_do_fork",将探测_do_fork函数。.symbol_name = symbol,
};/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
#ifdef CONFIG_X86pr_info("<%s> pre_handler: p->addr = %pF, ip = %lx, flags = 0x%lx\n",p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
#ifdef CONFIG_ARM64pr_info("<%s> pre_handler: p->addr = %pF, pc = 0x%lx,"" pstate = 0x%lx\n",p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
#endif/* A dump_stack() here will give a stack backtrace */return 0;
}/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,unsigned long flags)
{
#ifdef CONFIG_X86pr_info("<%s> post_handler: p->addr = %pF, flags = 0x%lx\n",p->symbol_name, p->addr, regs->flags);
#endif
#ifdef CONFIG_ARM64pr_info("<%s> post_handler: p->addr = %pF, pstate = 0x%lx\n",p->symbol_name, p->addr, (long)regs->pstate);
#endif
}/** fault_handler: this is called if an exception is generated for any* instruction within the pre- or post-handler, or when Kprobes* single-steps the probed instruction.*/
static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{pr_info("fault_handler: p->addr = %pF, trap #%dn", p->addr, trapnr);/* Return 0 because we don't handle the fault. */return 0;
}static int __init kprobe_init(void)
{int ret;kp.pre_handler = handler_pre;---------------------------------------------------初始化kp的三个回调函数。kp.post_handler = handler_post;kp.fault_handler = handler_fault;ret = register_kprobe(&kp);-----------------------------------------------------注册kp探测点到内核。if (ret < 0) {pr_err("register_kprobe failed, returned %d\n", ret);return ret;}pr_info("Planted kprobe at %pF\n", kp.addr);return 0;
}static void __exit kprobe_exit(void)
{unregister_kprobe(&kp);pr_info("kprobe at %pF unregistered\n", kp.addr);
}module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
模块的编译Makefile如下:
obj-m := kprobe_example.oCROSS_COMPILE=''KDIR := /lib/modules/$(shell uname -r)/build
all:make -C $(KDIR) M=$(PWD) modules
clean:rm -f *.ko *.o *.mod.o *.mod.c .*.cmd *.symvers modul*
执行结果如下:
[ 9363.905687] Planted kprobe at _do_fork+0x0/0x3f0
[ 9366.924852] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x3f0, ip = ffffffff88a86a61, flags = 0x246
[ 9366.924858] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x3f0, flags = 0x246
[ 9366.932935] <_do_fork> pre_handler: p->addr = _do_fork+0x0/0x3f0, ip = ffffffff88a86a61, flags = 0x246
[ 9366.932938] <_do_fork> post_handler: p->addr = _do_fork+0x0/0x3f0, flags = 0x246
[ 9366.957594] kprobe at _do_fork+0x0/0x3f0 unregistered
可以通过sudo cat /proc/kallsyms | grep l_do_fork来验证地址和符号是否对应。
若没有sudo,看不到真实的地址。
参考资料:
- Linux kprobe调试技术使用
- Linux内核调试技术——kprobe使用与实现
相关文章:

Linux 调试技术 Kprobe
目录 用途:一、技术背景1.1 kprobes的特点与使用限制1.2 kprobe原理 二、 基于kprobe探测模块的探测方式2.1、struct kprobe结构体2.2 kprobe API函数2.3 示例代码参考资料: 用途: 判断内核函数是否被调用,获取调用上下文、入参以…...

一文了解评估 K8s 原生存储产品需要关注的关键能力
近些年,越来越多的企业使用 Kubernetes(K8s)支持生产环境关键业务。这些业务往往对存储性能和稳定性具有更高的要求,传统存储方案难以充分满足,因此不少用户开始关注更契合 K8s 环境的 K8s 原生存储方案。 不过&#…...

linux免密登录报错 Bad owner or permissions on /etc/ssh/ssh_config.d/05-redhat.conf
问题:权限不对的 解决: 1.检查文件的所有者和权限。 确保文件的所有者是正确的。 运行以下命令来确定文件的所有者和权限: ls -l /etc/ssh/ssh_config.d/05-redhat.conf 通常情况下,SSH配置文件应该属于root用户。如果所有者不是…...

Kafka常用参数
文章目录 概要broker端参数producer端参数consumer端参数 概要 kafka broker、consumer、和producer都有很多可配置的参数。本文主要总结日常开发中常用到的参数。其中producer端可以在org.apache.kafka.clients.producer.ProducerConfig 中找到配置项,consumer端可…...

NFT Insider#105:The Sandbox即将参加韩国区块链周,YGG的声誉和进步(RAP)将引领玩家晋升到下一层级
引言:NFT Insider由NFT收藏组织WHALE Members(https://twitter.com/WHALEMembers)、BeepCrypto(https://twitter.com/beep_crypto)联合出品,浓缩每周NFT新闻,为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周…...

TCP socket error (The proxy type is invalid for this operation).
“TCP socket error (The proxy type is invalid for this operation)” 错误通常是由于使用了无效的代理类型导致的。在使用QModbusTcpClient连接Modbus TCP设备时,如果您没有配置代理服务器,或者配置的代理类型不正确,就会出现这个错误。 …...

根据需求生成一个Vue模块的类图示例
以下是一个Vue模块的类图示例: ------------------------ | VueModule | ------------------------ | -name: string | | -data: object | | -methods: object | | -computed: object | | -watchers: object | ---…...

C# 类class、继承、多态性、运算符重载,相关练习题
34.函数重载 /*函数重载您可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。下面的实例演示了几个相同的函数 Add(),用于对…...

Mysql高级(进阶)SQL语句
目录 常用查询 按关键字排序 区间判断及查询不重复记录 对结果进行分组 限制结果条目 设置别名(alias —— as) 通配符 子查询 MySQL视图 NULL 值 连接查询 常用查询 (增、删、改、查) 对 MySQL 数据库的查询…...

java八股文面试[JVM]——JVM性能优化
JVM性能优化指南 JVM常用命令 jps 查看java进程 The jps command lists the instrumented Java HotSpot VMs on the target system. The command is limited to reporting information on JVMs for which it has the access permissions. jinfo (1)实时…...

联发科MTK6762/MT6762核心板_安卓主板小尺寸低功耗4G智能模块
MT6762安卓核心板是一款基于MTK平台的高性能智能模块,是一款工业级的产品。该芯片也被称为Helio P22。这款芯片内置了Arm Cortex-A53 CPU,最高可运行于2.0GHz。同时,它还提供灵活的LPDDR3/LPDDR4x内存控制器,此外,Medi…...

Redis未授权访问漏洞复现
Redis 简单使用 Redis 未设置密码,客户端工具可以直接链接。 Redis 是非关系型数据库系统,没有库表列的逻辑结构,仅仅以键值对的方式存储数据。 先启动容器 Redis 未设置密码,客户端工具可以直接链接 https://github.com/xk11z/…...

用深度强化学习来玩Flappy Bird
目录 演示视频 核心代码 演示视频 用深度强化学习来玩Flappy Bird 核心代码 import torch.nn as nnclass DeepQNetwork(nn.Module):def __init__(self):super(DeepQNetwork, self).__init__()self.conv1 nn.Sequential(nn.Conv2d(4, 32, kernel_size8, stride4), nn.ReLU(inp…...

HTML5-4-表单
文章目录 表单属性表单标签输入元素文本域(Text Fields)密码字段单选按钮(Radio Buttons)复选框(Checkboxes)按钮(button)提交按钮(Submit)label标签 文本框(textarea&am…...

Nacos 开源版的使用测评
文章目录 一、Nacos的使用二、Nacos和Eureka在性能、功能、控制台体验、上下游生态和社区体验的对比:三、记使使用Nacos中容易犯的错误四、对Nacos开源提出的一些需求 一、Nacos的使用 这里配置mysql的连接方式,spring.datasource.platformmysql是老版本…...

【Linux】一些常见查看各种各样信息的命令
Linux命令 find命令,用来查找文件。常用的按照名字查找-name,按照文件类型查找-type,linux常用的文件类型有七种,普通文件,目录文件,管道,套接字,软链接,块设备…...

51单片机DHT11温湿度控制系统仿真设计( proteus仿真+程序+原理图+报告+讲解视频)
51单片机DHT11温湿度控制系统仿真设计 1.主要功能:2.仿真3. 程序代码4. 原理图元器件清单5. 设计报告6. 设计资料内容清单&下载链接 51单片机DHT11温湿度控制系统仿真设计( proteus仿真程序原理图报告讲解视频) 仿真图proteus8.9及以上 程序编译器&…...

神仙级python入门教程(非常详细),从0到精通,从看这篇开始!
毫无疑问,Python 是当下最火的编程语言之一。对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握 Python 看似是一件十分困难的事。其实,只要掌握了科学的学习方法并制定了合理的学习计划,Python 从 入门到精通只需要一个月…...

详解4种类型的爬虫技术
聚焦网络爬虫是“面向特定主题需求”的一种爬虫程序,而通用网络爬虫则是捜索引擎抓取系统(Baidu、Google、Yahoo等)的重要组成部分,主要目的是将互联网上的网页下载到本地,形成一个互联网内容的镜像备份。 增量抓取意…...

QTday1基础
作业 一、做个QT页面 #include "hqyj.h"HQYJ::HQYJ(QWidget *parent)//构造函数定义: QWidget(parent)//显性调用父类的有参构造 {//主界面设置this->resize(540,410);//设置大小this->setFixedSize(540,410);//设置固定大小this->setWindowIcon(QIcon(&q…...

activiti 通过xml上传 直接部署模型
通过流程xml 直接先发布模型,然后再通过发布模型之后的流程定义获取bpmn model来创建Model. 1、通过xml先发布模型 InputStream bpmnStream multipartFile.getInputStream() deployment repositoryService.createDeployment().addInputStream(multipartFile.getO…...

算法题打卡day56-编辑距离 | 583. 两个字符串的删除操作、72. 编辑距离
583. 两个字符串的删除操作 - 力扣(LeetCode) 状态:查看思路后AC。 和查找子序列的操作类似,但是考虑的是删除操作。代码如下: class Solution { public:int minDistance(string word1, string word2) {int len1 wor…...

SQL中的CASE WHEN语句:从基础到高级应用指南
SQL中的CASE WHEN语句:从基础到高级应用指南 准备工作 - 表1: products 示例数据: 我们使用一个名为"Products"的表,包含以下列:ProductID、ProductName、CategoryID、UnitPrice、StockQuantity。 -- 建表 CREATE TA…...

超时取消子线程任务
文章目录 前言一、编码思路二、使用步骤直接上代码 总结 前言 问题背景: 主线程需要执行一些任务,不能影响主任务执行,这些任务有超时时间,当超过处理时间后,应该不予处理;如果未超时,应该获取到这些任务的执行结果; 一、编码思路 由于主线程正常执行不能影响,任务会处理很久…...

模块化---common.js
入口文件:app.js // require是同步加载 // 客户端:common.js的模块化,需要browserify编译之后才能使用 // 服务端:运行时同步加载,无问题 let module1 require(./module1.js) let module2 require(./module2.js) co…...

VSCode下载、安装及配置、调试的一些过程理解
第一步先下载了vscode,官方地址为:https://code.visualstudio.com/Download 第二步安装vscode,安装环境是win10,安装基本上就是一步步默认即可。 第三步汉化vscode,这一步就是去扩展插件里面下载一个中文插件即可&am…...

KC705开发板——MGT IBERT测试记录
本文介绍使用KC705开发板进行MGT的IBERT测试。 KC705开发板 KC705开发板的图片如下图所示。FPGA芯片型号为XC7K325T-2FFG900C。 MGT MGT是 Multi-Gigabit Transceiver的缩写,是Multi-Gigabit Serializer/Deserializer (SERDES)的别称。MGT包含GTP、GTX、GTH、G…...

前端代码优化散记
把import Button from xxx 的引入方式,变成import {Button} from xxx 的方式引入,以利于按需打包。原生监听事件、定时器等,必须在componentWillUnmount中清除,大型项目会发生内存泄露,极度影响性能。使用PureComponen…...

HTML <map> 标签的使用
map标签的用途:是与img标签绑定使用的,常被用来赋予给客户端图像某处区域特殊的含义,点击该区域可跳转到新的文档。 编写格式: <img src"图片" border"0" usemap"#planetmap" alt"Planets…...

stable diffusion实践操作-大模型介绍
本文专门开一节写大模型相关的内容,在看之前,可以同步关注: stable diffusion实践操作 模型下载网站 国内的是:https://www.liblibai.com 国外的是:https://civitai.com(科学上网) 一、发展历…...