git仓库地址:https://github.com/cilium/ebpf
我的环境:Ubuntu 20.04 64bit
安装依赖
1 | $ sudo apt install clang-13 llvm-13 |
编译运行
1 | $ cd kprobe |
如果能打印出sys_execve的执行次数,说明环境搭建成功了。
第一个项目
创建项目目录
1 | $ mkdir first |
将Cilium eBPF example中的相关文件复制过来作为基础进行修改
1 | $ cd ebpf/examples |
修改main.go第19行
1 | -I../headers -> -I./headers |
go generate命令是go 1.4版本里面新添加的一个命令,当运行go generate时,它将扫描与当前包相关的源代码文件,找出所有包含”//go:generate”的特殊注释,提取并执行该特殊注释后面的命令,命令为可执行程序,形同shell下面执行。
将依赖添加到go.mod中,并进行生成和编译
1 | $ cd first |
代码分析
先看一下main.go中main函数的前半部分
1 | func main() { |
fn
中定义了kprobe附着的函数为sys_execve
,并锁定当前进程 eBPF 资源的内存。
之后是调用loadBpfObjects
将预先编译的 eBPF 程序和 maps 加载到内核,其定义在生成的.go文件中,最后是调用link.Kprobe
进行真正的attach。
关于这个objs
,其类型是bpfObjects
,定义在生成的.go文件
1 | // bpfSpecs contains maps and programs before they are loaded into the kernel. |
bpfProgramSpecs
、bpfMapSpecs
的定义分别为:
1 | // bpfSpecs contains programs before they are loaded into the kernel. |
kprobe_execve
、kprobe_map
分别对应 kprobe.c 文件中定义的:
1 | struct bpf_map_def SEC("maps") kprobe_map = { |
所以,Go 中的这两个名字 KprobeExecve、KprobeMap 就是根据 C 程序中的这两个名字生成过来的,规则是:首字母大写,去除下划线_并大写后一个字母。
实现新功能
利用刚刚创建的 Cilium eBPF 项目,编写一个可以监听 open 系统调用,获取 filename 的程序。首先先看一下open系统调用
1 | SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) |
1 | static long do_sys_openat2(int dfd, const char __user *filename, |
目标是获取do_sys_openat2的第二个参数filename。打开kprobe.c开始改造,将宏SEC的名字和函数名改为
1 | SEC("kprobe/do_sys_openat2") |
想知道当前是哪个进程进行了open系统调用,所以可以通过BPF辅助函数bpf_get_current_pid_tgid
获得当前pid_tgid
1 | u32 pid = bpf_get_current_pid_tgid() >> 32; |
关于BPF辅助函数,可以参考文档:https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html
filename 在kprobe_openat2的第二个参数,可以通过PT_REGS_PARM2
宏获取,其定义在bpf_tracing.h
1 |
__user
代表该数据在用户空间,所以需要bpf_probe_read_user_str
读取
1 |
|
之后可以通过bpf_printk
将这些数据输出到/sys/kernel/debug/tracing/trace
中
1 | bpf_printk("pid:%d,filename:%s,err:%ld",pid,filename,err); |
kprobe.c改造结束了,但是使用PT_REGS_PARM2
需要指定target,在main.go中,继续修改第19行为
1 | //go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS --target=amd64 bpf kprobe.c -- -I./headers |
我所使用的机器平台为amd64,所以我加上了–target=amd64,删除之前生成的文件,否则可能会在之后报错,执行go generate调用bpf2go生成,此次由于指定了target为amd64,所以生成的文件为x86版本。
接着修改main.go中对应的函数名(26和44行)
1 | fn := "do_sys_openat2" |
44行中的名字可以在生成的bpf_bpfel_x86.go文件中看到。
最后编译并运行
1 | $ go build |
查看输出
1 | $ sudo cat /sys/kernel/debug/tracing/trace |