目录
一、内核调试的痛点
二、kprobe的优点
三、kprobe探测点的要点
四、探测点的开销与优化
五、内核配置
六、API
七、程序架构
八、实例
内核调试,添加打印信息。在运行过程中想看某个函数的变量,需要重新编译内核。这样破坏了执行的过程。
kprobe 可以在系统运行期间,自定义回调函数,动态插入探测点。当内核执行到探测函数时,会调用回调函数。同时,也可以动态移除探测函数。
探测点的类型
2005年使用的典型CPU,kprobe命中需要0.5到1.0微秒来处理,返回探测命中的时间通常比kprobe命中的时间长50-75%
k = kprobe; r = return probe; kr = kprobe + return probe
on same function
i386: Intel Pentium M, 1495 MHz, 2957.31 bogomips
k = 0.57 usec; r = 0.92; kr = 0.99
x86_64: AMD Opteron 246, 1994 MHz, 3971.48 bogomips
k = 0.49 usec; r = 0.80; kr = 0.82
ppc64: POWER5 (gr), 1656 MHz (SMT disabled, 1 virtual CPU per physical CPU)
k = 0.77 usec; r = 1.26; kr = 1.45
优化的kprobe命中需要0.07到0.1微秒才能处理
k = unoptimized kprobe, b = boosted (single-step skipped), o = optimized kprobe,
r = unoptimized kretprobe, rb = boosted kretprobe, ro = optimized kretprobe.
i386: Intel(R) Xeon(R) E5410, 2.33GHz, 4656.90 bogomips
k = 0.80 usec; b = 0.33; o = 0.05; r = 1.10; rb = 0.61; ro = 0.33
x86-64: Intel(R) Xeon(R) E5410, 2.33GHz, 4656.90 bogomips
k = 0.99 usec; b = 0.43; o = 0.06; r = 1.24; rb = 0.68; ro = 0.30
CONFIG_KPROBES
CONFIG_KALLSYMS
CONFIG_KALLSYMS_ALL
CONFIG_DEBUG_INFO
#include
#include //注册
int register_kprobe(struct kprobe *kp);//探测前回调函数
int pre_handler(struct kprobe *p, struct pt_regs *regs);//探测后回调函数
void post_handler(struct kprobe *p, struct pt_regs *regs,unsigned long flags);int disable_kprobe(struct kprobe *kp);int enable_kprobe(struct kprobe *kp);void unregister_kprobes(struct kprobe **kps, int num);
#include
#include static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{}static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{}static struct kprobe kpb;
static int __init kprobe_init(void)
{kpb.pre_handler = handler_pre;kpb.post_handler = handler_post;kpb.symbol_name = kprobe_func;if (register_kprobe(&kpb)) {pr_alert("register_kprobe failed!\n“);return -EINVAL;}
}static void __exit kprobe_exit(void)
{unregister_kprobe(&kpb);pr_info("bye, unregistering kernel probe @ '%s'\n", kprobe_func);
}module_init(kprobe_init);
module_exit(kprobe_exit);
使用kprobe跟踪do_sys_openat2,并输出打开的文件名参数
内核中do_sys_openat2的原型
static long do_sys_openat2(int dfd, const char __user *filename,struct open_how *how)
{}
kprobe.c源码
#include
#include
#include
#include
#include
#include
#include
#include
#include #include
#include MODULE_AUTHOR("wy");
MODULE_LICENSE("Dual MIT/GPL");
MODULE_VERSION("0.1");static spinlock_t lock;
static struct kprobe kpb;
static u64 tm_start, tm_end;
static char *fname;//接收脚本传递的参数 kprobe_func
#define MAX_FUNCNAME_LEN 64
static char kprobe_func[MAX_FUNCNAME_LEN];
module_param_string(kprobe_func, kprobe_func, sizeof(kprobe_func), 0);
MODULE_PARM_DESC(kprobe_func, "function name to attach a kprobe to");#define pr_fmt(fmt) "%s:%s(): " fmt, KBUILD_MODNAME, __func__//显示时间差
#define SHOW_DELTA(later, earlier) do { \if (time_after((unsigned long)later, (unsigned long)earlier)) { \s64 delta_ns = ktime_to_ns(ktime_sub(later, earlier)); \pr_info("delta: %lld ns", delta_ns); \if (delta_ns/1000 >= 1) \pr_info(" %lld us", delta_ns/1000); \if (delta_ns/1000000 >= 1) \pr_info(" %lld ms", delta_ns/1000000); \} else \pr_warn("SHOW_DELTA(): *invalid* earlier > later?\n"); \
} while (0)//探测前的执行
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{char *param_fname_reg;param_fname_reg = (char __user *)regs->si;
#if 1//拷贝数据if (!strncpy_from_user(fname, param_fname_reg, PATH_MAX + 1))
#else/* 使用 copy_from_user() 会产生调度 导致CPU挂掉*/if (!copy_from_user(fname, (const char __user *)regs->si,strnlen_user((const char __user *)regs->si, PATH_MAX + 1)))
#endifreturn -EFAULT;pr_info("FILE being opened: reg:0x%px fname:%s\n",(void *)param_fname_reg, fname);spin_lock(&lock);tm_start = ktime_get_real_ns();spin_unlock(&lock);return 0;
}//探测之后的输出
static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{spin_lock(&lock);tm_end = ktime_get_real_ns();//计算时间SHOW_DELTA(tm_end, tm_start);spin_unlock(&lock);
}static int __init kprobe_init(void)
{if (kprobe_func[0] == '\0') {pr_warn("expect a valid kprobe_func= module parameter");return -EINVAL;}//申请内存空间,用来存储do_sys_openat2参数名fname = kzalloc(PATH_MAX + 1, GFP_ATOMIC);if (unlikely(!fname))return -ENOMEM;kpb.pre_handler = handler_pre;kpb.post_handler = handler_post;kpb.symbol_name = kprobe_func;//注册if (register_kprobe(&kpb)) {pr_alert("kernel fun register_kprobe failed!\n", kprobe_func);return -EINVAL;}pr_info("registering kernel probe @ '%s'\n", kprobe_func);spin_lock_init(&lock);return 0; /* success */
}static void __exit kprobe_exit(void)
{kfree(fname);unregister_kprobe(&kpb);pr_info("bye, unregistering kernel probe @ '%s'\n", kprobe_func);
}module_init(kprobe_init);
module_exit(kprobe_exit);
Makefile
FNAME_C := kprobeKDIR ?= /lib/modules/$(shell uname -r)/buildCC := $(CROSS_COMPILE)gcc
PWD := $(shell pwd)
obj-m += ${FNAME_C}.oall:make -C $(KDIR) M=$(PWD) modules
install:makesudo make -C $(KDIR) M=$(PWD) modules_install
clean:make -C $(KDIR) M=$(PWD) clean
测试程序打开/home/kprobe.c 测试在kprobe探测能否探测到
#include
#include void main()
{FILE *fp = NULL;while(1){fp = fopen("/home/kprobe.c","r+");if(fp == NULL)return;fclose(fp);usleep(10000);}
}
gcc main.c 生成a.out
运行用户程序
a.out
运行内核程序
insmod ./kprobe.ko kprobe_func=do_sys_openat2
运行输出截取,运行时很多的干扰可以使用 journalctl
#journalctl -k >log.txt#cat log.txt | grep "/home/kprobe.c"
将内核输出都写到log.txt中(dmesg只能写部分),使用grep检索所需要查询的信息。
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x00007ffd9a320820 fname:/sys/module/3_kprobe/uevent
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 37 ns
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x00007ffd9a320890 fname:/run/udev/data/+module:3_kprobe
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 19 ns
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_pre(): FILE being opened: reg:0x000055cd74eb8007 fname:/home/kprobe.c
10月 24 21:24:58 wy-virtual-machine kernel: 3_kprobe:handler_post(): delta: 40 ns
在输出信息中可以看到函数的地址、打开的文件名、函数执行的时间40ns等信息
参考
Kernel Probes (Kprobes) — The Linux Kernel documentation
下一篇:手撕七大排序 (三)