我们从kprobe到perf到bpf都提到了调试,这里eBPF是必不可少的一个内容,本文以一个hello world程序为例,以kprobe和kretprobe中观测的do_sys_openat2为例,实践一个最简单的eBPF程序
我们知道eBPF是由BPF演进而来,随着内核可观测的逐渐发展,eBPF逐渐火热,已经成为当前最热门的内核技术之一了,我们调试内核的研发,或多或少都有了解eBPF程序。eBPF的主要框图如下:
关于eBPF的核心原理介绍,用不着我解释,如果想要了解的,可以查看文档如下:
https://ebpf.io/what-is-ebpf/
为了编写eBPF,我们需要两个步骤
为eBPF编写c程序来映射一个event 使用python打印这个event的内容 关于bpf map,可以参考
https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md
我以https://github.com/iovisor/bcc/blob/master/examples/tracing/hello_perf_output.py
为例,使用了BPF_PERF_OUTPUT定义
为了能够获取pid,time,进程名等信息,这里需要使用bpf的接口,如下参考
https://docs.ebpf.io/linux/helper-function/
故实现代码如下:
#include <uapi/linux/openat2.h> #include <linux/sched.h> struct data_t { u32 pid; u64 ts; char comm[TASK_COMM_LEN]; }; BPF_PERF_OUTPUT(events); int kylin(struct pt_regs *ctx) { struct data_t data = { }; data.pid = bpf_get_current_pid_tgid(); data.ts = bpf_ktime_get_ns(); bpf_get_current_comm(&data.comm, sizeof(data.comm)); events.perf_submit(ctx, &data, sizeof(data)); return 0; }
这里构造了结构体data_t,其内容通过bpf的api填充,并将event映射出去。
c写好了之后,需要编写python,其目的是获取event的结构,然后打印出来,如下:
#!/usr/bin/env python3 from bcc import BPF from bcc.utils import printb b = BPF(src_file="kylin.c") b.attach_kprobe(event="do_sys_openat2", fn_name="kylin") print("%-18s %-16s %-6s" % ("TIME(s)", "COMM", "PID")) start = 0 def print_event(cpu, data, size): global start event = b["events"].event(data) if start == 0: start = event.ts time_s = (float(event.ts - start)) / 1000000000 printb(b"%-18.9f %-16s %-6d" % (time_s, event.comm, event.pid)) b["events"].open_perf_buffer(print_event) while 1: try: b.perf_buffer_poll() except KeyboardInterrupt: exit()
值得注意的是,我这里借助的还是kprobe,因为kprobe和kretprobe我也是观测的"do_sys_openat2",代码借鉴examples/tracing/hello_perf_output.py
至此,代码编写完成,我们直接试验
# python3 kylin.py TIME(s) COMM PID 0.000000000 systemd-journal 313 0.000131833 systemd-journal 313 0.000204458 systemd-journal 313
可以发现,我们正确的抓到了time,comm和pid
# pidof systemd-journald 313
根据上面的例子,我们可以初步的了解了eBPF的程序如何编写。