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…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Bean 作用域有哪些?如何答出技术深度?
导语: Spring 面试绕不开 Bean 的作用域问题,这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开,结合典型面试题及实战场景,帮你厘清重点,打破模板式回答,…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
