gobpf 使用示例:开发环境及 Hello World

前言

gobpf 使用示例系列记录使用 gobpf 编写 eBPF 程序的一些例子。

搭建开发环境

所有示例程序都基于 Ubuntu 20.04 和 Go 1.6 进行编写,同时所有示例使用的 github.com/iovisor/gobpf 版本为 v0.1.1

  • 可以通过各种方式安装 Ubuntu 20.04
  • 同样可以通过各种方式安装 Go 1.6

安装完 Ubuntu 和 Go 后,还需要安装编译 eBPF 程序所需的编译工具和内核源码:

$ sudo apt update
$ sudo apt install build-essential git make libelf-dev libelf1 \
clang llvm strace tar make bpfcc-tools linux-headers-$(uname -r) gcc-multilib

$ cd /tmp/ && \
    git clone --depth 1 git://kernel.ubuntu.com/ubuntu/ubuntu-focal.git && \
    sudo mv ubuntu-focal  /kernel-src && \
    cd /kernel-src/tools/lib/bpf && \
    sudo make && sudo make install prefix=/usr/local && \
    sudo mv /usr/local/lib64/libbpf.* /lib/x86_64-linux-gnu/

第一个 eBPF 程序

第一个 eBPF 程序将 trace 所有的 open 系统调用,显示 open 系统调用调用时的文件路径参数

eBPF C 代码如下(hello.c):

#include <linux/bpf.h>
#include <linux/ptrace.h>
#include <linux/version.h>
#include <bpf_helpers.h>

SEC("kprobe/do_sys_open")
int kprobe__do_sys_open(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;
}

char _license[] SEC("license") = "GPL";

通过下面的方法编译出最终的 hello.o 文件:

$ clang -O2 -emit-llvm -I/kernel-src/tools/testing/selftests/bpf -c hello.c -o hello.ll
hello.c:11:48: warning: incompatible integer to pointer conversion passing 'unsigned long' to parameter of type 'const void *' [-Wint-conversion]
                bpf_probe_read(file_name, sizeof(file_name), PT_REGS_PARM2(ctx));
                                                             ^~~~~~~~~~~~~~~~~~
/kernel-src/tools/testing/selftests/bpf/bpf_helpers.h:398:26: note: expanded from macro 'PT_REGS_PARM2'
#define PT_REGS_PARM2(x) ((x)->rsi)
                         ^~~~~~~~~~
1 warning generated.
$ llc -march=bpf -filetype=obj -o hello.o hello.ll

$ ls hello.o
hello.o

然后在 Go 中使用 gobpf 加载的方法如下(hello.go):

package main

import (
    "fmt"
    "time"
    "github.com/iovisor/gobpf/elf"
)

func main() {
    mod := elf.NewModule("hello.o")

    err := mod.Load(nil)
    if err != nil {
            panic(err)
    }

    err = mod.EnableKprobes(0)
    if err != nil {
            panic(err)
    }

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

编译运行:

$ go mod init
$ go get github.com/iovisor/gobpf
$ go build hello.go
$ sudo ./hello
Waiting...

新开一个 shell 窗口,然后在窗口内执行下面的命令可以看到被 trace 的 open 系统调用:

$ sudo cat  /sys/kernel/debug/tracing/trace_pipe
...
systemd-journal-364     [000] .... 16819.802559: 0: open file /proc/492/attr/current
systemd-journal-364     [000] .... 16819.802573: 0: open file /proc/492/sessionid
systemd-journal-364     [000] .... 16819.802583: 0: open file /proc/492/loginuid
systemd-journal-364     [000] .... 16819.802594: 0: open file /proc/492/cgroup
...

备注:

多次运行 hello 程序会出现如下错误:

$ sudo ./hello
panic: cannot write "p:pdo_sys_open do_sys_open\n" to kprobe_events: write /sys/kernel/debug/tracing/kprobe_events: file exists

可以通过下面的方法清理上次运行的遗留信息:

$ echo "" |sudo tee /sys/kernel/debug/tracing/kprobe_events

然后再运行 sudo ./hello 就不会有问题了。

P.S. 本文的所有代码在 Github 上都有一份完整版:https://github.com/mozillazg/gobpf-examples/tree/master/1-helloword


Comments