libbpfgo 使用示例:编写使用 ringbuf map 的 ebpf 程序

前言

本文简单记录一下如何编写一个使用 ringbuf map 传递数据的 ebpf 程序, 以及如何使用 libbpfgo 处理 ringbuf map 中存储的数据。

ebpf 代码

ebpf c 代码中使用 ringbuf map 的方法主要分三步:

  1. 定义一个 BPF_MAP_TYPE_RINGBUF 类型的 map
  2. 写入数据前,先通过 bpf_ringbuf_reserve 申请内存空间,申请成功后才能真正写入数据,缺少这一步的话,程序执行时会提示 libbpf: load bpf program failed: Invalid argument 错误
  3. 通过 bpf_ringbuf_submit 函数写入数据

示例程序代码片段如下:

/* 定义 map */
    /* BPF ringbuf map */
    struct {
            __uint(type, BPF_MAP_TYPE_RINGBUF);
            __uint(max_entries, 256 * 1024 /* 256 KB */);
    } events SEC(".maps");

    SEC("kprobe/do_sys_openat2")
    int kprobe__do_sys_openat2(struct pt_regs *ctx)
    {
                    struct event *e;

                    // 必需步骤
                    e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
                    if (!e) {
                            return 0;
                    }

                    e->pid = bpf_get_current_pid_tgid() >> 32;

                    // 写入数据
                    bpf_ringbuf_submit(e, 0);

                    return 0;
    }

golang 代码

使用 libbpfgo 读取这个 map 里的数据的方法主要有三步:

  1. 调用 InitRingBuf 方法初始化一个 ringbuf map 数据接收实例
  2. 通过 Start() 启动实例
  3. 接收并解码数据

相关代码片段示例如下:

eventsChannel := make(chan []byte)
pb, err := bpfModule.InitRingBuf("events", eventsChannel)
if err != nil {
        panic(err)
}

pb.Start()
defer func() {
        pb.Stop()
        pb.Close()
}()

for {
        select {
        case e := <-eventsChannel:
                // 解码收到的数据: u32 pid
                pid := binary.LittleEndian.Uint32(e[0:4])
                log.Printf("pid %d", pid)
        }
}

完整的代码详见:https://github.com/mozillazg/hello-libbpfgo/tree/master/03-ring-buf


Comments