Skip to main content
01.03.2026

BTF: The Type Format That Makes eBPF Portable

BTF and eBPF

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:

  1. Validate memory access: Ensure your program only accesses valid struct fields
  2. Enable map pretty-printing: Display map contents with field names
  3. Generate better errors: Show type mismatches during verification
  4. 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:

  1. Compile time: The compiler records which fields you're accessing
  2. Load time: libbpf reads the target kernel's BTF
  3. 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.

Automate incident response and prevent on-call burnout with AI-driven agents!