5

gobpf 使用示例:使用 perf event 保存数据

 3 years ago
source link: https://mozillazg.com/2021/04/ebpf-gobpf-store-and-read-data-use-perf-event.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

前面 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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK