Course contentsShow
← Linux
Lesson 1638 of 1,81914. Observability and Performance TroubleshootingFree lesson

BPF Program CO-RE Principles

Implement Compile-Once-Run-Everywhere logic to ensure eBPF code portability across kernel versions.

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:

  1. BTF (BPF Type Format): The kernel exposes its internal type definitions (structs, unions, sizes) in a compact format.
  2. 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 pid inside task_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.