1. 环境


2. 安装依赖包

2.1. Ubuntu 22.04 换国内源(可选)

sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
sudo rm -f /etc/apt/sources.list

将阿里云源添加到 /etc/apt/sources.list 中:

deb https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse
deb https://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse
deb-src https://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse

执行如下命令更新源:

sudo apt update

2.2. 安装依赖包

sudo apt install -y openssl libssl-dev git curl build-essential cmake llvm-14 llvm-14-dev clang-14 linux-headers-`uname -r` linux-tools-`uname -r` libbpf-dev libbpf0
ln -s `which clang-14` /usr/bin/clang
ln -s `which llvm-strip-14` /usr/bin/llvm-strip

常用命令:


3. 安装 go 1.21.10、bpf2go v0.14.0

wget https://go.dev/dl/go1.21.10.linux-amd64.tar.gz
tar -xf go1.21.10.linux-amd64.tar.gz
mv go /usr/local/

# 在 /etc/profile 中增加如下内容
export GOROOT=/usr/local/go
export GOPATH=/root/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

source /etc/profile
go env -w GOPROXY=https://goproxy.cn,direct

# 安装 bpf2go
go install github.com/cilium/ebpf/cmd/bpf2go@v0.14.0
bpf2go -h

4. 创建测试项目

本示例来自于 cilium/ebpf

4.1. 创建项目

mkdir ebpftest
cd ebpftest/
go mod init ebpftest
go get github.com/cilium/ebpf@v0.14.0

4.2. 创建 eBPF 程序

kprobe_percpu.c:

//go:build ignore

#include "common.h"

char __license[] SEC("license") = "Dual MIT/GPL";

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

SEC("kprobe/sys_execve")
int kprobe_execve() {
    u32 key     = 0;
    u64 initval = 1, *valp;

    valp = bpf_map_lookup_elem(&kprobe_map, &key);
    if (!valp) {
        bpf_map_update_elem(&kprobe_map, &key, &initval, BPF_ANY);
        return 0;
    }
    __sync_fetch_and_add(valp, 1);

    return 0;
}

4.3. 创建用户空间程序

// This program demonstrates attaching an eBPF program to a kernel symbol and
// using percpu map to collect data. The eBPF program will be attached to the
// start of the sys_execve kernel function and prints out the number of called
// times on each cpu every second.
package main

import (
    "log"
    "time"

    "github.com/cilium/ebpf/link"
    "github.com/cilium/ebpf/rlimit"
)

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

const mapKey uint32 = 0

func main() {

    // Name of the kernel function to trace.
    fn := "sys_execve"

    // Allow the current process to lock memory for eBPF resources.
    if err := rlimit.RemoveMemlock(); err != nil {
        log.Fatal(err)
    }

    // 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 Kprobe at the entry point of the kernel function 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.
    kp, err := link.Kprobe(fn, objs.KprobeExecve, nil)
    if err != nil {
        log.Fatalf("opening kprobe: %s", err)
    }
    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()

    log.Println("Waiting for events..")

    for range ticker.C {
        var all_cpu_value []uint64
        if err := objs.KprobeMap.Lookup(mapKey, &all_cpu_value); err != nil {
            log.Fatalf("reading map: %v", err)
        }
        for cpuid, cpuvalue := range all_cpu_value {
            log.Printf("%s called %d times on CPU%v\n", fn, cpuvalue, cpuid)
        }
        log.Printf("\n")
    }
}

4.4. 拷贝头文件

https://github.com/cilium/ebpf/tree/v0.14.0/examples/headers 目录中的头文件拷贝到项目目录的 headers/ 子目录下。

4.5. 修改 main.go,生成 eBPF 程序

修改 main.go:

//go:generate bpf2go bpf kprobe_percpu.c -- -I./headers

运行如下命令生成 eBPF 程序:

go generate

4.6 编译、运行

go mod tidy
go build -tags amd64 -o main
./main

打开另一个窗口,运行若干条命令,同时观察输出。

注意:

  1. 根据 CPU 架构,指定 go build 中的 -tags 选项的值