- 前言
- 一、为什么使用libbpf?
- 二、环境搭建
- 1.Libbpf相关环境搭建
- 2.GO环境搭建
- 三、使用libbpfgo编译运行eBPF程序
- 步骤1:生成头文件
- 步骤2:Clang将BPF程序的源代码编译为.o对象文件
- 步骤3:使用GO编译为二进制文件并运行
- 后记
前言
本文参考: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) linux-tools-common gcc-
安装clang和llvm,当前最新版本为12。
sudo apt install clang llvm
如果存在因为某些版本问题无法安装的软件时,建议:
- 升级到最新版系统
- 使用sudo aptitude install代替sudo apt install
安装完成后,于命令行输入
clang -v
见到类似下图的输出则安装成功:
sudo apt-get install 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 将其编译为二进制文件,加载到内核中并监听输出。
首先新建一个目录作为本次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#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
成功编译后,目录结构应该如下:
(注:本阶段的一些命令需要不使用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 %vn", 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两个文件。并会有如下显示:
之后,使用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 ,文件目录结构如下:
于命令行输入以下命令运行二进制文件
sudo ./libbpfgo-prog
随着系统运行,能够以下列格式捕捉到系统的exec信息
至此,你的第一个通过libbpfgo构建的eBPF项目成功运行!
后记
关于本文代码中的详细解释,可见How to Build eBPF Programs with libbpfgo。



