libbpfgo 使用示例:搭建开发环境以及编写第一个 ebpf 程序

前言

本文记录搭建 libbpfgo 开发环境以及使用 libbpfgo 编写一个简单的 ebpf 示例程序。

搭建开发环境

为了简单起见,我这里使用 vagrant 搭建虚拟机开发环境:

  1. 安装 vagrant-env 插件:
$ vagrant plugin install vagrant-env
  1. clone 示例代码仓库:
$ 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. 配置 .env 文件(用于将本机的 GOPATH 挂载到虚拟机中):
# 修改 .env 文件,将 GOPATH 的值修改为本机对应的路径
$ cp .env.example .env
  1. 启动虚拟机:
$ vagrant up
  1. 等虚拟机就位后,进入虚拟机:
$ vagrant ssh

编写示例程序

这里使用 hello-libbpfgo 中 01-hello-world 目录下的示例程序为例简单介绍一下示例程序。

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 这个程序比较简单,就是hook do_sys_openat2 这个内核函数的调用, 将函数调用参数中的 filename 信息给记录下来。

主要看一下,怎么在 go 程序中使用 libbpfgo 调用这个 ebpf 程序:

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)
        }
}

从上面的 go 程序中可以看到,使用 libbpfgo 调用 ebpf 程序主要有四个步骤:

  1. 通过 bpf.NewModuleFromFile 方法读取编译好的 .o 文件
  2. 使用 bpfModule.BPFLoadObject() 加载读取的 .o 文件中对象信息
  3. 使用 bpfModule.GetProgram 获取 ebpf 程序中对应的 hook 函数
  4. 如果这个函数是个 kprobe hook 函数,那么就调用 prog.AttachKprobe 把它 attach 到对应的内核函数hook中。

下面编译程序然后看一下对应的效果:

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

另开一个终端,查看 ebpf 中打印的信息:

$ 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

更多信息,详见 https://github.com/mozillazg/hello-libbpfgo/tree/master/01-hello-world


Comments