eBPF’s Compile Once - Run Everywhere (CO-RE) paradigm is a game-changer for portability, allowing eBPF programs to adapt to different kernel versions without on-host recompilation. This magic heavily relies on BPF Type Format (BTF), a metadata format describing kernel and eBPF program types. Modern Linux kernels (typically 5.2+) often provide embedded BTF, but deploying eBPF applications on older kernels, or even newer ones where BTF support wasn’t enabled by the distribution, presents a significant challenge.
This article provides a definitive guide to understanding and overcoming these BTF limitations, enabling CO-RE capabilities for your eBPF applications on a wider range of Linux systems. We’ll cover how to leverage external BTF sources like BTFHub, minimize BTF file sizes, and integrate this into your eBPF loading logic using libbpf
and cilium/ebpf
.
The Crux of the Problem: CO-RE Needs Kernel BTF
eBPF CO-RE works by embedding BTF information within the compiled eBPF object file. This “program BTF” describes the types the eBPF program expects. At load time, libbpf
(or equivalent libraries like cilium/ebpf
) compares this program BTF with the “kernel BTF” of the target host. If discrepancies are found (e.g., a struct field offset has changed between kernel versions), libbpf
performs runtime relocations, adjusting the eBPF bytecode to correctly access kernel data structures.
The problem arises when the target kernel doesn’t provide its own BTF. This typically happens in:
- Kernels older than Linux 5.2: These versions predate the
CONFIG_DEBUG_INFO_BTF
Kconfig option necessary for generating embedded BTF. - Kernels (even 5.2+) where
CONFIG_DEBUG_INFO_BTF=y
was not set: Some distributions didn’t enable this by default, often due to older build toolchains (e.g.,pahole
version < v1.16).
Without kernel BTF, libbpf
has no reference to understand the target kernel’s actual data structure layouts, and CO-RE relocations cannot be performed, leading to load failures or runtime errors. The standard location for kernel-provided BTF is /sys/kernel/btf/vmlinux
.
You can quickly check for native BTF support:
|
|
If this file is missing, you’ll likely need an alternative BTF solution for CO-RE.
Solution 1: Leveraging External BTF with BTFHub
The most robust solution for kernels lacking native BTF is to provide it externally. BTFHub is a community project that collects and archives BTF files for a vast number of Linux distributions and kernel versions.
- BTFHub: https://github.com/aquasecurity/btfhub
- BTFHub-Archive (where BTF files reside): https://github.com/aquasecurity/btfhub-archive
The workflow involves:
- Identifying the target kernel: Determine the distribution, version, architecture, and kernel release string (e.g.,
uname -r
). - Fetching the corresponding BTF file: Download the appropriate
.btf
file from the BTFHub-Archive. These are often compressed (e.g.,.btf.tar.xz
). - Providing it to the eBPF loader: Instruct
libbpf
orcilium/ebpf
to use this external BTF file.
Using External BTF with libbpf
(C/C++)
libbpf
allows specifying a custom path for kernel BTF via bpf_object_open_opts
:
|
|
Note: Ensure opts.sz
is correctly initialized. The btf_custom_path
option effectively overrides libbpf
’s default search for /sys/kernel/btf/vmlinux
.
Using External BTF with cilium/ebpf
(Go)
The cilium/ebpf
library provides similar functionality through CollectionOptions
:
|
|
This approach allows your Go eBPF application to bundle or fetch BTF files and supply them at runtime.
Solution 2: Minimizing BTF with bpftool gen min_core_btf
Full kernel BTF files from BTFHub can be several megabytes. Shipping many such files with an application is often impractical. The solution is to generate a minimal BTF file containing only the types relevant to your specific eBPF program.
bpftool
(a standard eBPF utility) provides the gen min_core_btf
command for this purpose (this functionality was historically part of btfgen
):
|
|
This command analyzes my_bpf_program.o
, identifies all kernel types it references via CO-RE, and extracts only those type definitions from the FULL_KERNEL_BTF_PATH
, writing them to MINIMAL_BTF_PATH
.
This process is typically integrated into the eBPF application’s build system:
- For each target kernel (or range of kernels) you want to support:
- Obtain its full BTF from BTFHub.
- Run
bpftool gen min_core_btf
against your eBPF object(s).
- Bundle these minimal BTF files with your application.
- At runtime, select the appropriate minimal BTF based on the detected kernel and provide it to the loader library.
This dramatically reduces the storage overhead for supporting multiple kernel versions.
Diagnostic Steps and Considerations
Kernel Version and Configuration:
- Always start by checking the kernel version:
uname -r
. - Check for native BTF:
ls -l /sys/kernel/btf/vmlinux
. - If possible, check the kernel’s build config:
grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r)
(This file might not always be available or accurate for custom kernels).
- Always start by checking the kernel version:
Loader Library Verbosity:
- libbpf: Use
libbpf_set_print(LIBBPF_PRINT_KERNEL)
orLIBBPF_PRINT_DEBUG
to get detailed output fromlibbpf
during the object opening and loading process. This will show which BTF files it’s trying to use and any errors encountered.1 2
// Example: Set libbpf print level libbpf_set_print(LIBBPF_PRINT_DEBUG); // Or LIBBPF_PRINT_KERNEL
- cilium/ebpf: Program loading options can include
ebpf.ProgramOptions{ LogLevel: ebpf.LogLevelInstruction }
to get verifier logs.
- libbpf: Use
bpftool feature probe
: This command can provide information about BPF features supported by the currently running kernel, although it doesn’t directly confirm BTF availability for arbitrary types.BTF File Accuracy: Ensure the BTF file (full or minimal) precisely matches the target kernel’s architecture and exact version. Even minor patch differences could alter structures, though BTFHub aims for compatibility where possible.
Build System Complexity: Integrating BTFHub downloads and
bpftool gen min_core_btf
into a build pipeline adds complexity but is essential for robust CO-RE on older systems. Projects like Eunomia-bpf’sbpf-compatible
tool showcase packaging eBPF programs with necessary BTF.Limitations: Even with external BTF, some eBPF program types or features that intrinsically require newer kernel APIs (beyond just type information) might not function correctly on very old kernels.
Conclusion: Expanding eBPF’s Reach
While native BTF support is becoming more common, the ability to provide kernel BTF externally via BTFHub and tools like bpftool gen min_core_btf
is crucial for maximizing the portability of CO-RE eBPF applications. By understanding these mechanisms and integrating them into your development and deployment workflows, you can confidently target a broader spectrum of Linux kernels, including older ones that don’t ship with BTF.
This approach, while adding some build-time overhead, empowers developers to deliver powerful eBPF solutions that truly “Compile Once - Run Everywhere,” bridging the gap left by varying kernel configurations and ensuring wider applicability of modern eBPF observability and security tools.