【踩坑】使用libbpfgo构建你的第一个eBPF项目

文章目录


前言

本文参考:How to Build eBPF Programs with libbpfgo

但实际运行时出现了许多情况,因此记录分享。

写在最前:使用libbpf请将系统更新到最新版本,本文环境为Ubuntu21.04。
Ubuntu最新版本下载


一、为什么使用libbpf?

libbpf是可以在用户空间和 bpf 程序中导入的库。它为开发人员提供了一个用于加载 bpf 程序并与之交互的 API。

在使用libbpf前,先使用bcc对eBPF相关知识进行学习运行,学习曲线将更平滑。相对于bcc,libbpf与BPF CO-RE的实际编译部署的难度增大了。

然而性能优化大师 Brendan Gregg 在用 libbpf + BPF CO-RE 转换一个 BCC 工具后给出了性能对比数据:

As my colleague Jason pointed out, the memory footprint of opensnoopas CO-RE is much lower than opensnoop.py. 9 Mbytes for CO-RE vs 80 Mbytes for Python.

我们可以看到在运行时相比 BCC 版本,libbpf + BPF CO-RE 版本节约了近 9 倍的内存开销,这对于物理内存资源已经紧张的服务器来说会更友好。

二、环境搭建

大概是最困难的部分,clang相关的环境需要你的Linux系统升级至较新的版本,而go相关的环境缘于一些原因较难安装上。

1.Libbpf相关环境搭建

实际上要装很多的环境,参考了网上的文章。

sudo apt install build-essential git make libelf-dev strace tar bpfcc-tools libbpf-dev linux-headers-$(uname -r) gcc-

安装clang和llvm,当前最新版本为12。

sudo apt install clang llvm

如果存在因为某些版本问题无法安装的软件时,建议:

  1. 升级到最新版系统
  2. 使用sudo aptitude install代替sudo apt install

安装完成后,于命令行输入

clang -v

见到类似下图的输出则安装成功:
【踩坑】使用libbpfgo构建你的第一个eBPF项目

2.GO环境搭建

sudo apt-get golang

由于未来将使用go get相关命令来获取包,此处建议先换源,否则会出现connection refused的情况。

使用GOPROXY,在命令行输入:

export GOPROXY=https://goproxy.io
export GO111MODULE=on

三、使用libbpfgo编译运行eBPF程序

构建基于libbpf的BPF应用需要使用BPF CO-RE包含的几个步骤:

  • 生成带所有内核类型的头文件vmlinux.h;
  • 使用Clang(版本10或更新版本)将BPF程序的源代码编译为.o对象文件;
  • 使用 libbpfgo 将其编译为二进制文件,加载到内核中并监听输出。

步骤1:生成头文件

首先新建一个目录作为本次eBPF程序的目录,并将目录权限设置为可以新建或删除文件。

sudo mkdir libbpf-hello
sudo chmod 777 libbpf-hello

进入该目录下,生成带所有内核类型的头文件vmlinux.h

cd libbpfgo-hello/
sudo bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

看到目录下出现了vmlinux.h文件且里面有内容则成功。

步骤2:Clang将BPF程序的源代码编译为.o对象文件

首先,创建文件simple.h,内容如下:

typedef struct process_info {
    int pid;
    char comm[100];
} proc_info;

simple.h 包含我们想要包含在 bpf 和用户空间代码中的结构定义。

然后,创建文件simple.bpf.c,这是本次BPF程序原代码。

#include "vmlinux.h"
#include <bpf/bpf_helpers.h> 
#include "simple.h"

struct {
    __uint(type, BPF_MAP_TYPE_RINGBUF);
    __uint(max_entries, 1 << 24);
} events SEC(".maps");

long ringbuffer_flags = 0;

SEC("kprobe/sys_execve")
int kprobe__sys_execve(struct pt_regs *ctx)
{
    __u64 id = bpf_get_current_pid_tgid();
    __u32 tgid = id >> 32;
    proc_info *process;

    // Reserve space on the ringbuffer for the sample
    process = bpf_ringbuf_reserve(&events, sizeof(proc_info), ringbuffer_flags);
    if (!process) {
        return 0;
    }
    process->pid = tgid;
    bpf_get_current_comm(&process->comm, 100);
    bpf_ringbuf_submit(process, ringbuffer_flags);
    return 0;
}

最后,使用Clang进行编译。

clang -g -O2 -c -target bpf -o simple.bpf.o simple.bpf.c

成功编译后,目录结构应该如下:
【踩坑】使用libbpfgo构建你的第一个eBPF项目

步骤3:使用GO编译为二进制文件并运行

(注:本阶段的一些命令需要不使用sudo才能运行,否则连不上go的网络,我也不知道为什么…)

创建文件main.go,内容如下:

package main

import "C"

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"os"

	bpf "github.com/aquasecurity/tracee/libbpfgo"
)

func main() {

	bpfModule, err := bpf.NewModuleFromFile("simple.bpf.o")
	if err != nil {
		os.Exit(-1)
	}
	defer bpfModule.Close()

	bpfModule.BPFLoadObject()
	prog, err := bpfModule.GetProgram("kprobe__sys_execve")
	if err != nil {
		os.Exit(-1)
	}

	_, err = prog.AttachKprobe("__x64_sys_execve")
	if err != nil {
		os.Exit(-1)
	}

	eventsChannel := make(chan []byte)
	rb, err := bpfModule.InitRingBuf("events", eventsChannel)
	if err != nil {
		os.Exit(-1)
	}

	rb.Start()

	for {
		event := <-eventsChannel
		pid := int(binary.LittleEndian.Uint32(event[0:4])) // Treat first 4 bytes as LittleEndian Uint32
		comm := string(bytes.TrimRight(event[4:], "\x00")) // Remove excess 0's from comm, treat as string
		fmt.Printf("%d %v\n", pid, comm)
	}

	rb.Stop()
	rb.Close()
}

利用go的module工具,在目录下输入:

go mod init libbpfgo-hello

但是此时生成的go.mod是空的,为了加载所需module,需要输入:

go mod tidy

如果显示connection refused的话,只能使用*手段了,如:

proxychains go mod tidy

加载成功后,目录下将会多出go.mod与go.sum两个文件。并会有如下显示:

【踩坑】使用libbpfgo构建你的第一个eBPF项目
之后,使用go进行编译生成二进制文件。在命令行输入:

CC=gcc CGO_CFLAGS="-I /usr/include/bpf" CGO_LDFLAGS="/usr/lib/x86_64-linux-gnu/libbpf.a" go build -o libbpfgo-prog

注:文件libbpf.a的地址可能不一样,建议使用find命令找一下本机的libbpf.a文件。

编译完成后得到二进制文件 libbpfgo-prog ,文件目录结构如下:
【踩坑】使用libbpfgo构建你的第一个eBPF项目
于命令行输入以下命令运行二进制文件

sudo ./libbpfgo-prog

随着系统运行,能够以下列格式捕捉到系统的exec信息
【踩坑】使用libbpfgo构建你的第一个eBPF项目
至此,你的第一个通过libbpfgo构建的eBPF项目成功运行!


后记

关于本文代码中的详细解释,可见How to Build eBPF Programs with libbpfgo

上一篇:一文读懂eBPF/XDP


下一篇:Table样式