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

前言

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

btf raw tracepoint 跟常规 raw tracepoint 的区别

所谓的 btf raw tracepoint 指的是 BTF-powered raw tracepoint (tp_btf) 或者说是 BTF-enabled raw tracepoint

btf raw tracepoint 跟常规 raw tracepoint 有一个 最主要的区别 是: btf 版本可以直接在 ebpf 程序中访问内核内存, 不需要像常规 raw tracepoint 一样需要借助类似 bpf_core_readbpf_probe_read_kernel 这样 的辅助函数才能访问内核内存:

struct task_struct *task = (struct task_struct *) bpf_get_current_task();
u32 ppid = BPF_CORE_READ(task, real_parent, tgid);

// btf enabled
struct task_struct *task = (struct task_struct *) bpf_get_current_task_btf();
u32 ppid = task->real_parent->tgid;

btf raw tracepoint 可以监控哪些事件

btf raw tracepoint 跟 raw tracepoint 所能监控的事件是一样的,这里不再赘述。

SEC 内容的格式

btf raw tracepoint 事件对应的 SEC 格式为:

SEC("tp_btf/<name>")

// 比如:
// SEC("tp_btf/sched_switch")
// SEC("tp_btf/sys_enter")
// SEC("tp_btf/sys_exit")

<name> 的值跟 raw tracepoint SEC 中使用的 <name> 是一样的。

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

所有事件都是在 vmlinux.h 中存在一个名为 btf_trace_<name> 的定义。

比如 sys_enter 这个事件对应的定义如下:

typedef void (*btf_trace_sys_enter)(void *, struct pt_regs *, long int);

对应的 ebpf 函数可以定义成下面这样:

SEC("tp_btf/sys_enter")
int btf_raw_tracepoint__sys_enter(u64 *ctx)
{
  // ...
}

其中 ctx[0] 对应上面 btf_trace_sys_entervoid * 后面的第一个参数 struct pt_regs *, ctx[1] 是第二个参数 long int 。这两个参数的含义跟前面 raw tracepoint 中所说的 TP_PROTO(struct pt_regs *regs, long id) 中的含义是一样的。

对应的,使用 btf raw tracepoint 获取 fchmodat 系统调用事件的示例程序如下:

SEC("tp_btf/sys_enter")
int btf_raw_tracepoint__sys_enter(u64 *ctx)
{
    long int syscall_id = (long int)ctx[1];
    if(syscall_id != 268)    // fchmodat
        return 0;

    struct pt_regs *regs = (struct pt_regs *)ctx[0];
    // 后面的逻辑跟前面 raw tracepoint 示例程序中是一样的
    // ...
}

BTW, 在 btf raw tracepoint 程序中可以通过 bpf_get_current_task_btf() 获取 btf 版本的 task 信息。

完整的示例程序如下:


Comments

comments powered by Disqus