A libbpf CO-RE binary that works on a current Ubuntu server fails to load on an older Linux host, and `/sys/kernel/btf/vmlinux` does not exist there. What is missing, and what are your options for that host?
answer
- a config option, not a kernel capability
- types have no description here
- pahole turns DWARF into BTF
- BTF can come from a file
- exact build, not just the version
basics
~20 sThat kernel was built without CONFIG_DEBUG_INFO_BTF, so it exposes no BTF and libbpf has nothing to resolve the binary's CO-RE relocations against. Options are a kernel built with BTF enabled, supplying an externally generated BTF for that exact kernel, or falling back to a runtime-compiled bcc tool.
solid answer
~50 s`/sys/kernel/btf/vmlinux` appears only when the kernel was compiled with `CONFIG_DEBUG_INFO_BTF=y`, which requires a recent enough `pahole` from the dwarves package at kernel build time. Without it there is no description of that kernel's types, so libbpf cannot patch the struct field offsets your binary recorded at compile time, and the load fails rather than reading the wrong memory. You have three realistic moves. Upgrade or rebuild the kernel with BTF enabled — cleanest, and on most distributions a newer stock kernel already has it. Or supply BTF out of band: generate it from that kernel's debug info and point libbpf at the file through `btf_custom_path` in `struct bpf_object_open_opts`, which is what the community BTFHub archive of prebuilt BTF for older distro kernels exists for. Or, for a one-off, run a bcc tool that compiles against the host's kernel headers instead.
code
bash · 3 linesls -l /sys/kernel/btf/vmlinux || echo "no kernel BTF"
grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.hgo deeper
Know that CO-RE binaries rely on a type description the kernel publishes at /sys/kernel/btf/vmlinux, and that its absence is a property of how the kernel was built.
Explain the chain: CONFIG_DEBUG_INFO_BTF plus pahole at build time produces the BTF blob, libbpf reads it at load time to resolve relocations, and with no blob the load fails by design.
Weigh the three fixes on a real host — upgrade the kernel, ship an externally generated BTF and point libbpf at it, or fall back to a header-compiled bcc tool — and say why an exact build match matters for the second.
Own the fleet baseline: declare the minimum kernel and BTF requirement for shipped tooling, decide whether legacy hosts get external BTF or are excluded, and make the check part of the rollout rather than a discovery during an incident.
## What the missing file means BTF (BPF Type Format) is a compact description of a kernel's types — every struct, its members and their offsets. The kernel exposes its own BTF as a read-only blob at `/sys/kernel/btf/vmlinux`, and one file per loaded module under `/sys/kernel/btf/<module>`. That blob is produced *at kernel build time*: the build compiles with debug info and then runs `pahole` (from the `dwarves` package) to convert DWARF into BTF, all gated behind `CONFIG_DEBUG_INFO_BTF=y`. If the option was off, or the build machine's pahole was too old, the kernel ships without it and the file is simply absent. Check it directly: ```bash ls -l /sys/kernel/btf/vmlinux grep CONFIG_DEBUG_INFO_BTF /boot/config-$(uname -r) bpftool btf dump file /sys/kernel/btf/vmlinux format c | head ``` ## Why the load fails rather than degrading Your binary carries CO-RE relocation records — "the instruction at this offset reads field `X` of `struct Y`; fix its offset for the running kernel". libbpf resolves each one by looking the type up in the kernel's BTF. With no BTF, there is nothing to look up, and libbpf refuses to load. That is the right behaviour: the alternative would be to keep the build machine's offsets and read arbitrary bytes out of kernel structures, producing plausible nonsense. Failing loudly at load is much better than silently wrong tracing data. ## Option 1 — get a kernel with BTF On a supported distribution this is usually a package away: mainstream kernels have enabled the option since roughly 2020, so a newer stock kernel or a hardware-enablement kernel fixes it outright. If you build your own kernels, turn on `CONFIG_DEBUG_INFO_BTF` and make sure the build host has a pahole new enough for your kernel version — a too-old pahole is the classic reason the option is silently dropped during a build. This is the option to push for, because it fixes every CO-RE tool on that host at once instead of one at a time. ## Option 2 — supply BTF from outside BTF for a kernel can be generated anywhere, as long as you have that exact kernel's debug information. The pattern is: obtain the kernel's debug symbols (a distro debuginfo package), run pahole against them to produce a `.btf` file, put that file on the host, and tell libbpf to use it instead of the kernel's own: ```c LIBBPF_OPTS(bpf_object_open_opts, opts, .btf_custom_path = "/lib/btf/vmlinux-4.19.0.btf"); ``` The skeleton's `__open_opts()` entry point takes the same options struct. Doing this per kernel version for a whole fleet is tedious, which is exactly why the community BTFHub project publishes prebuilt BTF blobs for older distro kernels — the file is generated once and shipped alongside the tool. bpftool can also produce a **reduced** BTF containing only the types a given object actually needs, which shrinks a multi-megabyte blob to something you are willing to embed in a binary. The operational catch: the BTF must match the *exact* kernel build, not merely the version string. A vendor's respin with different config produces different offsets, and using another build's BTF reintroduces the very bug CO-RE prevents. ## Option 3 — do not use CO-RE on that host bcc compiles against the running kernel's headers, so it needs no BTF at all. If the host is a one-off legacy box and you need an answer now rather than a shipping story, installing kernel headers and running the equivalent bcc tool is the pragmatic move. It costs a compiler on the host and slow startup, but it works where CO-RE cannot. ## The judgment an interviewer is listening for The weak answer is "the kernel is too old for eBPF". Usually it is not — the host may run BPF programs perfectly well; it merely cannot describe its own types. Separating those two facts, checking `/boot/config-$(uname -r)`, and knowing that BTF can come from somewhere other than the running kernel is what distinguishes someone who has shipped eBPF tooling across a real fleet from someone who has only run it on their laptop.
- What is vmlinux.h, and why does a libbpf project generate it rather than including kernel headers?It is a single C header containing every kernel type, produced with `bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h`. Including it means the BPF source depends on no kernel headers at all, which is what makes the build reproducible off-target. The layouts it declares are only a starting point — CO-RE relocations correct the offsets at load time for whatever kernel the binary lands on.
- Does an eBPF program that never touches a kernel struct still need BTF on the target?Not for relocations — a program that only uses its context arguments and helpers has nothing to relocate and can load on a kernel without BTF. But some features are BTF-dependent regardless, notably `fentry`/`fexit`-style attachment and BTF-annotated verifier output, so "no BTF" still narrows what you can write.
- Why does the BTF you supply externally have to match the exact kernel build rather than the version number?Because struct layouts follow the build's configuration, not its version string. A vendor respin that enables one extra option can shift members inside `task_struct`, so BTF from a different build of the same version yields wrong offsets — and the failure is silent, because relocation succeeds against a plausible but incorrect description.
saying these in an interview costs you the question
- Concluding the kernel is too old to run eBPF at all
- Thinking BTF is generated when the program is loaded
- Reusing another host's BTF because the version string matches
- Believing kernel headers can substitute for BTF in a CO-RE binary
- Assuming every kernel exposes /sys/kernel/btf/vmlinux