Fundamentals 14 min read

Mastering eBPF with CO-RE: From Basics to Go Implementation

This article introduces eBPF fundamentals, explains the Compile‑Once‑Run‑Everywhere (CO‑RE) approach, compares it with traditional eBPF, outlines best practices, and walks through a complete Go‑based example using the Cilium/eBPF library and the eunomia‑bpf runtime.

BirdNest Tech Talk
BirdNest Tech Talk
BirdNest Tech Talk
Mastering eBPF with CO-RE: From Basics to Go Implementation

eBPF Introduction

Official documentation: https://ebpf.io/

Kernel‑related docs: https://prototype-kernel.readthedocs.io/en/latest/bpf/

Chinese beginner guide: https://www.ebpf.top/post/ebpf_intro

Curated list of resources: https://github.com/zoidbergwill/awesome-ebpf

Kernel version compatibility table: https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md

eBPF Toolchain

bpftrace tutorial (simplest entry point): https://eunomia.dev/zh/tutorials/bpftrace-tutorial/

BCC example collection: https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md

libbpf bootstrap examples: https://github.com/libbpf/libbpf-bootstrap

libbpf + eunomia‑bpf tutorial: https://github.com/eunomia-bpf/bpf-developer-tutorial

XDP tutorial: https://github.com/xdp-project/xdp-tutorial

Linux kernel sample programs: https://github.com/torvalds/linux/tree/master/samples/bpf

Go eBPF Libraries

cilium/ebpf – widely used Go library for loading and managing eBPF programs.

iovisor/gobpf – Go bindings for BCC.

libbpfgo – thin Go wrapper around libbpf.

dropbox/goebpf – simple Go eBPF library from Dropbox.

eBPF CO‑RE: Compile Once – Run Everywhere

CO‑RE solves the portability problem by embedding BTF (BPF Type Format) information in the eBPF object. At load time the kernel provides type and symbol data, allowing the loader to relocate memory accesses without recompiling the program for each kernel version.

Traditional eBPF vs CO‑RE eBPF

Traditional eBPF limitations

Poor portability – recompilation required for every target kernel.

Full kernel headers needed at compile time.

Programs often tied to a specific kernel version.

CO‑RE advantages

Compile once, run on many kernels.

No need for complete kernel headers; BTF supplies the necessary type information.

Adaptable to different kernel configurations.

Simplified deployment – a single binary can be distributed.

Best Practices

Use the latest libbpf release to obtain the newest CO‑RE support.

Enable BTF generation with the -g compile flag.

Generate a skeleton header via bpftool gen skeleton to simplify loading.

Avoid hard‑coded struct offsets; rely on BPF helpers and CO‑RE relocations.

Test the program on multiple kernel versions to verify correct relocation.

eunomia‑bpf Overview

eunomia‑bpf is an open‑source runtime and toolchain built on libbpf’s CO‑RE support. It abstracts away the need to write user‑space loading code while remaining 100 % compatible with libbpf, libbpfgo, and libbpf‑rs APIs.

Only kernel‑mode code is written; eunomia‑bpf extracts exported symbols and loads the program dynamically.

User‑space logic can be written in WebAssembly, enabling multi‑language development.

Pre‑compiled eBPF objects can be packaged as JSON or WASM modules, allowing cross‑architecture and cross‑kernel distribution without recompilation.

Cilium/eBPF Library

Cilium/eBPF provides a safe, high‑performance Go API for loading, managing, and interacting with eBPF programs.

Program loading – load pre‑compiled eBPF object files.

Map management – create, access, and manage eBPF maps.

Program attachment – attach programs to various kernel hook points (kprobes, tracepoints, XDP, etc.).

BTF support – enables CO‑RE compatibility across kernel versions.

CO‑RE support – write once, run everywhere.

Error handling – detailed error messages for debugging.

Security checks – prevents unsafe operations at load time.

Basic Cilium/eBPF Example

The following Go program loads a simple kprobe that tracks the execve system call. The C source kprobe.c is compiled to an eBPF object with bpf2go (triggered by the go:generate directive).

package main

import (
    "fmt"
    "log"

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

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

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

    // Load the pre‑compiled eBPF program
    objs := bpfObjects{}
    if err := loadBpfObjects(&objs, nil); err != nil {
        log.Fatalf("loading objects: %v", err)
    }
    defer objs.Close()

    // Create a kprobe program
    kp, err := ebpf.NewProgram(&ebpf.ProgramSpec{
        Name:    "kprobe_exec",
        Type:    ebpf.Kprobe,
        License: "GPL",
    })
    if err != nil {
        log.Fatalf("creating program: %v", err)
    }
    defer kp.Close()

    // Attach the kprobe to the execve syscall
    if err := kp.Attach("__x64_sys_execve"); err != nil {
        log.Fatalf("attaching program: %v", err)
    }

    fmt.Println("Successfully loaded and attached eBPF program")
    // Keep the program running
    select {}
}

The accompanying C file defines the kernel‑mode logic:

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "Dual BSD/GPL";

struct event {
    u32 pid;
    u8  comm[80];
};

struct {
    __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
    __uint(key_size, sizeof(u32));
    __uint(value_size, sizeof(u32));
} events SEC(".maps");

SEC("kprobe/__x64_sys_execve")
int kprobe_exec(struct pt_regs *ctx) {
    struct event event = {};
    event.pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&event.comm, sizeof(event.comm));
    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &event, sizeof(event));
    return 0;
}

Program flow:

Retrieve the current process ID with bpf_get_current_pid_tgid().

Fetch the process name via bpf_get_current_comm().

Emit the event struct to user space through a perf event array.

Additional Notes

Requires a recent Linux kernel (5.4+ recommended).

Root privileges are typically needed to load eBPF programs.

Some features depend on kernel configuration options (e.g., CONFIG_BPF, CONFIG_DEBUG_INFO_BTF).

References

[1] bpf‑developer‑tutorial: https://github.com/eunomia-bpf/bpf-developer-tutorial/blob/main/src/0-introduce/README.md

[2] cilium/ebpf: https://github.com/cilium/ebpf

[3] iovisor/gobpf: https://github.com/iovisor/gobpf

[4] libbpfgo: https://github.com/aquasecurity/libbpfgo

[5] dropbox/goebpf: https://github.com/dropbox/goebpf

[6] eunomia‑bpf: https://github.com/eunomia-bpf/eunomia-bpf

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GoLinuxeBPFlibbpfCiliumCO-REeunomia-bpf
BirdNest Tech Talk
Written by

BirdNest Tech Talk

Author of the rpcx microservice framework, original book author, and chair of Baidu's Go CMC committee.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.