libbpfgo example: Write an eBPF program that uses perfbuf map

Preface

This article briefly documents how to write an ebpf program that uses a perfbuf map to transfer data, and how to use libbpfgo to process data stored in the perfbuf map.

ebpf codes

The way perfbuf map is used in ebpf c code is in two main steps:

  1. Define a BPF_MAP_TYPE_PERF_EVENT_ARRAY map
  2. write data via the bpf_perf_event_output function

The sample code snippet is as follows:

/* BPF perfbuf map */
struct {
        __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
        __uint(key_size, sizeof(u32));
        __uint(value_size, sizeof(u32));
} events SEC(".maps");

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

                e.pid = bpf_get_current_pid_tgid() >> 32;

    // write data
                bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &e, sizeof(e));

                return 0;
}

golang codes

There are three main steps to read the data in this map using libbpfgo:

  1. Call the InitPerfBuf method to initialize a perfbuf map data receiving instance.
  2. Start the instance with Start()
  3. Receive and decode data

The sample code snippet is as follows:

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

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

for {
        select {
        case e := <-eventsChannel:
                // decode data: u32 pid
                pid := binary.LittleEndian.Uint32(e[0:4])
                log.Printf("pid %d", pid)
        case e := <-lostChannel:
                log.Printf("lost %d events", e)
        }
}

You can check out full codes on Github: https://github.com/mozillazg/hello-libbpfgo/tree/master/02-perf-buf


Comments