本文通过监听ksys_write函数的调用情况来学习cilium/ebpf的编写方法,相较于bcc,cilium/ebpf对环境的依赖较少。
环境安装
cilium/ebpf是一个纯GO库,提供加载,编译和调试eBPF程序的实用程序,安装环境llvm11、clang11、go 1.7,安装命令如下:
1 | sudo apt install libllvm11 llvm-11-dev libclang-11-dev |
代码编写
内核函数原型如下,发起write系统调用后会执行该函数,我们通过指定pid的方式,获取调用者传输给该函数的三个参数(文件描述符、写入内容、写入数量)。
1 | ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count) |
新建go项目
1 | $ go mod init myebpf |
从cilium/ebpf官方仓库(https://github.com/cilium/ebpf/)
examples目录下拷贝headers文件夹,放到新创建的go项目下。
go.mod文件中添加cilium/ebpf项目依赖。
1 | module github.com/myebpf |
创建kprob.c文件,编写bpf内核部分的代码。
1 | // +build ignore |
创建main.go文件,编译并加载bpf内核部分的代码。
1 | //go:build linux |
代码中有过滤PID以及读取数据方式供大家参考。
执行编译命令:
1 | $ go generate |
该命令的具体内容在main.go文件的上方//go:generate注释处,可以通过编辑该行注释调整命令的执行内容。
1 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS --target=amd64 bpf kprobe.c -- -I./headers |
例如:$BPF_CLANG环境变量系统中通常是没有的,可以将此处替换为clang,或者在当前shell中执行:
$ export BPF_CLANG=clang
如果找不到头文件,还要注意该行注释中headers文件夹的相对路径是否配置正确。
最后–target=amd64选项能够让我们使用PT_REGS_PARM1宏定义来读取内核函数的参数。
编译后生成bpf_bpfel_x86.go和bpf_bpfel_x86.o文件,如图所示。
运行程序
1 | $ go run main.go bpf_bpfel_x86.go -pid=1235 |
至此完成程序的编写与数据采集。
iovisor/gobpf
最后附上相同内容在iovisor/gobpf环境下的写法,以供对比参考。
1 | package main |
参考资料:
https://github.com/iovisor/gobpf
https://github.com/cilium/ebpf
https://pkg.go.dev/github.com/cilium/ebpf