说明
前面 ebpf-example 都是通过llvm
编译ebpf
的内核程序得到BPF格式.o
后缀的文件,通过go:embed
的方式将.o
文件嵌入到go
程序中,然后通过ebpfmanager
加载和运行ebpf
程序。
最近在学习 vArmor-ebpf,发现采用的是bpf2go
的方式编译eBPF
程序得到golang
程序。用户态通过这些Golang
程序就可以与eBPF
程序交互。
为了以后再遇到通过bpf2go
方式编译eBPF
程序,于是就学习了相关的概念。
基本概念
在了解bpf2go
之前,首先还是看看llvm
编译ebpf
的内核程序得到BPF格式.o
后缀的文件,这种方式如何加载和运行ebpf
程序。还是以 helloworld 为例。
1 | //go:embed ebpf/bin/probe.o |
通过go:embed ebpf/bin/probe.o
的方式,得到[]byte
类型的_bytecode
变量,然后通过m.Init(bytes.NewReader(_bytecode))
的方式加载和运行ebpf
程序。
而bpf2go
其实就是将这一部分操作封装起来,让开发人员不需要关心这些细节,只需要关心ebpf
程序的开发即可。整个bpf2go
的逻辑如下所示:
bpf2go
工具的主要作用是将位于C源文件中的eBPF程序转换为Go语言代码,并生成相应的Go包。这样,开发人员可以在Go项目中使用这些生成的代码来与eBPF程序进行交互和操作。如上图所示bpf_bpfel.o
和bpf_bpfel.go
文件就是由bpf2go
工具生成。
上面的例子中bpf_bpfel.o
和bpf_bpfel.go
表示小端。bpf字节码文件bpf_bpfeb.o(大端)和bpf_bpfel.o(小端),然后bpf2go会基于ebpf字节码文件生成bpf_bpfeb.go或bpf_bpfel.go。生成大端或者是小端,是通过bpf2go
程序运行时指定,后面的示例代码会说明如何使用bpf2go
工具。
bpf_bpfel.go
bpf_bpfel.o
是由eBPF
程序编译得到的,和使用LLVM编译得到的程序基本上没有差别,这个文件也没有什么特殊的地方,就不作介绍,主要是看看bpf_bpfel.go
内容。
读取bpf_bpfel.o
文件
1 | // loadBpf returns the embedded CollectionSpec for bpf. |
在bpf_bpfel.go
文件中,也是通过go:embed bpf_bpfel.o
,将bpf_bpfel.o
转换成为[]byte
数据,之后在loadBpf()
函数中,通过bytes.NewReader(_BpfBytes)
加载程序,通过这种方式加载和解析eBPF
程序。
加载bpf_bpfel.o
文件1
2
3
4
5
6
7
8func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
spec, err := loadBpf()
if err != nil {
return err
}
return spec.LoadAndAssign(obj, opts)
}
这个函数用于加载eBPF程序并将其转换为结构体。它接受一个obj参数,该参数可以是bpfObjects、bpfPrograms或*bpfMaps类型的对象。它还接受一个可选的ebpf.CollectionOptions参数。函数内部调用loadBpf()函数获取ebpf.CollectionSpec对象,并使用spec.LoadAndAssign()将程序和映射加载到内核中,并将其分配给obj对象。
实际流程
上面对bpf2go
工具和生成的bpf_bpfel.go
文件进行了一个简要的分析,接下来以一个实际的例展示用法。
内核态
1 | SEC("kprobe/vfs_mkdir") |
内核态程序保持不变,因为bpf2go
只是一个编译工具,不会对内核态程序进行任何修改。
编译
1 |
|
首先是设置了BPF_CLANG
和BPF_CFLAGS
,然后运行go generate ./...
。go generate ./...
是执行的命令。它使用 go generate
命令来生成 eBPF 代码和库。./...
表示递归地在当前目录及其子目录中执行 go generate
。
generate
前面在Makefile
中存在go generate ./...
,那么就会编译在go文件中存在go:generate
的代码。在本例中的代码就是在main.go
中。
1 | package main |
核心代码是是:1
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS -target bpfel bpf ebpf/main.c -- -I ./ebpf/bpf -I ./ebpf/coreheaders
这段代码的目的是使用bpf2go
工具将位于ebpf/main.c
文件中的eBPF程序转换为Go语言代码,并生成相应的Go包。这样,开发人员可以在Go项目中使用这些生成的代码来与eBPF程序进行交互和操作。
-target bpfel
,表示目标平台为bpfel
,即基于eBPF的Little-endian平台。bpf ebpf/main.c
,要转换为 eBPF 代码的 C 文件路径-- -I ./ebpf/bpf -I ./ebpf/coreheaders
:这是传递给 C 编译器的附加选项,其中 -I 用于指定包含文件的搜索路径。
最终通过make build-ebpf
的效果就是:1
2
3
4
5
6export BPF_CLANG=clang; \
export BPF_CFLAGS="-O2 -g -Wall -Werror"; \
go generate ./...
Compiled /ebpf-example/bpf2go/bpf_bpfel.o
Stripped /ebpf-example/bpf2go/bpf_bpfel.o
Wrote /ebpf-example/bpf2go/bpf_bpfel.go
就会成功生成bpf_bpfel.go
和bpf_bpfel.o
文件。
用户态
由于用户态的程序基本上都是大同小异,主要是关于如何加载eBPF
的程序。1
2
3
4
5
6
7
8
9
10
11
12// Load pre-compiled programs and maps into the kernel.
objs := bpfObjects{}
if err := loadBpfObjects(&objs, nil); err != nil {
fmt.Println("loading objects: %s", err)
}
defer objs.Close()
kp, err := link.Kprobe("vfs_mkdir", objs.bpfPrograms.KprobeVfsMkdir, nil)
if err != nil {
fmt.Println("opening Kprobe: %s", err)
}
defer kp.Close()
loadBpfObjects(&objs, nil)
,加载eBPF
程序并赋值到objs
对象上。
Kprobe()
,函数用于将给定的eBPF程序附加到一个性能事件(perf event),当给定的内核符号开始执行时触发该事件。它接受三个参数:内核符号的名称、eBPF程序的句柄和可选的ebpf.LinkOptions参数。在这里,我们将vfs_mkdir
内核符号作为第一个参数,将objs.bpfPrograms.KprobeVfsMkdir
作为第二个参数,将nil作为第三个参数。这个函数返回一个ebpf.Link对象,它可以用来关闭这个链接。
其中KprobeVfsMkdir
实际的值就是:1
2
3type bpfPrograms struct {
KprobeVfsMkdir *ebpf.Program `ebpf:"kprobe_vfs_mkdir"`
}
通过link.Kprobe(...)
就是加载vfs_mkdir
事件。
实际运行
1 | cat /sys/kernel/debug/tracing/trace_pipe |
通过trace_pipe
成功读取到消息,表示eBPF
程序运行成功。
vArmor分析
存在Makefile
代码:1
2
3
4
5
6
7
8CLANG ?= clang
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
generate-ebpf: export BPF_CLANG := $(CLANG)
generate-ebpf: export BPF_CFLAGS := $(CFLAGS)
generate-ebpf: ## Generate the ebpf code and lib
go generate ./...
设置了BPF_CLANG
和BPF_CFLAGS
的环境变量,然后运行go generate ./...
命令。
go generate
是Go语言的一个命令,用于自动生成Go代码或执行自定义的代码生成操作。它允许开发人员在Go源代码中添加特殊的注释指令(称为”generate directive”),以告诉Go编译器在构建过程中执行额外的代码生成任务。通过使用go generate
命令,您可以在构建过程中自动化执行一些常见的代码生成任务,例如根据模板生成代码、从其他源生成代码等。这样可以减少手动重复的工作,提高开发效率。
分析enforcer.go
和tracer.go
中的代码例子:
tracer.go
1
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS -target bpfel -type event bpf bpf/tracer.c -- -I./bpf/headers
enforcer.go
1
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS -target bpfel bpf bpf/enforcer.c -- -I./bpf/headers
两者的代码基本上都是一致的,上面的代码结合Makefile
就等价于下面的命令:1
go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags -O2 -g -Wall -Werror -target bpfel bpf bpf/enforcer.c -- -I./bpf/headers
go:generate
:这是一个特殊的注释指令,告诉Go编译器在构建过程中执行代码生成任务。go run
:这是go generate
命令执行的子命令,用于运行Go源代码或可执行文件。github.com/cilium/ebpf/cmd/bpf2go
:这是要运行的Go程序的导入路径,即bpf2go
工具的位置。-cc clang
:这是bpf2go
工具的参数,指定使用Clang作为C编译器。-cflags -O2 -g -Wall -Werror
:这是bpf2go
工具的参数,指定C编译器的一些选项,例如优化级别、调试信息和错误处理。-target bpfel
:这是bpf2go
工具的参数,指定目标平台为bpfel
,即基于eBPF的Little-endian平台。bpf bpf/enforcer.c
:这是bpf2go
工具的参数,指定要转换为Go代码的C源文件和相应的包名。-- -I./bpf/headers
:这是bpf2go
工具的参数,指定C编译器的附加头文件搜索路径。
总体而言,这段代码的目的是使用bpf2go
工具将位于bpf/enforcer.c
文件中的eBPF程序转换为Go语言代码,并生成相应的Go包。这样,开发人员可以在Go项目中使用这些生成的代码来与eBPF程序进行交互和操作。
运行完成之后,最终得到的结果如下:1
2
3
4
5
6
7
8make generate-ebpf
go generate ./...
Compiled /vArmor-ebpf/pkg/behavior/bpf_bpfel.o
Stripped /vArmor-ebpf/pkg/behavior/bpf_bpfel.o
Wrote /vArmor-ebpf/pkg/behavior/bpf_bpfel.go
Compiled /vArmor-ebpf/pkg/bpfenforcer/bpf_bpfel.o
Stripped /vArmor-ebpf/pkg/bpfenforcer/bpf_bpfel.o
Wrote /vArmor-ebpf/pkg/bpfenforcer/bpf_bpfel.go
成功生成了bpf_bpfel.go
文件。在用户态的加载使用,也可以参考之前写的文章字节vArmor客户端代码解读
总结
bpf2go
将一些和eBPF
的.o
程序相关的操作全部都封装好了,方便程序员加载和使用eBPF
内核态程序。向比较直接使用.o
,可以简化很多的步骤。
当然也可以直接使用ebpfmanager
来管理eBPF
程序,这个工具也是比较方便的。至于具体的选择,就依据实际的情况而定。
参考
- https://github.com/cilium/ebpf/tree/main/cmd/bpf2go
- https://tonybai.com/2022/07/19/develop-ebpf-program-in-go/
- https://github.com/bigwhite/experiments/tree/master/ebpf-examples/helloworld
- https://github.com/bigwhite/experiments/tree/master/ebpf-examples/helloworld-go
- https://luckymrwang.github.io/2022/08/13/Cilium-eBPF-%E6%90%AD%E5%BB%BA%E4%B8%8E%E4%BD%BF%E7%94%A8/