In this lesson, you will learn how to leverage BPF CO-RE (Compile-Once-Run-Everywhere) to write eBPF programs that remain portable across different kernel versions without requiring recompilation.
The Problem: Kernel Fragility
Historically, if you wrote an eBPF program that accessed a kernel structure field—like task_struct->pid—the compiler would bake the exact byte offset of that field into your bytecode. Because kernel developers often shift fields around between kernel versions (or change data structures entirely), a program compiled on a 5.4 kernel would frequently crash or fail to load on a 5.15 kernel. This forced developers to ship source code and recompile BPF programs on every target machine, which is slow and error-prone.
The Solution: BTF and Relocation
CO-RE solves this by decoupling the BPF bytecode from the hardcoded memory offsets. It relies on two pillars:
- BTF (BPF Type Format): The kernel exposes its internal type definitions (structs, unions, sizes) in a compact format.
- CO-RE Relocations: Instead of hardcoding an offset, the BPF compiler emits a "relocation record." This record tells the BPF loader: "I need the offset for field
pidinsidetask_struct."
When your program is loaded, the libbpf loader inspects the target kernel's BTF data, looks up the current layout of task_struct, calculates the correct offset on-the-fly, and patches the BPF bytecode before it is verified by the kernel.
A Concrete Illustration
To use this, you define the structures you need and decorate them with the bpf_core_read helper or rely on the compiler to handle the relocation:
#include "vmlinux.h"
#include <bpf/bpf_core_read.h>
SEC("kprobe/sys_execve")
int BPF_KPROBE(handle_execve) {
struct task_struct *task = (void *)bpf_get_current_task_btf();
pid_t pid;
// The compiler records this as a CO-RE relocation
bpf_core_read(&pid, sizeof(pid), &task->tgid);
return 0;
}
By using the vmlinux.h header—which is a generated header containing all kernel types for your specific environment—and bpf_core_read, you ensure that your code is not anchored to a single kernel’s memory layout.
Key Takeaway: BPF CO-RE uses BTF metadata and relocation records to dynamically patch memory offsets at load-time, allowing a single binary to run across disparate kernel versions.