BTF: The Type Format That Makes eBPF Portable

Quick Reference:
# Check if kernel has BTF support
ls -la /sys/kernel/btf/vmlinux
# Generate BTF from DWARF debug info
pahole --btf_encode_detached vmlinux.btf vmlinux
# Inspect BTF contents
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# List loaded BTF objects
bpftool btf list
eBPF programs need to read kernel data structures, but those structures change between kernel versions. A field at offset 8 in kernel 5.10 might be at offset 16 in kernel 6.1. How do you write eBPF programs that work across versions?
The answer is BTF (BPF Type Format), a compact debug information format that enables eBPF portability through a mechanism called CO-RE (Compile Once, Run Everywhere).
The Problem: Kernel Structure Volatility
Consider reading a process's PID from an eBPF program:
// This breaks when task_struct layout changes
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
int pid = task->pid; // What's the offset of pid?
The offset of pid within task_struct varies by:
- Kernel version
- Kernel configuration (CONFIG options)
- Architecture (x86 vs ARM)
- Distribution patches
Without type information, you'd need to compile your eBPF program for each specific kernel.
What is BTF?
BTF is a compact format for encoding C type information. Think of it as a lightweight alternative to DWARF debug info, specifically designed for BPF.
BTF encodes:
- Types: integers, pointers, arrays, structs, unions, enums
- Functions: function signatures and prototypes
- Variables: global variables and their types
- Line info: source file and line number mappings
BTF vs DWARF
| Aspect | DWARF | BTF |
|---|---|---|
| Size | ~100MB for vmlinux | ~5MB for vmlinux |
| Purpose | Full debugging | BPF type info |
| Complexity | Very complex | Relatively simple |
| Runtime use | Not designed for | Kernel loads it |
BTF is roughly 20x smaller than DWARF while containing the essential type information BPF needs.
BTF Structure
A BTF blob contains:
struct btf_header {
__u16 magic; // 0xeB9F
__u8 version;
__u8 flags;
__u32 hdr_len;
__u32 type_off; // Offset to type section
__u32 type_len; // Length of type section
__u32 str_off; // Offset to string section
__u32 str_len; // Length of string section
};
Type Kinds
BTF supports these type kinds:
| Kind | Value | Description |
|---|---|---|
| BTF_KIND_INT | 1 | Integer types |
| BTF_KIND_PTR | 2 | Pointer types |
| BTF_KIND_ARRAY | 3 | Array types |
| BTF_KIND_STRUCT | 4 | Struct types |
| BTF_KIND_UNION | 5 | Union types |
| BTF_KIND_ENUM | 6 | Enum types (32-bit) |
| BTF_KIND_FWD | 7 | Forward declarations |
| BTF_KIND_TYPEDEF | 8 | Typedef |
| BTF_KIND_FUNC | 12 | Function definitions |
| BTF_KIND_FUNC_PROTO | 13 | Function prototypes |
| BTF_KIND_VAR | 14 | Variables |
| BTF_KIND_DATASEC | 15 | Data sections |
| BTF_KIND_ENUM64 | 19 | 64-bit enums |
How the Kernel Uses BTF
The kernel exposes its BTF at /sys/kernel/btf/vmlinux:
# Check BTF availability
ls -la /sys/kernel/btf/vmlinux
# -r--r--r-- 1 root root 5765939 Mar 1 00:00 /sys/kernel/btf/vmlinux
# Check kernel config
zcat /proc/config.gz | grep CONFIG_DEBUG_INFO_BTF
# CONFIG_DEBUG_INFO_BTF=y
When you load a BPF program, the kernel verifier uses BTF to:
- Validate memory access: Ensure your program only accesses valid struct fields
- Enable map pretty-printing: Display map contents with field names
- Generate better errors: Show type mismatches during verification
- Support CO-RE: Relocate field accesses at load time
CO-RE: Compile Once, Run Everywhere
CO-RE is the killer feature enabled by BTF. Here's how it works:
Without CO-RE (the old way)
// Hardcoded offset - breaks on different kernels
int pid = *(int *)((char *)task + 1234);
With CO-RE
// libbpf resolves the correct offset at load time
int pid = BPF_CORE_READ(task, pid);
The magic happens through relocations:
- Compile time: The compiler records which fields you're accessing
- Load time: libbpf reads the target kernel's BTF
- Relocation: libbpf patches your program with correct offsets
CO-RE Helpers
libbpf provides CO-RE helper macros:
// Read a field (handles pointer chasing)
BPF_CORE_READ(task, pid)
// Read nested fields
BPF_CORE_READ(task, mm, pgd)
// Check if field exists
bpf_core_field_exists(task->pid)
// Get field size
bpf_core_field_size(task->pid)
// Check enum value existence
bpf_core_enum_value_exists(enum_type, ENUM_VALUE)
Generating BTF
From Kernel Build
Enable in kernel config:
CONFIG_DEBUG_INFO=y
CONFIG_DEBUG_INFO_BTF=y
The kernel build generates /sys/kernel/btf/vmlinux automatically.
Using pahole
Convert DWARF to BTF:
# Install pahole (dwarves package)
apt install dwarves # Debian/Ubuntu
dnf install dwarves # Fedora/RHEL
# Generate BTF from vmlinux with DWARF
pahole --btf_encode_detached vmlinux.btf vmlinux
Generate vmlinux.h
Create a header with all kernel types:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
This 200K+ line header contains every kernel type, letting you write eBPF programs without kernel headers:
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>
SEC("kprobe/do_sys_open")
int trace_open(struct pt_regs *ctx) {
struct task_struct *task = (void *)bpf_get_current_task();
int pid = BPF_CORE_READ(task, pid);
// ...
}
Inspecting BTF
bpftool
# List all BTF objects
bpftool btf list
# Dump kernel BTF
bpftool btf dump file /sys/kernel/btf/vmlinux
# Dump specific type
bpftool btf dump file /sys/kernel/btf/vmlinux format c | grep -A 20 "struct task_struct {"
# Dump BPF program's BTF
bpftool prog dump xlated id 42
btf_dump
Programmatic BTF inspection:
#include <bpf/btf.h>
struct btf *btf = btf__load_vmlinux_btf();
int type_id = btf__find_by_name(btf, "task_struct");
const struct btf_type *t = btf__type_by_id(btf, type_id);
BTF for BPF Maps
BTF enables typed maps with pretty-printing:
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, __u32);
__type(value, struct event_data);
} events SEC(".maps");
With BTF, bpftool map dump shows field names:
bpftool map dump name events
# [{
# "key": 12345,
# "value": {
# "pid": 1234,
# "comm": "nginx",
# "timestamp": 1234567890
# }
# }]
Kernel Module BTF
Kernel modules can have their own BTF:
# List module BTF
ls /sys/kernel/btf/
# vmlinux nf_conntrack ext4 ...
# Modules reference vmlinux BTF for base types
bpftool btf dump file /sys/kernel/btf/ext4
Common Issues
BTF not available
# Check kernel config
zcat /proc/config.gz | grep BTF
# CONFIG_DEBUG_INFO_BTF=y required
# Check if file exists
ls /sys/kernel/btf/vmlinux || echo "BTF not available"
Solutions:
- Use a kernel with BTF enabled
- Use BTF from BTFHub for older kernels
BTFHub for Older Kernels
BTFHub provides pre-generated BTF for kernels without built-in BTF:
# Download BTF for specific kernel
wget https://github.com/aquasecurity/btfhub-archive/raw/main/ubuntu/20.04/x86_64/5.4.0-42-generic.btf
Field not found
CO-RE relocation fails if a field doesn't exist:
// Guard with field_exists
if (bpf_core_field_exists(task->loginuid)) {
uid = BPF_CORE_READ(task, loginuid);
}
BTF in Practice
libbpf Skeleton
Modern libbpf workflow with BTF:
# Compile BPF program
clang -g -O2 -target bpf -c prog.bpf.c -o prog.bpf.o
# Generate skeleton
bpftool gen skeleton prog.bpf.o > prog.skel.h
# Use in userspace
#include "prog.skel.h"
struct prog_bpf *skel = prog_bpf__open_and_load();
prog_bpf__attach(skel);
Checking CO-RE Relocations
# Show relocations in BPF object
llvm-readelf -r prog.bpf.o
# Or with bpftool
bpftool prog dump xlated name my_prog
Requirements
| Component | Minimum Version |
|---|---|
| Kernel | 4.18+ (basic BTF), 5.2+ (CO-RE) |
| Clang/LLVM | 10+ |
| pahole | 1.16+ |
| libbpf | 0.0.6+ |
| bpftool | 5.2+ |
Conclusion
BTF transformed eBPF from a kernel-version-specific technology to a truly portable observability platform. By encoding type information compactly and enabling CO-RE relocations, BTF lets you write eBPF programs once and run them across kernel versions without recompilation.
For any serious eBPF development, understanding BTF is essential. It's the foundation that makes tools like Cilium, Falco, and bpftrace work reliably across diverse Linux environments.
Building observability tools with eBPF? Akmatori AI agents can help automate eBPF program deployment, analyze performance data, and respond to detected issues.
