前言¶
前面 hello 示例中我们是通过 sudo cat /sys/kernel/debug/tracing/trace_pipe 的方式来查看数据的, 本文简单讲述如何通过 perf event 来保存 eBPF 数据然后再在 Go 程序中读取 perf event 中保存的数据。
使用 perf event 保存数据¶
还是前面的那个 hello.c ,不过这次会使用 perf event 来保存 open 的文件名称
eBPF C 代码如下(hello.c):
#include <linux/bpf.h>
#include <linux/ptrace.h>
#include "include/bpf_helpers.h"
struct bpf_map_def SEC("maps/open_event") open_event = {
.type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(__u32),
.max_entries = 128,
.pinning = 0,
.namespace = "",
};
struct data_t {
__u32 pid;
char file_name[256];
};
SEC("kprobe/do_sys_open")
int kprobe__do_sys_open(struct pt_regs *ctx) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid() >> 32;
__u32 cpu = bpf_get_smp_processor_id();
bpf_probe_read(&data.file_name, sizeof(data.file_name), PT_REGS_PARM2(ctx));
bpf_perf_event_output(ctx, &open_event, cpu, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
简单来说就是定义一个 type 为 BPF_MAP_TYPE_PERF_EVENT_ARRAY 的 bpf_map_def 变量 open_event, 然后通过 bpf_perf_event_output 函数将数据写入到定义的 open_event 中。
在 Go 程序中读取 perf event 数据¶
package main
import (
"fmt"
"os"
"unsafe"
"github.com/iovisor/gobpf/elf"
)
/*
#include <linux/types.h>
struct data_t {
__u32 pid;
char file_name[256];
};
*/
import "C"
type Event struct {
Pid uint32
FileName string
}
func main() {
mod := elf.NewModule("hello.o")
err := mod.Load(nil)
if err != nil {
panic(err)
}
defer mod.Close()
err = mod.EnableKprobes(128)
if err != nil {
panic(err)
}
channel := make(chan []byte)
lost := make(chan uint64)
perfMap, err := elf.InitPerfMap(mod, "open_event", channel, lost)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to init perf map: %s\n", err)
os.Exit(1)
}
go func() {
for {
l := <-lost
fmt.Println(l)
}
}()
perfMap.PollStart()
defer perfMap.PollStop()
for {
var event Event
data := <-channel
event = openEventToGo(&data)
fmt.Printf("pid %d open file %s\n", event.Pid, event.FileName)
}
}
func openEventToGo(data *[]byte) (event Event) {
eventC := (*C.struct_data_t)(unsafe.Pointer(&(*data)[0]))
event.Pid = uint32(eventC.pid)
event.FileName = C.GoString(&eventC.file_name[0])
return
}
通过 elf.InitPerfMap 指定要读取的 perf event map 的变量的名称 (跟 eBPF 程序中定义的 open_event 相对应),然后使用方法的 channel 读取数据。
同时会使用 cgo 来将 c 中的数据类型转换为 go 中定义的结构体
编译并运行:
$ make $ make run ... pid 364 open file /proc/493/status pid 364 open file /proc/493/status pid 364 open file /proc/493/comm ...
可以看到已经可以在 go 程序中读取到 eBPF 程序保存的数据了。
P.S. 本文的所有代码在 Github 上都有一份完整版: https://github.com/mozillazg/gobpf-examples/tree/master/2-perf-event
Comments