本文主要探寻kprobe的执行路径,也就是说如何trap到kprobe,以及如何回到原路径继续执行。 unregister_kprobe(&kp); pr_info("kprobe at %p unregistered\n", kp.addr); } module_init(kprobe_init 深入探究 是否只能基于symbol_name做kprobe? 显然不太可能,struct kprobe中有一个addr成员,很明显是可以直接基于地址做kprobe的。 这个肯定要分析代码了,好在代码相当简单: register_kprobe |------arm_kprobe | |------__arm_kprobe | | |---- --arch_arm_kprobe /* arm kprobe: install breakpoint in text */ void __kprobes arch_arm_kprobe(struct
我总劝人不要用 kretprobe 耍技巧,会脱手, Linux kernel 的 kretprobe 机制和 kprobe 完全不同,本质原因在于,函数的入口地址是固定的,但函数的返回地址不固定,由于返回位置不固定 因此,kprobe 在register 时即可将 hook 挂载,与 kprobe 不同,kretprobe 需在函数每次被调用时才能将 hook 挂载。 该函数的注释是:This kprobe pre handler is registered with every kretprobe. 工人提出用 kretprobe 修改 init cwnd,经理会说这是非标的方案,kprobe/kretprobe 更多只做 debug 和可观测性,不能上线...但工人想修改 nit cwnd 却没方案时 经理知道 kprobe/kretprobe,但并不真懂,仅科普水平,或做过 demo,但也仅此。对技术细节非常感兴趣又亲自指挥细节的经理不是合格经理。
kprobe(内核动态探测) 定义与作用 kprobe 是一种用于在内核空间进行动态探测的机制。它允许开发者在不修改内核源代码的情况下,在内核函数的特定位置插入探测点。 例如,可以在系统调用的入口或出口处插入 kprobe,这样就能捕获系统调用的相关信息,如参数、返回值等。 工作原理 当在一个内核函数上设置 kprobe 时,内核会在该函数的入口处插入一个断点。 例如,在sys_open系统调用函数入口设置 kprobe,当进程执行open系统调用时,就会触发 kprobe 的处理程序,从而可以获取到open系统调用的参数,如文件名、打开模式等。 例如,通过在关键的内核调度函数上设置 kprobe,统计每个函数调用的时间开销,进而优化内核性能。 故障排查:在系统出现内核相关的故障时,如内核崩溃或者异常行为,kprobe 可以帮助定位问题。 虽然 ftrace 和 kprobe 不完全相同,但其中列出的许多函数也是可以使用 kprobe 进行跟踪的。不过需要注意的是,这个列表可能会受到内核配置和模块加载情况的影响。
kprobe的工作过程大致如下: 注册kprobe。 注册的每个kprobe对应一个kprobe结构体,该结构体记录着插入点(位置),以及该插入点本来对应的指令original_opcode; 替换原有指令。 步骤2,3,4便是一次kprobe工作的过程,它的一个基本思路就是将本来执行一条指令扩展成执行kprobe->pre_handler--->指令--->kprobe-->post_handler这样三个过程 在异常态中,内核通过BRK指令的错误码判断这是一个kprobe异常,于是进入了kprobe处理函数。 kprobe异常处理函数会根据发生异常的地址来找到对应的kprobe(kprobe的addr域记录着地址),执行kprobe的pre_handler函数,然后设置single-step相关的寄存器,为下一步执行原指令时发生
kprobe是什么? kprobe代码示例 sample/kprobe/kprobe_example.c // sample/kprobe/kprobe_example.c #include <linux/kernel.h static struct hlist_head kprobe_table[KPROBE_TABLE_SIZE]; struct kprobe *get_kprobe(void *addr) { [hash_ptr(p->addr, KPROBE_HASH_BITS)]); ..} kprobe的注册 kprobe注册时传入的是struct kprobe结构体,里面包含指令地址或者函数名地址和函数内偏移 kprobe的应用 1.trace_kprobe kernel ftrace子系统基于kprobe实现了kprobe_event,可以probe任意函数。
本文首先简单描述这3种探测技术的原理与区别,然后主要围绕其中的kprobe技术进行分析并给出一个简单的实例介绍如何利用kprobe进行内核函数探测,最后分析kprobe的实现过程(jprobe和kretprobe 三、kprobe使用实例 在分析kprobe的实现之前先来看一下如何利用kprobe对函数进行探测,以便于让我们对kprobre所完成功能有一个比较清晰的认识。 下面来分别介绍: 1、编写kprobe探测模块 内核提供了一个struct kprobe结构体以及一系列的内核API函数接口,用户可以通过这些接口自行实现探测回调函数并实现struct kprobe结构 涉及的API函数接口如下: int register_kprobe(struct kprobe *kp) //向内核注册kprobe探测点 void unregister_kprobe(struct kprobe *kp) //临时暂停指定探测点的探测 int enable_kprobe(struct kprobe *kp) //恢复指定探测点的探测 1.2、用例kprobe_example.c
2、注册一个kprobe实例 kprobe探测模块调用register_kprobe向kprobe子系统注册一个kprobe探测点实例,代码路径kernel/kprobes.c int register_kprobe 接下来调用再次copy_kprobe将aggr kprobe中保存的指令opcode和ainsn字段拷贝到本次要注册的kprobe的对应字段中,然后调用add_new_kprobe函数将新注册的kprobe //重复触发kprobe #define KPROBE_HIT_SSDONE 0x00000008 //kprobe单步执行阶段结束 而prev_kprobe则是用于在kprobe 函数设置当前正在处理的kprobe并更新kprobe状态标识为KPROBE_HIT_ACTIVE,表明开始处理该kprobe。 函数保存当前正在运行的kprobe,然后绑定p和current_kprobe并设置kprobe_status为KPROBE_REENTER;对于非重入的情况则设置kprobe_status为KPROBE_HIT_SS
Linux内核调试技术——kprobe使用与实现(一) Linux内核调试技术——kprobe使用与实现(二) Linux内核调试技术——kprobe使用与实现(三) Linux内核调试技术——kprobe 使用与实现(四)--kprobe内核注册过程 kprobe探测模块调用register_kprobe向kprobe子系统注册一个kprobe探测点实例,代码路径kernel/kprobes.c ? 回到register_kprobe函数中,下面调用check_kprobe_rereg函数防止同一个kprobe实例被重复注册,其中check_kprobe_rereg->__get_valid_kprobe 回到register_aggr_kprobe函数中,如果本次是第二次以上向同一地址注册kprobe实例,则此时的orig_p已经是aggr kprobe了,则会调用kprobe_unused函数判断该kprobe 接下来调用再次copy_kprobe将aggr kprobe中保存的指令opcode和ainsn字段拷贝到本次要注册的kprobe的对应字段中,然后调用add_new_kprobe函数将新注册的kprobe
Linux内核调试技术——kprobe使用与实现(一) 在上一篇文章中介绍了内核加载的方式使用kprobe的方法,现在介绍一下使用debugfs接口使用kprobe的方法。 debugfs下(确切地说,应该是ftrace)提供了一套注册、使能、注销kprobe的接口,可以很方便地操作kprobe。 >kprobe_events 命令可以将上面两个kprobe删除。 2 .开启某个kprobe 创建kprobe的时候,会在events/kprobes/下为每个probe创建一个目录,目录下有这个kprobe相关的接口。下面是开启kprobe的方式。 ? 3. ,第二个是进程PID,触发kprobe的时候记录的。
在《Linux 内核调试利器 | kprobe 的使用》一文中,我们介绍过怎么使用 kprobe 来追踪内核函数,而本文将会介绍 kprobe 的原理和实现。 kprobe 原理 kprobe 可以用来跟踪内核函数中某一条指令在运行前和运行后的情况。 下图展示了 kprobe 的执行流程: (图4) kprobe 实现 了解了 kprobe 的原理后,现在我们开始分析 kprobe 的代码实现。 kprobe模块哈希表 我们在《Linux 内核调试利器 | kprobe 的使用》一文中介绍过,一个 kprobe 模块是由一个 struct kprobe 结构来描述的。 注册 kprobe 实例 在《Linux 内核调试利器 | kprobe 的使用》一文中介绍过,编写好的 kprobe 模块需要通过调用 register_kprobe() 函数来注册到内核。
Linux内核调试技术——kprobe使用与实现(一) Linux内核调试技术——kprobe使用与实现(二) 对于kprobe功能的实现主要利用了内核中的两个功能特性:异常(尤其是int 3),单步执行 主要包括kprobes的初始化、注册kprobe和触发kprobe(包括arm结构和x86_64架构的回调函数和single-step单步执行) 本篇文章首先介绍kprobe的初始化过程。 ? 接下来调用populate_kprobe_blacklist函数将kprobe实现相关的代码函数保存到kprobe_blacklist这个链表中去,用于后面注册探测点时判断使用,注意这里的__start_kprobe_blacklist 例如其中的get_kprobe函数: ? 再次回到init_kprobes函数,接下来分别注册die和module的内核通知链kprobe_exceptions_nb和kprobe_module_nb: ? ?
Linux内核提供的基础设施: tarcepoints => 静态探测点 kprobe => 内核态动态探测点(kernel/kprobe.c, example:sample/kprobe) uprobe 经过大量实验探索,依赖少并可深入自定义的,kprobe胜出。 三、利用kprobe分析kworker行为 调试源码见附录,kprobe使用及原理自行学习,本文不做基础介绍,重点在思路、步骤,主要展示方法论的形成过程。 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
Linux内核调试技术——kprobe使用与实现(一) Linux内核调试技术——kprobe使用与实现(二) Linux内核调试技术——kprobe使用与实现(三) Linux内核调试技术——kprobe 使用与实现(四) Linux内核调试技术——kprobe使用与实现(五)-触发kprobe探测和回调 前文中,从register_kprobe函数注册kprobe的流程已经看到,用户指定的被探测函数入口地址处的指令已经被替换成架构相关的 1,针对reenter的情况会将kprobe_status状态设置为KPROBE_REENTER并调用save_previous_kprobe执行保存当前kprobe的操作)。 2、p存在但curent_kprobe不存在 ? 这是一般最通用的kprobe执行流程,首先调用set_current_kprobe绑定p为当前正在处理的kprobe: ? 并设置kprobe_status为KPROBE_REENTER;对于非重入的情况则设置kprobe_status为KPROBE_HIT_SS。
二、kprobe原理 下面来介绍一下kprobe是如何工作的。具体流程见下图: ? 三、kprobe使用实例 在分析kprobe的实现之前先来看一下如何利用kprobe对函数进行探测,以便于让我们对kprobre所完成功能有一个比较清晰的认识。 下面来分别介绍: 1、编写kprobe探测模块 内核提供了一个struct kprobe结构体以及一系列的内核API函数接口,用户可以通过这些接口自行实现探测回调函数并实现struct kprobe结构 涉及的API函数接口如下: int register_kprobe(struct kprobe *kp) //向内核注册kprobe探测点 void unregister_kprobe(struct kprobe *kp) //临时暂停指定探测点的探测 int enable_kprobe(struct kprobe *kp) //恢复指定探测点的探测 1.2、用例kprobe_example.c
(&kp);//其实这里才是入口,ko向kernel注册kprobe if (ret < 0) { printk(KERN_INFO "register_kprobe failed (cth); unregister_kprobe(&kp); printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr); } module_init (kprobe_init); module_exit(kprobe_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("PiZhenwei p_ace@126.com 本质来说,systemtap也是用kprobe实现的(不过它需要debug symbol,也就是vmlinux,不过也可以捕获更精确的代码,原因在后面的kprobe实现一起分析)。 所谓内核热补丁,也可以用kprobe实现。在不重启内核的情况下,动态加载ko,修改内核行为。
计算链表的长度 基于kprobe实现kmod,来dump出来链表的长度。 (void) { int ret; ret = register_kprobe(&kp); if (ret < 0) { pr_err("register_kprobe failed ]kprobe at %p unregistered\n", kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE (void) { int ret; ret = register_kprobe(&kp); if (ret < 0) { pr_err("register_kprobe failed ]kprobe at %p unregistered\n", kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE
(&kp); return 0;} static void __exit kprobe_exit(void){ unregister_kprobe(&kp); pr_info("kprobe pt_regs *regs){ kprobe_opcode_t *addr; struct kprobe *p; struct kprobe_ctlblk *kcb; addr = (kprobe_opcode_t *)(regs->ip - sizeof(kprobe_opcode_t)); kcb = get_kprobe_ctlblk(); / kprobe *cur = kprobe_running(); struct kprobe_ctlblk *kcb = get_kprobe_ctlblk(); // 恢复指令为系统函数的指令 = KPROBE_REENTER) && cur->post_handler) { kcb->kprobe_status = KPROBE_HIT_SSDONE; cur
本文是 eBPF 入门开发实践指南的第二篇,在 eBPF 中使用 kprobe 捕获 unlink 系统调用。 kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。 kprobes的特点与使用限制: kprobes允许在同一个被被探测位置注册多个kprobe,但是目前jprobe却不可以;同时也不允许以其他的jprobe回调函数和kprobe的post_handler > char LICENSE[] SEC("license") = "Dual BSD/GPL"; SEC("kprobe/do_unlinkat") int BPF_KPROBE(do_unlinkat EXIT: pid = %d, ret = %ld\n", pid, ret); return 0; } kprobe 是 eBPF 用于处理内核空间入口和出口(返回)探针(kprobe 和 kretprobe
2、jprobe实现分析 jpeobe的实现基于kprobe,在其基础之上分析它的实现,述主要包括jprobe注册流程和触发探测流程,涉及kprobe的部分不再详细描。 可见jprobe的注册流程非常的简单,它的本质就是注册一个kprobe,利用kprobe机制实现探测,只是探测回调函数并非用户自己定义,使用jprobe私有的而已。 在注册完成后,jprobe(kprobe)机制启动,当函数调用流程执行到被探测函数时就会触发jprobe(kprobe)探测。 最后需要注意的是,jprobe是不能在同一个被探测点注册多个的,在kprobe的注册流程register_kprobe->register_aggr_kprobe->add_new_kprobe中会有判断 2.3、触发jprobe探测 基于kprobe机制,在执行到被探测函数后,会触发CPU异常,按照kprobe的执行流程,由kprobe_handler函数调用到pre_handler回调函数,即setjmp_pre_handler
本文是 eBPF 入门开发实践教程的第二篇,在 eBPF 中使用 kprobe 捕获 unlink 系统调用。 kprobes 技术包括的3种探测手段分别时 kprobe、jprobe 和 kretprobe。 kprobes 的特点与使用限制:kprobes 允许在同一个被探测位置注册多个 kprobe,但是目前 jprobe 却不可以;同时也不允许以其他的 jprobe 回调函数和 kprobe 的 post_handler <bpf/bpf_core_read.h>char LICENSE[] SEC("license") = "Dual BSD/GPL";SEC("kprobe/do_unlinkat")int BPF_KPROBE SEC("kprobe/do_unlinkat")int BPF_KPROBE(do_unlinkat, int dfd, struct filename *name){ pid_t pid;