Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BPF Type Format (BTF)

Introduction

BPF Type Format (BTF) is a compact, kernel-embedded metadata format that describes C data structures, functions, and types used by eBPF programs. Introduced in Linux 5.2 (2019), BTF is the foundation of CO-RE (Compile Once — Run Everywhere), which allows eBPF programs to run across different kernel versions without recompilation.

Key properties:

  • Compact encoding — minimal overhead for type information
  • Kernel-embedded/sys/kernel/btf/vmlinux contains all kernel type info
  • CO-RE support — enables portable eBPF programs
  • Lightweight — typically 1-3 MB for full kernel type info
  • Self-describing — BTF describes its own format

Why BTF Matters

Before BTF, eBPF programs had to be compiled against specific kernel headers, making them version-dependent:

graph LR
    subgraph "Without BTF (pre-5.2)"
        OLD1["eBPF compiled<br>on kernel 5.4"] -->|"❌"| RUN1["Crashes on<br>kernel 5.10"]
        OLD2["eBPF compiled<br>on kernel 5.10"] -->|"❌"| RUN2["Crashes on<br>kernel 5.4"]
    end
    subgraph "With BTF + CO-RE"
        NEW["eBPF compiled<br>once (BTF)"] -->|"✅"| R1["Runs on 5.4"]
        NEW -->|"✅"| R2["Runs on 5.10"]
        NEW -->|"✅"| R3["Runs on 6.1"]
    end

    style OLD1 fill:#e53e3e,color:#fff
    style NEW fill:#38a169,color:#fff

BTF Data Format

BTF Kinds

BTF encodes type information as a sequence of type records:

KindIDDescription
BTF_KIND_UNKN0Unknown/void
BTF_KIND_INT1Integer types
BTF_KIND_PTR2Pointer types
BTF_KIND_ARRAY3Array types
BTF_KIND_STRUCT4Structures
BTF_KIND_UNION5Unions
BTF_KIND_ENUM6Enumerations
BTF_KIND_FWD7Forward declarations
BTF_KIND_TYPEDEF8Type aliases
BTF_KIND_VOLATILE9Volatile qualifier
BTF_KIND_CONST10Const qualifier
BTF_KIND_RESTRICT11Restrict qualifier
BTF_KIND_FUNC12Functions
BTF_KIND_FUNC_PROTO13Function prototypes
BTF_KIND_VAR14Variables
BTF_KIND_DATASEC15Data sections
BTF_KIND_FLOAT16Floating point
BTF_KIND_DECL_TAG17Declaration tags
BTF_KIND_TYPE_TAG18Type tags

BTF Header Structure

struct btf_header {
    __u16   magic;          /* 0xEB9F (reversed "BPF") */
    __u8    version;        /* BTF version (1) */
    __u8    flags;          /* BTF flags */
    __u32   hdr_len;        /* Header length */
    /* All following are offsets from end of header */
    __u32   type_off;       /* Type section offset */
    __u32   type_len;       /* Type section length */
    __u32   str_off;        /* String section offset */
    __u32   str_len;        /* String section length */
};

Example: BTF Type Encoding

// Original C code:
struct task_struct {
    int pid;
    char comm[16];
    struct list_head tasks;
};

// BTF encoding:
// Type 1: BTF_KIND_INT "int" size=4 encoding=SIGNED
// Type 2: BTF_KIND_ARRAY type=1 nelems=16  → char[16]
// Type 3: BTF_KIND_STRUCT "list_head" size=16 vlen=2
// Type 4: BTF_KIND_STRUCT "task_struct" size=... vlen=3
//   member: "pid"    type=1 offset=0
//   member: "comm"   type=2 offset=32
//   member: "tasks"  type=3 offset=...

Accessing BTF Information

Kernel BTF

# Check if kernel has BTF
ls -la /sys/kernel/btf/vmlinux

# Dump kernel BTF as text
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# List all types matching a pattern
bpftool btf dump file /sys/kernel/btf/vmlinux | grep "struct task_struct"

# Show type info for a specific struct
bpftool btf dump file /sys/kernel/btf/vmlinux format c | \
    sed -n '/^struct task_struct {/,/^};/p'

# Get BTF size
ls -lh /sys/kernel/btf/vmlinux

BTF for Modules

# Module BTFs are in /sys/kernel/btf/<module>
ls /sys/kernel/btf/ | head -20

# Dump module BTF
bpftool btf dump file /sys/kernel/btf/nf_conntrack format c

# List all BTF objects
bpftool btf list

Generating vmlinux.h

# Generate minimal vmlinux.h from kernel BTF
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# Check size (typically 1-3 MB)
wc -l vmlinux.h

# Generate for specific types only
bpftool btf dump file /sys/kernel/btf/vmlinux format c | \
    grep -A 50 'struct task_struct {' > task_struct.h

CO-RE: Compile Once — Run Everywhere

How CO-RE Works

flowchart TD
    SRC["eBPF C Source<br>(uses kernel types)"] --> COMPILE["Clang + BTF<br>compile"]
    COMPILE --> BPF[".bpf.o<br>(with CO-RE relocations)"]
    BPF --> LOAD["libbpf loader"]
    LOAD --> BTF_CHECK["Read kernel BTF<br>(/sys/kernel/btf/vmlinux)"]
    BTF_CHECK --> RELOC["Apply CO-RE relocations<br>(adjust offsets/sizes)"]
    RELOC --> RUN["Run on any kernel<br>with BTF enabled"]

    style SRC fill:#3182ce,color:#fff
    style RELOC fill:#e53e3e,color:#fff
    style RUN fill:#38a169,color:#fff

CO-RE Relocation Types

RelocationDescription
FIELD_BYTE_OFFSETField offset in struct (handles layout changes)
FIELD_BYTE_SIZEField size (handles type changes)
FIELD_EXISTSCheck if field exists in target kernel
FIELD_SIGNEDWhether field is signed
FIELD_LSHIFT_U64Left shift for bitfield extraction
FIELD_RSHIFT_U64Right shift for bitfield extraction
TYPE_ID_LOCALLocal type ID
TYPE_ID_TARGETTarget kernel type ID
TYPE_EXISTSCheck if type exists in target kernel
TYPE_SIZESize of type in target kernel

CO-RE Example

// my_ebpf.c — Portable eBPF program using CO-RE
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_core_read.h>

// Use CO-RE to read task_struct fields
SEC("tracepoint/sched/sched_process_exec")
int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
{
    struct task_struct *task = (void *)bpf_get_current_task();

    // CO-RE: reads field offset from kernel BTF at load time
    int pid = BPF_CORE_READ(task, pid);

    // CO-RE: check if field exists (handles kernel version differences)
    if (bpf_core_field_exists(task->real_parent)) {
        struct task_struct *parent = BPF_CORE_READ(task, real_parent);
        int ppid = BPF_CORE_READ(parent, pid);
        bpf_printk("exec: pid=%d ppid=%d\n", pid, ppid);
    }

    return 0;
}

char LICENSE[] SEC("license") = "GPL";

Compiling with CO-RE

# Compile eBPF program with BTF and CO-RE
clang -g -O2 -target bpf \
    -D__TARGET_ARCH_x86 \
    -I/usr/include/bpf \
    -c my_ebpf.c -o my_ebpf.bpf.o

# Verify BTF is embedded
bpftool btf dump file my_ebpf.bpf.o

# Load and attach
sudo bpftool prog load my_ebpf.bpf.o /sys/fs/bpf/my_prog

CO-RE Helper Macros

#include <bpf/bpf_core_read.h>

// Read a field (handles relocation)
BPF_CORE_READ(task, comm);

// Read nested fields
BPF_CORE_READ(task, mm, start_code);

// Read into a local variable (safer for complex types)
struct mm_struct *mm;
bpf_core_read(&mm, sizeof(mm), &task->mm);

// Check if a field exists in the running kernel
if (bpf_core_field_exists(task->signal)) { ... }

// Get the size of a type in the running kernel
int task_size = bpf_core_type_size(struct task_struct);

// Check if a type exists
if (bpf_core_type_exists(struct cgroup_subsys_state)) { ... }

// Read enum value (handles value changes between kernel versions)
int state = BPF_CORE_READ(task, __state);

BTF for Map Definitions

BTF enables typed BPF maps with automatic key/value type information:

// Typed BPF map with BTF
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, __u32);        // BTF-infers key type
    __type(value, struct event); // BTF-infers value type
} events SEC(".maps");

// The map automatically has BTF information
// bpftool can dump the map's key/value types
# View map with BTF type info
sudo bpftool map dump id 123

# Output shows typed fields, not just raw bytes
# key: 42
# value: { pid: 1234, comm: "bash", duration_ns: 123456 }

BTF Generation

From Kernel Config

# Ensure kernel is compiled with BTF
# CONFIG_DEBUG_INFO_BTF=y

# Rebuild kernel BTF (if missing)
sudo pahole --btf_encode_force -j /usr/lib/debug/boot/vmlinux-$(uname -r)

From Custom Code

# Generate BTF from C source
clang -g -O2 -target bpf -c my_prog.c -o my_prog.bpf.o
# BTF is automatically embedded by clang with -g

# Extract BTF from compiled object
bpftool btf dump file my_prog.bpf.o

# Generate standalone BTF file
bpftool btf dump file my_prog.bpf.o > my_prog.btf

BTF from Scratch (for userspace)

// Generate BTF programmatically using libbpf
#include <bpf/btf.h>

struct btf *btf = btf__new_empty();
int id;

// Add a struct type
id = btf__add_struct(btf, "my_struct", 16);
btf__add_field(btf, "x", btf__add_int(btf, "int", 4, 0), 0);
btf__add_field(btf, "y", btf__add_int(btf, "int", 4, 0), 32);

// Use BTF for map creation
union bpf_attr attr = {
    .map_type = BPF_MAP_TYPE_HASH,
    .key_size = 4,
    .value_size = 16,
    .max_entries = 1024,
    .btf_fd = btf__fd(btf),
    .btf_key_type_id = btf__add_int(btf, "int", 4, 0),
    .btf_value_type_id = id,
};
int map_fd = bpf(BPF_MAP_CREATE, &attr, sizeof(attr));

bpftool BTF Commands

# List all BTF objects
bpftool btf list

# Show BTF details
bpftool btf show id 1

# Dump BTF as C header
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# Dump BTF as raw
bpftool btf dump file /sys/kernel/btf/vmlinux format raw

# Search for specific type
bpftool btf dump file /sys/kernel/btf/vmlinux | grep -A 10 "struct cred {"

# Pin BTF to bpffs
bpftool btf pin id 1 /sys/fs/btf/my_btf

# Compare BTF between kernel versions
bpftool btf dump file /sys/kernel/btf/vmlinux format c | \
    diff - old_vmlinux.h

BTF and BPF CO-RE in Practice

Monitoring with libbpf-bootstrap

# Clone libbpf-bootstrap for examples
git clone https://github.com/libbpf/libbpf-bootstrap
cd libbpf-bootstrap/examples/c

# Build minimal example with CO-RE
make minimal

# Run
sudo ./minimal

CO-RE Example: File Opens Tracer

// file_open.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>

SEC("fentry/do_sys_openat2")
int BPF_PROG(trace_open, int dfd, const char *filename)
{
    char buf[256];
    bpf_probe_read_user_str(buf, sizeof(buf), filename);
    bpf_printk("open: %s\n", buf);
    return 0;
}

char LICENSE[] SEC("license") = "GPL";
# Compile with CO-RE
clang -g -O2 -target bpf -D__TARGET_ARCH_x86 \
    -c file_open.bpf.o -o file_open.bpf.o

# Load and run
sudo bpftool prog load file_open.bpf.o /sys/fs/bpf/file_open

# Read output
sudo cat /sys/kernel/debug/tracing/trace_pipe

BTF for Kernel Debugging

Examining Kernel Structures

# Get struct layout (offsets, sizes)
bpftool btf dump file /sys/kernel/btf/vmlinux format c | \
    awk '/^struct task_struct \{/,/^\};/' | head -100

# Check specific field offset
# Useful for debugging eBPF programs that read kernel data
bpftool btf dump file /sys/kernel/btf/vmlinux format c | \
    grep -A 2 "struct task_struct" | grep "pid"

# Get all function prototypes
bpftool btf dump file /sys/kernel/btf/vmlinux | \
    grep "FUNC " | head -20

BTF and pahole

# pahole shows struct layouts from BTF/DWARF
pahole -C task_struct /sys/kernel/btf/vmlinux

# Show holes/padding in structs
pahole --holes 1 -C task_struct /sys/kernel/btf/vmlinux

# Show all structures sorted by size
pahole --sizes /sys/kernel/btf/vmlinux | sort -k2 -rn | head -20

BTF Overhead and Optimization

BTF Size

ComponentTypical Size
Kernel vmlinux BTF1-3 MB
Module BTF (each)10-100 KB
eBPF program BTF1-10 KB
BTF for maps0.1-1 KB

Kernel Configuration

# Enable BTF in kernel
CONFIG_DEBUG_INFO_BTF=y          # Generate BTF for vmlinux
CONFIG_DEBUG_INFO_BTF_MODULES=y  # Generate BTF for modules

# BTF is generated during kernel build using pahole
# Requires pahole >= 1.16

# Check if running kernel has BTF
[ -f /sys/kernel/btf/vmlinux ] && echo "BTF available" || echo "No BTF"

Troubleshooting

SymptomCauseSolution
“CO-RE relocations failed”Missing kernel BTFEnable CONFIG_DEBUG_INFO_BTF
“vmlinux.h: no such file”Not generatedRun bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
“Field not found”Struct layout mismatchUse BPF_CORE_READ instead of direct access
“BTF ID not found”Module BTF not loadedCheck /sys/kernel/btf/<module>
Large vmlinux.hIncludes all typesKeep only needed types
“Type not compatible”CO-RE type mismatchUse bpf_core_type_exists() check

BTF Specification Details (from kernel docs)

The following details are drawn from the official BPF Type Format (BTF) kernel documentation.

BTF Header

The BTF data blob begins with:

struct btf_header {
    __u16 magic;       /* 0xEB9F — different encoding for big/little endian */
    __u8  version;     /* BTF version (currently 1) */
    __u8  flags;
    __u32 hdr_len;     /* sizeof(struct btf_header) when generated */
    /* All offsets are in bytes relative to the end of this header */
    __u32 type_off;    /* offset of type section */
    __u32 type_len;    /* length of type section */
    __u32 str_off;     /* offset of string section */
    __u32 str_len;     /* length of string section */
};

The magic 0xEB9F has different encoding for big and little endian systems, and can be used to test whether BTF is generated for a big- or little-endian target. The btf_header is designed to be extensible with hdr_len equal to sizeof(struct btf_header) when a data blob is generated.

String Encoding

The first string in the string section must be a null string. The rest of the string table is a concatenation of null-terminated strings.

Type Encoding

Type ID 0 is reserved for the void type. The type section is parsed sequentially and type IDs are assigned starting from 1. Each type contains:

struct btf_type {
    __u32 name_off;
    /* "info" bits arrangement:
     *   bits 0-23:  vlen (e.g. # of struct members)
     *   bits 24-30: kind (e.g. int, ptr, array...)
     *   bit 31:     kind_flag (used by struct, union, enum, fwd, enum64,
     *                           decl_tag and type_tag)
     */
    __u32 info;
    /* "size" is used by INT, ENUM, STRUCT, UNION and ENUM64.
     * "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
     *         FUNC, FUNC_PROTO, DECL_TAG and TYPE_TAG.
     */
    union {
        __u32 size;
        __u32 type;
    };
};

Integer Encoding

BTF_KIND_INT is followed by a u32 with:

  • BTF_INT_ENCODING(VAL) — attributes: BTF_INT_SIGNED, BTF_INT_CHAR, BTF_INT_BOOL
  • BTF_INT_OFFSET(VAL) — starting bit offset for value calculation
  • BTF_INT_BITS(VAL) — actual number of bits (max 128)

btf_type.size * 8 must be ≥ BTF_INT_BITS().

Struct/Union Members

struct btf_member {
    __u32 name_off;
    __u32 type;
    __u32 offset;  /* bit offset; if kind_flag set, also contains bitfield size */
};

When kind_flag is set, the offset contains both bitfield size and bit offset:

#define BTF_MEMBER_BITFIELD_SIZE(val) ((val) >> 24)
#define BTF_MEMBER_BIT_OFFSET(val)    ((val) & 0xffffff)

Function and Function Prototype

  • BTF_KIND_FUNC (12): Represents a defined subprogram (not a type). Links to a BTF_KIND_FUNC_PROTO.
  • BTF_KIND_FUNC_PROTO (13): Function prototype with parameter types and return type.

Variables and Data Sections

  • BTF_KIND_VAR (14): Global variable with type and linkage.
  • BTF_KIND_DATASEC (15): Section containing variables (maps to ELF sections).

Additional Kinds

  • BTF_KIND_FLOAT (16): Floating point types
  • BTF_KIND_DECL_TAG (17): Declaration tags (annotations on declarations)
  • BTF_KIND_TYPE_TAG (18): Type tags (annotations on types)
  • BTF_KIND_ENUM64 (19): Enumeration up to 64-bit values

Further Reading

See Also