使用go开发ebpf程序最常见的一个框架就是cilium。开发前需要了解ebpf,了解go语言的基础知识。
在本地安装go之后下载bpf2go
go get github.com/cilium/ebpf/cmd/bpf2go
从最简单的开发框架开始
下载示例源码
git clone https://github.com/cilium/ebpf.git
在ebpf/examples下是官方给出的开发样例,以kprobe为例:
代码结构如下
这里以.o结尾的文件都是预编译,使用命令行
go run -exec sudo ./kprobe
就可以直接运行,打印调用次数
如果使用
go clean go generate go build
就可以清除预编译然后重新执行编译
这里我们通过写一个简单的ebpf代码来看一下如何开发
首先bpf_bpfeb.go和bpf_bpfel.go文件不需要更改,属于模板文件。我们要修改的是c文件和main.go文件
首先在c文件中我们一般不需要引入头文件,项目的examples/headers里面已经预准备了头文件,在main.go的//go:generate部分会链接进来
这里的大概逻辑是使用map去存储每次我们调用bpf函数获取到的结果,在main.go中去访问这个map并打印结果或者写到日志
struct bpf_map_def SEC("maps") counting_map = { .type = BPF_MAP_TYPE_ARRAY, .key_size = sizeof(u32), .value_size = sizeof(u64), .max_entries = 1, };
这个结构体记录了key和value的对应值 首先采集到的数据通过更新或者插入写到map中然后使用sync函数同步
在main函数中有几个固定的步骤
- 定义要trace的内核函数名称
- 允许当前进程锁定内存
- 初始化bpfObjects并link到我们自定义的函数,link可以调用kprobe也可以调用Tracepoint,需要看个人需求。这里需要填写刚才定义的objs的自定义函数。这里需要注意,比如我在c文件中定义的函数名为handle_tp,在这里如果直接这样写会报错objs找不到这个成员函数。这里需要将函数转为驼峰写法(应该是go做过转换)。写成HandleTp就可以正常编译。
- 启动一个定时器,每秒去读一次map,并打印值
这里bpf_map_def结构体非常重要,他的key其实是固定的为0,当然我们可以自定义一个key值。这里map是一段共享内存可以在内核和用户空间共享