5

ebpf/libbpf 程序使用 tracepoint 的常见问题

 1 year ago
source link: https://mozillazg.com/2022/05/ebpf-libbpf-tracepoint-common-questions.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

记录一些编写 ebpf/libbpf 程序时涉及到的 tracepoint 相关的常见问题。

tracepoint 可以监控哪些事件

可以通过查看 /sys/kernel/debug/tracing/available_events 文件的内容找到 tracepoint 可监控的事件。 文件中每行内容的格式是:

<category>:<name>
syscalls:sys_enter_execve

SEC 内容的格式

tracepoint 事件对应的 SEC 格式为:

SEC("tracepoint/<category>/<name>")

// 比如:
// SEC("tracepoint/syscalls/sys_enter_openat")
SEC("tp/<category>/<name>")

// 比如:
// SEC("tp/syscalls/sys_enter_openat")

SEC("tp/xx/yy") 跟 SEC("tracepoint/xx/yy") 其实是等效的,看个人喜好随便用哪种都行。

确定 tracepoint 事件处理函数的参数类型,获取对应的内核调用参数

假设,我们想监控 chmod 这个命令涉及的 fchmodat 系统调用的 tracepoint 事件, 那么,如何确定ebpf 中事件处理函数的参数类型,以及如何获取到对应的 fchmodat 这个系统调用涉及的参数的内容, 比如拿到操作文件名称以及操作的权限 mode 的值。

第一步,先确定 chmod 所使用的系统调用,这个比较简单,有很多种方法可以做到,比如通过 strace 命令:

$ strace chmod 600 a.txt
...
fchmodat(AT_FDCWD, "a.txt", 0600)       = 0
...

第二步,找到针对这个系统调用可以使用的 tracepoint 事件:

$ sudo cat /sys/kernel/debug/tracing/available_events |grep fchmodat
syscalls:sys_exit_fchmodat
syscalls:sys_enter_fchmodat

可以看到,有 sys_enter_fchmodat 和 sys_exit_fchmodat 这两个事件。这里选择 sys_enter_fchmodat 这个事件进行后续的说明。

第三步,确定函数的参数类型。这个需要到 vmlinux.h 文件中进行查找, 一般 sys_enter_xx 对应 trace_event_raw_sys_enter , sys_exit_xx 对应 trace_event_raw_sys_exit , 其他的一般对应 trace_event_raw_<name> ,如果没找到的话,可以参考 trace_event_raw_sys_enter 的例子找它相近的 struct。 对于 sys_enter_fchmodat ,我们使用 trace_event_raw_sys_enter 这个 struct:

struct trace_event_raw_sys_enter {
    struct trace_entry ent;
    long int id;
    long unsigned int args[6];
    char __data[0];
};

其中 args 中就存储了事件相关的我们可以获取的信息,至于里面包含了哪些信息就是第三步需要确定的信息。

第三步,确定事件本身可以获取到哪些信息,虽然我们指定 fchmodat 系统调用需要提供文件名称和 mode 信息, 但是,我们不确定是否可以在 ebpf 程序中获取到这些信息。可以通过查看 /sys/kernel/debug/tracing/events/<category>/<name>/format 文件获取到我们可以获取哪些信息。 比如 sys_enter_fchmodat 这个事件的 /sys/kernel/debug/tracing/events/syscalls/sys_enter_fchmodat/format 的内容如下:

$ sudo cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_fchmodat/format
name: sys_enter_fchmodat
ID: 647
format:
        field:unsigned short common_type;       offset:0;       size:2; signed:0;
        field:unsigned char common_flags;       offset:2;       size:1; signed:0;
        field:unsigned char common_preempt_count;       offset:3;       size:1; signed:0;
        field:int common_pid;   offset:4;       size:4; signed:1;

        field:int __syscall_nr; offset:8;       size:4; signed:1;
        field:int dfd;  offset:16;      size:8; signed:0;
        field:const char * filename;    offset:24;      size:8; signed:0;
        field:umode_t mode;     offset:32;      size:8; signed:0;

print fmt: "dfd: 0x%08lx, filename: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->mode))

__syscall_nr 之后的字段都是我们可以在 ebpf 程序中获取的信息。 从上面可以看到,我们可以获取 sys_enter_fchmodat 事件的 dfd 、 filename 以及 mode 信息, 这里就包含了前面所说的文件名称以及权限 mode 信息。 通过 trace_event_raw_sys_enter 的 args 数组获取,通过 args[0] 获取 dfd , args[1] 获取 filename 以此类推。

信息都确定好了,就可以写程序了。比如上面 sys_enter_fchmodat 事件的示例 ebpf 程序如下:

SEC("tracepoint/syscalls/sys_enter_fchmodat")
int tracepoint__syscalls__sys_enter_fchmodat(struct trace_event_raw_sys_enter *ctx)
{
        struct event_t *event;
        event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
        if (!event) {
            return 0;
        }


        char *filename_ptr = (char *) BPF_CORE_READ(ctx, args[1]);
        bpf_core_read_user_str(&event->filename, sizeof(event->filename), filename_ptr);
        event->mode = BPF_CORE_READ(ctx, args[2]);

        bpf_ringbuf_submit(event, 0);
        return 0;
}

完成的示例程序详见:https://github.com/mozillazg/hello-libbpfgo/tree/master/07-tracepoint-args


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK