1

cilium/ebpf是开发ebpf用户态程序的前端框架,通过它可以方便将ebpf程序加载到内核并挂载执行。

cilium/ebpf提供了bpf2go工具,可以通过go generate+bpf2go工具,将c语言编写的ebpf程序,生成bpf.go和bpf.o。

在开发ebpf用户态程序时,可以调用生成的bpf.go中的函数,完成ebpf程序的加载、挂载和执行。

下面以cilium/ebpf/examples下面的tracepoint_in_c为例,讲解其使用过程。

一.bpf2go工具

cilium/ebpf实现了bpf2go工具,代码入口在cmd/bpf2go/main.go中。

可以通过go build命令生成bpf2go:

# go build -o cmd/ebpf2go cmd/bpf2go/*.go

二.内核态程序

内核态的ebpf程序使用c编写:

  • 监听了内存分配函数:kmem/mm_page_alloc;
  • tracepoint被触发时,将map中的计数器+1;

map的定义:

// tracepoint.c

struct bpf_map_def SEC("maps") counting_map = {
    .type        = BPF_MAP_TYPE_ARRAY,
    .key_size    = sizeof(u32),
    .value_size  = sizeof(u64),
    .max_entries = 1,
};

监听点和处理函数:

  • 发生内存分配时,更新map的key对应的value,值+1;

    // tracepoint.c
    
    SEC("tracepoint/kmem/mm_page_alloc")
    int mm_page_alloc(struct alloc_info *info) {
      u32 key     = 0;
      u64 initval = 1, *valp;
    
      valp = bpf_map_lookup_elem(&counting_map, &key);
      if (!valp) {
          bpf_map_update_elem(&counting_map, &key, &initval, BPF_ANY);
          return 0;
      }
      __sync_fetch_and_add(valp, 1);
      return 0;
    }

三.用户态程序

用户态应用程序通常命名为main.go。

首先,在main.go中,为ebpf程序添加go generate注解:

// main.go
...
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go bpf tracepoint.c -- -I../headers
...

执行go generate命令后,将生成:

  • Bpf_bpfel.o 和 Bpf_bpfel.go

    • 为little-endian架构生成,比如amd64、arm64、riscv64和loong64;
  • Bpf_bpfeb.o 和 bpf_bpfeb.go

    • 为big-endian架构生成,比如s390(x)、mips和sparc;
# go generate
Compiled github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.o
Stripped github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.o
Wrote github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfel.go
Compiled github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.o
Stripped github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.o
Wrote github.com/cilium/ebpf/examples/tracepoint_in_c/bpf_bpfeb.go

生成的.go文件末尾,均包含 //go:embed 注解,该注解会将.o文件包含到最终的go二进制文件中,便于程序的分发:

// bpf_bpfel.go
…
// Do not access this directly.
//
//go:embed bpf_bpfeb.o
var _BpfBytes []byte

然后,编写main.go代码,它会调用bpf_bpfel.go中的函数,执行ebpf程序的加载、挂载和执行:

  • 使用1s的定时器,每隔1s读取map中的数据;
// main.go

func main() {
    ...
    // Load pre-compiled programs and maps into the kernel.
    objs := bpfObjects{}
    if err := loadBpfObjects(&objs, nil); err != nil {
        log.Fatalf("loading objects: %v", err)
    }
    defer objs.Close()

    // Open a tracepoint and attach the pre-compiled program. Each time
    // the kernel function enters, the program will increment the execution
    // counter by 1. The read loop below polls this map value once per
    // second.
    // The first two arguments are taken from the following pathname:
    // /sys/kernel/tracing/events/kmem/mm_page_alloc
    kp, err := link.Tracepoint("kmem", "mm_page_alloc", objs.MmPageAlloc, nil)
    defer kp.Close()

    // Read loop reporting the total amount of times the kernel
    // function was entered, once per second.
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for range ticker.C {
        var value uint64
        if err := objs.CountingMap.Lookup(mapKey, &value); err != nil {
            log.Fatalf("reading map: %v", err)
        }
        log.Printf("%v times", value)
    }
}

执行go build生成二进制可执行文件:

# CGO_ENABLED=0 GOARCH=amd64 go build

执行二进制文件:

# ./tracepoint_in_c
2023/11/07 20:49:44 Waiting for events..
2023/11/07 20:49:45 14 times
2023/11/07 20:49:46 26 times
2023/11/07 20:49:47 49 times

参考

1.https://github.com/cilium/ebpf


a朋
63 声望38 粉丝