libbpfgo example: setting up the development environment and writing the first eBPF program

Preface

This article documents setting up the libbpfgo development environment and writing a simple eBPF sample application using libbpfgo.

Setting up a development environment

For simplicity, I'm using vagrant to build the VM development environment:

  1. install vagrant-env plugin:
$ vagrant plugin install vagrant-env
  1. clone sample codes:
$ mkdir -p $GOPATH/src/github.com/mozillazg && \
  cd $GOPATH/src/github.com/mozillazg && \
  git clone https://github.com/mozillazg/hello-libbpfgo.git && \
  cd hello-libbpfgo && \
  git submodule update --init --recursive
  1. setting up .env file:
$ cp .env.example .env
  1. start the VM:
$ vagrant up
  1. ssh into the VM:
$ vagrant ssh

Write sample program

Here is a brief introduction to the sample program using the sample program in the 01-hello-world directory of hello-libbpfgo as an example.

main.ebpf.c:

#include "vmlinux.h"

#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

SEC("kprobe/do_sys_openat2")
int kprobe__do_sys_openat2(struct pt_regs *ctx)
{
                char file_name[256];
                bpf_probe_read(file_name, sizeof(file_name), PT_REGS_PARM2(ctx));

                char fmt[] = "open file %s\n";
                bpf_trace_printk(fmt, sizeof(fmt), &file_name);

                return 0;
}

main.ebpf.c is a simple program that hooks do_sys_openat2, a kernel function call. The filename information in the function call parameters is recorded.

The main point is to see how to call this ebpf program in a go program using libbpfgo.

main.go:

package main

import (
        "fmt"
        "time"

        bpf "github.com/aquasecurity/libbpfgo"
)

func main() {
        bpfModule, err := bpf.NewModuleFromFile("main.bpf.o")
        if err != nil {
                panic(err)
        }
        defer bpfModule.Close()
        if err := bpfModule.BPFLoadObject(); err != nil {
                panic(err)
        }
        prog, err := bpfModule.GetProgram("kprobe__do_sys_openat2")
        if err != nil {
                panic(err)
        }
        if _, err := prog.AttachKprobe("do_sys_openat2"); err != nil {
                panic(err)
        }

        for {
                fmt.Println("Waiting...")
                time.Sleep(10 * time.Second)
        }
}

As you can see from the go program above, there are four main steps in calling the ebpf program using libbpfgo:

  1. Read the compiled .o file with the bpf.NewModuleFromFile method.
  2. Use bpfModule.BPFLoadObject() to load the object information from the read .o file.
  3. Use bpfModule.GetProgram to get the corresponding hook function in the ebpf program.
  4. If the function is a kprobe hook, then the prog.AttachKprobe method is called to attach it to the corresponding kernel function hook.

Let's compile the program and then looks at the corresponding results:

$ vagrant ssh
$ cd $GOPATH/src/github.com/mozillazg/hello-libbpfgo/01-hello-world
$ make
$ sudo ./main

Open another terminal to see output:

$ vagrant ssh
$ sudo cat /sys/kernel/debug/tracing/trace_pipe

runc:[2:INIT]-100616  [000] d...  5527.233315: bpf_trace_printk: open file /proc/self/fd

runc:[2:INIT]-100616  [000] d...  5527.233641: bpf_trace_printk: open file /proc/self/status

runc:[2:INIT]-100616  [000] d...  5527.233802: bpf_trace_printk: open file /etc/passwd

runc:[2:INIT]-100616  [000] d...  5527.233829: bpf_trace_printk: open file /etc/group

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


Comments