内核追踪技术之kprobe

1、内核模块开发

在内核编程中,module_init(begin_func);是指定begin_func为该模块的入口,而相对应的module_exit(exit_func)指定了模块退出时的出口函数为exit_func,这两者皆为模块加载函数。

printk()是由内核定义的,把要打印的信息输入到系统日志中,可以用dmesg命令查看。

极简Makefile:

obj-m := hellops.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

2、通过内核编程自己写probe handler(uprobe为例)

可以参考内核代码kprobe_example.c。下面是个upbore的例子,需要自己给出监控函数的偏移量。

#define DEBUGGEE_FILE "/mnt/hgfs/tjs/tmp/kern_uprobe/hello"
#define DEBUGGEE_FILE_OFFSET (0x5b7)
static struct inode *debuggee_inode;

static int uprobe_sample_handler(struct uprobe_consumer *con,
                                 struct pt_regs *regs)
{
    printk("handler is executed, arg0: %s\\n",regs->di);

    return 0;
}

static int uprobe_sample_ret_handler(struct uprobe_consumer *con,
                                     unsigned long func,
                                     struct pt_regs *regs)
{
    printk("ret_handler is executed\\n");
    return 0;
}

static struct uprobe_consumer uc = {
        .handler = uprobe_sample_handler,
        .ret_handler = uprobe_sample_ret_handler
};

static int __init init_uprobe_sample(void)
{
    int ret;
    struct path path;

    ret = kern_path(DEBUGGEE_FILE, LOOKUP_FOLLOW, &path);
    if (ret) {
        return -1;
    }

    debuggee_inode = igrab(path.dentry->d_inode);
    path_put(&path);

    ret = uprobe_register(debuggee_inode,
                          DEBUGGEE_FILE_OFFSET, &uc);
    if (ret < 0) {
        return -1;
    }

    printk(KERN_INFO "insmod uprobe_sample\\n");
    return 0;
}

3、不编程的内核追踪技术(tracepoint)

参考/sys/kernel/debug/tracing目录下available_events内容,在对应的events/GRP/function/enable开启即可,输出内容参考同目录下format文件。

4、不编程的kprobe追踪技术(ftrace)

tracepoint是内核代码静态编写的,提供的是内核代码预期会暴露的东西,稳定性高。但是如果想跟踪任意内核函数,还是需要借助kprobe。前面介绍的内核模块编程实现跟踪kprobe,实现方式比较重,门槛高,对内核稳定性影响比较大。

借助ftrace引入的kprobe追踪技术,就比较方便灵活。

修改/sys/kernel/debug/tracing目录下kprobe_events,添加需要追踪的kprobe。比如官方文档给的例子:echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events。Linux内部应该是会跟踪debugfs文件系统,识别变化,触发create_trace_kprobe函数执行,按照特定的语法解析 kprobe_event 文件内容,创建一个 kprobe。

kprobe_event文件内容语法如下:

p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]-------------------设置一个probe探测点
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS]------------------------------设置一个return probe探测点
-:[GRP/]EVENT----------------------------------------------------------删除一个探测点

实际使用过程中,do_sys_open在Linux高内核版本已经不支持了。高内核版本可以参考这个,echo 'p vfs_open name=+0x38(+0x8($arg1)):string namep=+0(+0x28(+0x8($arg1))):string' >> kprobe_events。

修改kprobe_events后,注意开启对应kprobe的跟踪。

echo 1 > events/kprobes/p_vfs_open_0/enable
echo  1 >events/kprobes/enable
echo 1 > tracing_on

输出日志如下:

cat trace_pipe
			systemd-oomd-595     [000] ..... 115817.344240: p_vfs_open_0: (vfs_open+0x4/0x40) name="meminfo" namep="meminfo"
      bash-10029   [000] ..... 115817.566949: p_vfs_open_0: (vfs_open+0x4/0x40) name="enable" namep="enable"

6、其他kprobe跟踪技术

  1. perf probe

perf probe -x 增加uprobe跟踪, -a增加kprobe跟踪。 perf record -e 记录事件,perf script查看。

perf record开启监控后,即设置标志位(不是ftrace里常用的enable方式),uprobe event 的 probe handler 固定是 uprobe_dispatcher 函数,uprobe_dispatcher 函数会根据 uprobe event 的 flag 属性来判断往哪个 ring buffer 里写追踪数据,kprobe 也是同理,他会直接往 perf event 的 ringbuffer 中写数据。

2. ebpf

ebpf程序本身就是一个非内核模块的probe handler。参考内核代码bpf_load.c,也是通过sys_perf_event_open打开的。id需要从events/kprobes/EVENT/id中去取。

if (strncmp(event, "sys_", 4) == 0) {
			snprintf(buf, sizeof(buf),
				 "echo '%c:__x64_%s __x64_%s' >> /sys/kernel/debug/tracing/kprobe_events",
				 is_kprobe ? 'p' : 'r', event, event);
 id = atoi(buf);
	attr.config = id;

	efd = sys_perf_event_open(&attr, -1/*pid*/, 0/*cpu*/, -1/*group_fd*/, 0);
	if (efd < 0) {
		printf("event %d fd %d err %s\n", id, efd, strerror(errno));
		return -1;
	}

这样eBPF 内核态程序附加到 perf event。每个 perf event 的 kprobe probe handler 都是 kprobe_dispatch 函数,他会去 perf event 中获取注册在当前 perf event 的回调函数列表并依次执行,同时将指向 perf ringbuffer 的指针的传递给 eBPF 程序,eBPF 程序可以通过 libbpf 封装好的 PT_REGS_PARAMx 宏定义来获取缓冲区中的数据。

不论是 kprobes、tracepoint 类型的 eBPF 程序,都是复用 perf event 来实现 probe handler 注入,在某个内核版本,eBPF 的负责人 Alex 提出了一个新的方式 Raw Tracepoint,不需要依赖 perf event,eBPF 程序直接作为 probe handler 注册到 tracepoint 上。eBPF 程序接受到的数据是由 perf probe 传递过来的。tracepoint 关联的函数的参数会写到 perf ringbuffer 缓冲区,perf probe 会将指向缓冲区的指针传递给 eBPF 程序。

perf probe 传递了指向缓冲区的指针,eBPF 也无法直接使用指针访问内存上的数据,各个内核函数的参数不一样,在不知道数据的类型、长度,无法保证安全访问,所以需要借助 bpf 辅助函数读取数据。再说回 raw tracepoint 类型的 eBPF 程序,从使用上来说,它的函数参数结构体变成了 struct bpf_raw_tracepoint_args,不在需要我们定义 tracepoint 关联的结构体了。SEC 声明也改成 raw_traceoint,其他的在使用上和 tracepoint 类型的 eBPF 程序保持一致。

对于 tracepoint 类型 eBPF 程序,是 perf event 在 ringbuffer 中分配一块内存空间,然后内核会将函数的参数写到这个内存空间中,perf probe 再把这个内存空间的地址传递给 eBPF 程序,而原始访问则是,直接把函数参数全部转换为 u64 类型,得到一个数组,并把数组传递给 eBPF 程序。更短的调用链和跳过参数处理,相比于 tracepoint ,raw tracepoint 有更好的性能。

5、总结

内核模块,kprobetrace,perf probe,ebpf都是为kprobe内核追踪技术提供probe handler的方式。各项技术之间有相互使用,继承迭代的关系。

参考文档:

Linux内核编程入门 | Magic's Blog

万字长文解读Linux 内核追踪机制_语言 & 开发_InfoQ精选文章

Linux perf probe 的使用(三)_perf probe 使用-CSDN博客

深入ftrace kprobe原理解析_奇小葩的博客-CSDN博客

Kprobe-based Event Tracing — The Linux Kernel documentation

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值