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,看不到真实的地址。


参考资料:

  1. Linux kprobe调试技术使用
  2. Linux内核调试技术——kprobe使用与实现

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

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

相关文章

【css】z-index与层叠上下文

z-index属性用来设置元素的堆叠顺序&#xff0c;使用z-index有一个大的前提&#xff1a;z-index所作用元素的样式列表中必须有position属性并且属性值为absolute、relative或fixed中的一个&#xff0c;否则z-index无效。 层叠上下文 MDN讲解 我们给元素设置的z-index都是有一…

E5071C是德科技网络分析仪

描述 E5071C网络分析仪提供同类产品中最高的RF性能和最快的速度&#xff0c;具有宽频率范围和多功能。E5071C是制造和R&D工程师评估频率范围高达20 GHz的RF元件和电路的理想解决方案。特点: 宽动态范围:测试端口的动态范围> 123 dB(典型值)快速测量速度:41毫秒全2端口…

【原创】H3C三层交换机的路由模式

网络拓扑图 将三层交换机当路由器使用 交换机配置 <H3C>dis stp briefMST ID Port Role STP State Protection0 GigabitEthernet1/0/1 DESI LEARNING NONE0 GigabitEthernet1/0/2 …

国标视频融合云平台EasyCVR视频汇聚平台关于远程控制的详细介绍

EasyCVR国标视频融合云平台是一个能在复杂网络环境下统一汇聚、整合和集中管理各类分散视频资源的平台。该平台提供了多种视频能力和服务&#xff0c;包括视频监控直播、云端录像、云存储、录像检索与回看、智能告警、平台级联、集群、电子地图、H.265视频自动转码和智能分析等…

Solidity 小白教程:6. 引用类型, array, struct

Solidity 小白教程&#xff1a;6. 引用类型, array, struct 这一讲&#xff0c;我们将介绍solidity中的两个重要变量类型&#xff1a;数组&#xff08;array&#xff09;和结构体&#xff08;struct&#xff09;。 数组 array 数组&#xff08;Array&#xff09;是solidity常…

java+springboot+mysql校园跑腿管理系统

项目介绍&#xff1a; 使用javaspringbootmysql开发的校园跑腿管理系统&#xff0c;系统包含超级管理员&#xff0c;系统管理员、用户角色&#xff0c;功能如下&#xff1a; 超级管理员&#xff1a;管理员管理&#xff1b;用户管理&#xff08;充值&#xff09;&#xff1b;任…

vscode调教配置:快捷修复和格式化代码

配置vscode快捷键&#xff0c;让你像使用idea一样使用vscode&#xff0c;我们最常用的两个功能就是格式化代码和快捷修复&#xff0c;所以这里修改一下快捷修复和格式化代码的快捷键。 在设置中&#xff0c;找到快捷键配置&#xff1a; 然后搜索&#xff1a;快捷修复 在快捷键…

leetcode 503. 下一个更大元素 II

2023.8.28 本题类似于下一个更大元素I &#xff0c;区别就是数组变成循环的了&#xff0c;可以将nums数组先double一下&#xff0c;如&#xff1a;{1&#xff0c;2&#xff0c;1}变成{1&#xff0c;2&#xff0c;1&#xff0c;1&#xff0c;2&#xff0c;1}&#xff0c;再用单调…

易基因:De novo组装和转录组表征:东方田鼠原发性卵巢癌机制新见解|项目文章

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 2022年&#xff0c;中南大学湘雅医学院周智君教授团队在Mol Med Rep发表了题为" De novo assembly and transcriptome characterization: Novel insights into the mechanisms of p…

8.react18并发模式与startTransition(搜索高亮思路)

React 18 之前,渲染是一个单一的,不间断的,同步的事务,一旦渲染开始,就不能被中断 React 18引入并发模式,它允许你将标记更新作为一个transitions,这会告诉React他们可以被中断执行.这样可以将紧急任务先更新,不紧急任务后更新. 将任务给紧急任务先执行, 优先级低的任务后执行…

Navicat15天试用期过期解决办法

如果你是windows电脑&#xff0c;发现过期了先把Nvaicat关掉&#xff0c;按照以下步骤可以恢复到15天试用。 1.注册表输入regedit winR打开注册表 2.搜索输入HKEY_CURRENT_USER\Software\PremiumSoft\Navicat 删除Registration15XCS和Update这两个文件夹。 3.搜索HKEY_CURRE…

灾备中的网络加密是什么?

什么是网络加密&#xff1f; 在网络设计之初&#xff0c;对网络安全的问题考虑的不周全&#xff0c;数据在链路上传输的时候都是明文传输的&#xff0c;稍微有点技术的人&#xff0c;都可以轻松的拿到网络上的数据流量&#xff0c;并进行查看。试想一下&#xff0c;如果我们为…