eBPF Overview

Extended Berkeley Packet Filter · Kernel-Level Programmability · Linux 3.15+
BCC · libbpf CO-RE · ebpf-go · aya · bpftrace // verifier · maps · hooks // companion to the bpf-fun lab tools

What eBPF Actually Is

eBPF is a sandboxed virtual machine inside the Linux kernel. It lets you run custom bytecode at kernel hook points without modifying kernel source or loading kernel modules. Programs are verified at load time for safety (no unbounded loops, no invalid memory access), then JIT-compiled to native machine code.

The "extended" part matters: classic BPF (cBPF, 1992) was a simple packet filter with 2 registers and 32-bit ops. eBPF (2014+) is a general-purpose in-kernel execution engine with 11 registers (R0-R10), 64-bit ops, maps (key-value stores), helper functions, kfuncs, and tail calls.

Programs attach to hook points: kprobes (any kernel function), uprobes (any userspace function), tracepoints (stable kernel instrumentation), XDP (network driver ingress), tc (traffic control), cgroup hooks, LSM hooks, and since Linux 6.12 even the CPU scheduler itself. Each hook type defines what context the program receives and what it can do.

Why this matters Before eBPF, custom kernel instrumentation meant writing a kernel module (dangerous, version-specific, can crash the system) or patching and recompiling the kernel. eBPF gives you the same reach with safety guarantees enforced by the verifier. It is now the foundation of production networking (Cilium, Katran), observability (bpftrace, Pixie, Parca), and security tooling (Tetragon, Falco).
How to read this Each tab is independent. Concepts first: Pipeline and Verifier & Maps. Writing your first probe: BCC Anatomy plus the runnable scripts in Lab Tools. Fighting the verifier: Verifier Errors. Choosing a stack for real tooling: Toolchains. What changed since 2020: Modern eBPF.

Kernel Execution Pipeline

Every eBPF program, regardless of frontend, travels the same road: source to bytecode, bytecode through the bpf() syscall, past the verifier, through the JIT, onto a hook.

USERSPACE
  BCC Python/Lua    bpftrace       libbpf CO-RE / ebpf-go / aya
       |                 |                   |
   C source        DSL script        pre-compiled .o (BTF-relocated)
       |                 |                   |
       v                 v                   v
  LLVM/Clang compiles to BPF bytecode   (or loaded directly)
       |
------ bpf() syscall --------------------------------------------
       |
KERNEL
       v
  VERIFIER   rejects unsafe programs: bounds checks, termination
       |        proof, stack <= 512 bytes, no wild pointer derefs *
       v
  JIT COMPILER   bytecode -> native (x86_64, arm64, riscv64, ...)
       |
       v
  ATTACH to hook point (kprobe, tracepoint, XDP, LSM, ...)
       |
       v
  BPF MAPS <-- shared state between kernel prog & userspace
  (hash, array, ringbuf, perf_event, LRU, stack_trace, arena, ...)
       |
       v
  Userspace reads maps / ring buffer / perf buffer for output

* bounded loops allowed since 5.3 (verifier proves termination);
  bpf_loop since 5.17, open-coded iterators since 6.4

The six steps

  1. Write the program C for BCC/libbpf, a DSL for bpftrace, Rust for aya. The program declares its hook type, maps, and logic.
  2. Compile to BPF bytecode LLVM's BPF backend (clang -target bpf). BCC does this at runtime on the target machine; libbpf-style toolchains do it once at build time.
  3. Load via the bpf() syscall Bytecode, map definitions, and BTF type info enter the kernel. Requires CAP_BPF (or root; delegable via BPF token since 6.9).
  4. Verification The verifier walks all code paths and proves the program is safe. No proof, no load. This is the gate everything else depends on.
  5. JIT compilation Accepted bytecode becomes native machine code. Effectively free at runtime; enabled by default on all major architectures.
  6. Attach The program is bound to its hook point and starts firing. Maps carry data back to userspace.

Verifier & Maps

The BPF verifier

Every BPF program passes through the verifier before execution. It walks all possible code paths and proves:

  1. No unreachable instructions
  2. No out-of-bounds or unchecked memory access
  3. All paths terminate (loops must be provably bounded)
  4. Stack usage <= 512 bytes per frame
  5. Only helpers/kfuncs allowed for the program type are called
  6. R0 holds a valid return value before exit
  7. Map lookup results are null-checked before dereference

The complexity budget is 1 million verified instructions per program (since 5.2; it was 4096 in the early days). The verifier is your biggest adversary when writing BPF C: R1 invalid mem access 'scalar' means you dereferenced a raw kernel pointer without bpf_probe_read_kernel(). In kprobe context, every struct field read from a kernel pointer needs an explicit probe read (BCC rewrites most of them for you; see BCC Anatomy).

BPF maps

Maps are the shared data structures between kernel-side BPF programs and userspace. They persist across program invocations and are the primary mechanism for collecting data.

Map type What it is Notes
BPF_MAP_TYPE_HASHGeneral key/value storeThe workhorse. BPF_HASH() in BCC.
BPF_MAP_TYPE_ARRAYFixed-size, indexed by u32Preallocated, fastest lookup.
BPF_MAP_TYPE_PERF_EVENT_ARRAYPer-CPU event stream to userspaceBPF_PERF_OUTPUT() in BCC. What the lab tools use.
BPF_MAP_TYPE_RINGBUFSingle shared ring buffer5.8+. Preferred over perf buffers: lower overhead, ordered, no per-CPU sizing.
BPF_MAP_TYPE_LRU_HASHHash with automatic evictionFor caches that must not fill up.
BPF_MAP_TYPE_PERCPU_HASH / _ARRAYPer-CPU copies, lock-free writesAggregate in userspace. Counters at high event rates.
BPF_MAP_TYPE_STACK_TRACECall stack captureFeeds flame graphs.
BPF_MAP_TYPE_PROG_ARRAYProgram referencesTail calls: one BPF program jumps to another.
BPF_MAP_TYPE_ARENASparse shared memory region6.9+. Kernel and userspace share pointers into one address space. See Modern eBPF.
Perf buffer vs ring buffer The BCC examples in this repo use perf buffers (BPF_PERF_OUTPUT + open_perf_buffer) because the BCC Python API for them is mature and simple. New code targeting 5.8+ should default to BPF_MAP_TYPE_RINGBUF: one buffer instead of one per CPU, events arrive in order, and memory is reserved in-place instead of copied twice.

Where BPF Programs Attach

The hook point determines the program type, the context struct it receives, and which helpers it may call. Three broad families: tracing, networking, security.

Tracing

Hook Reaches Notes
kprobe / kretprobeAny kernel function entry/returnMaximum reach, zero stability guarantees.
uprobe / uretprobeAny userspace function entry/returnHook libc, your own binaries, interpreters. Multi-attach since 6.6.
tracepointStable kernel instrumentation pointsSurvives kernel upgrades. Listed in /sys/kernel/debug/tracing/events.
raw_tracepointSame points, raw argumentsLower overhead, no argument casting done for you.
fentry / fexitAny kernel function via BPF trampoline5.5+. BTF-typed args, much lower overhead than kprobes. Modern default where available.
perf_eventPMC sampling, timersCPU profiling: sample stacks N times per second.
kprobe vs tracepoint kprobes can hook ANY kernel function, but the function may be renamed, inlined, or change signature between kernel versions. Tracepoints are a stable ABI but only exist where kernel developers placed them. Use tracepoints when one exists, kprobes (or fentry on modern kernels) when you need to reach deeper.

Networking

Hook Where Notes
XDPNetwork driver ingress, before skb allocationFastest path. Actions: PASS, DROP, TX, REDIRECT.
tc (clsact)Traffic control ingress/egressFull skb access, both directions. Cilium's main datapath.
sk_filterSocket-level filteringThe classic tcpdump attach point, extended.
sk_msg / sk_skbSockmap redirectSocket-to-socket splicing for service meshes.
cgroup/sock*Per-cgroup socket operationsPer-container network policy, connect/bind interposition.
struct_opsKernel subsystem callbacks5.6+. Entire TCP congestion control algorithms in BPF.
XDP performance XDP runs before the kernel allocates an skb (socket buffer), so it processes packets at line rate. Cloudflare and Meta (Katran) use it to absorb multi-Tbps DDoS attacks and do L4 load balancing on commodity hardware.

Security

Hook What it controls Notes
LSM (BPF LSM)Security module hooks5.7+. Enforce policy at the same points as SELinux/AppArmor, loaded dynamically.
cgroup/deviceDevice access per cgroupContainer device whitelisting.
cgroup/sysctlsysctl reads/writes per cgroupInterpose and deny.
LSM + eBPF, and a seccomp footnote BPF LSM lets you attach enforcement logic to hundreds of security hooks without rebooting or recompiling policy; Cilium's Tetragon builds its runtime security enforcement on this plus tracing hooks. Note that seccomp filters, despite the name "secure computing BPF", still run classic BPF (cBPF) bytecode, not eBPF; they are a related but separate mechanism.

BCC Anatomy

BCC (BPF Compiler Collection) provides a Python (and Lua) frontend for writing BPF programs. The kernel-side C is embedded as a string, compiled at runtime via LLVM, and loaded into the kernel. This is the fastest path from zero to working instrumentation, and it is what every tool in this repo uses.

Strengths Trade-offs
Rapid prototyping: write, run, iterate in secondsRequires LLVM + kernel headers on every target machine
Rich Python API for maps, perf buffers, formattingCompiles at runtime: slow startup, noticeable on small ARM boards
100+ battle-tested bundled tools (execsnoop, biolatency, ...)Programs are tied to the kernel they compiled against (no CO-RE)
Automatic struct offset resolution from kernel headersHeavy install, roughly 300 MB with LLVM
Where BCC sits in 2026 BCC's Python frontend remains the best learning and prototyping environment, which is why this guide teaches it. For tools you deploy, the ecosystem has moved to CO-RE: even the BCC project itself ships libbpf-tools, pre-compiled rewrites of its classic tools. See Toolchains.

A complete BCC program

BCC Python
#!/usr/bin/env python3
from bcc import BPF

# ---- kernel side (C) ----
prog = """
struct event_t {
    u32 pid;
    char comm[16];
    char fname[64];
    u64 size;
};

BPF_PERF_OUTPUT(events);              // declare perf buffer

int trace_read_entry(struct pt_regs *ctx,
                     struct file *file,
                     char __user *buf,
                     size_t count) {
    struct event_t evt = {};
    evt.pid = bpf_get_current_pid_tgid() >> 32;
    bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
    bpf_probe_read_kernel_str(&evt.fname, sizeof(evt.fname),
                               file->f_path.dentry->d_name.name);
    evt.size = count;
    events.perf_submit(ctx, &evt, sizeof(evt));
    return 0;
}
"""

# ---- userspace side (Python) ----
b = BPF(text=prog)                           # compile + load
b.attach_kprobe(event="vfs_read",
                fn_name="trace_read_entry") # attach to hook

def handle_event(cpu, data, size):         # callback for events
    evt = b["events"].event(data)
    print(f"{evt.pid}  {evt.comm}  {evt.fname}  {evt.size}")

b["events"].open_perf_buffer(handle_event)
while True:
    b.perf_buffer_poll()                     # blocks, calls handle_event
BCC's rewriter gotcha The C string is compiled by LLVM into BPF bytecode at runtime. BCC's rewriter automatically converts struct member access into probe-read calls, but it misses some patterns (like ntohs(sk->field) or reads through multiple pointer hops inside expressions). That is when you hit verifier errors and need an explicit bpf_probe_read_kernel().

The Lab Tools in This Repo

Every script in this repository is a runnable BCC program that demonstrates one or two of the concepts above. Run them with root (or CAP_BPF + CAP_PERFMON), roughly in this order.

Tool Hooks Maps What it teaches
01_hello_execsnoop.pykprobe on sys_execve arch wrapperperf bufferSmallest possible tracer: see every exec on the box.
02_syscall_counter.pytracepoint raw_syscalls:sys_enterhashIn-kernel aggregation: count instead of stream.
new_processes.pytracepoints sys_enter/exit_execvehash + perf bufferEntry/exit correlation to capture args and return value.
file_watcher.pytracepoints openat, read, writeperf bufferFiltering events per PID; watching one process's file activity.
file_walker.pynone (no BPF)noneWorkload generator: a predictable target for file_watcher.
io_scope.pytracepoints openat, read, writeperf bufferFull I/O profile of a process: files, bytes, rates.
slow_syscalls.pytracepoints raw_syscalls enter + exithash + perf bufferLatency measurement: timestamp on entry, delta on exit.
tcp_connect.pykprobe + kretprobe tcp_v4_connecthash + perf bufferkretprobe pattern: stash the socket on entry, read the result on return.
tcp_latency.pykprobes tcp_v4_connect, tcp_finish_connecthash + perf bufferHandshake RTT by correlating two different kernel functions.
conn_map.pykprobes tcp_sendmsg, tcp_cleanup_rbufhashPer-connection byte accounting in a keyed map.
net_scope.pyfive TCP kprobeshashes + 2 perf buffersConnection lifecycle: connect, latency, bytes, close, in one tool.
dns_snoop.pykprobe udp_sendmsgperf bufferReading packet payloads: parse DNS queries in flight.
port_scan_detect.pykprobe tcp_v4_conn_requestperf bufferSecurity angle: spot inbound SYN patterns that look like scans.
mem_scope.pytracepoints brk/mmap/munmap + kprobe handle_mm_faulthashes + perf bufferMemory behavior: allocations and page faults per process.
Architecture-specific syscall symbols Syscall entry points are wrapped per architecture since kernel 4.17: __x64_sys_execve on x86_64, __arm64_sys_execve on arm64. 01_hello_execsnoop.py attaches to the arm64 symbol; on an x86_64 machine change the attach line (or use b.get_syscall_fnname("execve"), which resolves it portably). The tracepoint-based tools are immune to this, which is exactly the kprobe-vs-tracepoint stability trade-off from Hook Points.

Reference Tables

BPF helpers you will actually use

Helper Purpose
bpf_get_current_pid_tgid()Returns u64: TGID (the userspace "PID") in the upper 32 bits, thread ID in the lower.
bpf_get_current_comm(buf, sz)Process name, 16 chars max.
bpf_ktime_get_ns()Monotonic nanoseconds. The clock for latency measurements.
bpf_probe_read_kernel(dst, sz, src)Safe kernel memory read. The fix for most verifier errors.
bpf_probe_read_user(dst, sz, src)Safe userspace memory read (syscall args, buffers).
bpf_probe_read_kernel_str() / _user_str()Read NUL-terminated strings safely.
bpf_map_lookup_elem(map, &key)Read from map. Returns pointer or NULL; the verifier forces you to check.
bpf_map_update_elem(map, &key, &val, flags)Write to map (BPF_ANY / BPF_NOEXIST / BPF_EXIST).
bpf_map_delete_elem(map, &key)Remove from map.
bpf_perf_event_output(ctx, map, flags, data, sz)Send an event to a perf buffer. perf_submit() in BCC.
bpf_ringbuf_reserve() / _submit()Zero-copy ring buffer output (5.8+). The modern replacement for the above.
bpf_trace_printk(fmt, ...)Debug only: slow, limited, output lands in /sys/kernel/debug/tracing/trace_pipe.

The old bpf_probe_read() still exists for compatibility but has been split into the _kernel/_user variants since 5.5; new code should always say which address space it means.

BCC macros and Python API

C side (embedded string)
BPF_HASH(name, key_t, val_t)   // hash map
BPF_ARRAY(name, val_t, max)    // array map
BPF_PERF_OUTPUT(name)          // perf buffer
BPF_PERCPU_ARRAY(name)         // per-cpu array
BPF_RINGBUF_OUTPUT(name, pgs)  // ring buffer
TRACEPOINT_PROBE(cat, name)    // tracepoint handler
Python side
b.attach_kprobe(event, fn_name)
b.attach_kretprobe(event, fn_name)
b.attach_uprobe(name, sym, fn_name)
b.get_syscall_fnname("execve")  # arch-portable
b["map"].open_perf_buffer(cb)
b.perf_buffer_poll()             # block + dispatch
b["map"][key]                    # direct map read

Common kprobe targets

Domain Function Fires on
File I/Ovfs_read / vfs_writeAll file reads/writes, any filesystem.
File I/Ovfs_openFile opens at the VFS layer.
File I/Odo_sys_openat2The openat syscall implementation.
Networktcp_v4_connect / tcp_v6_connectOutbound TCP connection attempts.
Networktcp_finish_connectHandshake completion.
Networktcp_sendmsg / tcp_cleanup_rbufTCP bytes out / bytes consumed in.
Networktcp_closeConnection teardown.
Networkudp_sendmsgUDP out (DNS lives here).
Process__x64_sys_execve / __arm64_sys_execveexec, per architecture (see Lab Tools).
Processdo_exitProcess exit.
Processwake_up_new_taskfork/clone: new task becomes runnable.
Memoryhandle_mm_faultPage faults.

Verifier Errors & Fixes

The verifier's error messages are terse and the line numbers refer to bytecode, not your C. These are the ones you will actually hit, and what they mean.

Error Cause Fix
R1 invalid mem access 'scalar'Dereferencing a raw kernel pointer directly.Read through bpf_probe_read_kernel().
BPF stack limit exceeded (512 bytes)Local struct too large for the BPF stack.Use a BPF_PERCPU_ARRAY of size 1 as scratch space, or bpf_ringbuf_reserve() and build the event in the ring buffer.
back-edge from insn X to YLoop the verifier cannot bound (always fatal before 5.3).#pragma unroll, a provably bounded loop on 5.3+, or bpf_loop() on 5.17+.
invalid indirect read from stackPassing uninitialized stack memory to a helper.Zero-initialize: struct foo bar = {};
map_value or possibly-null pointerUsing a bpf_map_lookup_elem() result without a NULL check.if (!val) return 0; immediately after the lookup.
BPF program is too large / processed N insnsBlew the 1M verified-instruction complexity budget.Simplify branches, bound loops tighter, split logic across tail calls.
btf_vmlinux is malformed / no BTF foundKernel BTF data missing.Kernel must be built with CONFIG_DEBUG_INFO_BTF=y (standard on modern distro kernels; check for /sys/kernel/btf/vmlinux).
Debugging strategy Read the last few lines of the verifier log first; the actual rejection reason is at the bottom. In BCC, run with the C macro shrunk to almost nothing, then add code back until it breaks. The verifier rejects programs, never crashes machines. Failing to load is the system working.

Toolchains Compared

Five realistic ways to write eBPF in 2026. The split that matters is runtime compilation (BCC) versus compile-once-run-everywhere (everything else, via BTF + CO-RE).

Stack Language Compile model Startup Portability Best for
BCCPython/Lua + embedded CLLVM on target, at runtimeSlowTied to build kernelLearning, prototyping, one-off investigation.
libbpf + CO-RECAhead of time, BTF relocationsInstantCross-kernelProduction tools, minimal footprint. The reference implementation.
ebpf-go (cilium/ebpf)Go userspace, C kernel sideAhead of time, bpf2go embeds bytecodeInstantCross-kernelGo services and infrastructure tooling. Powers Cilium.
ayaRust on both sidesAhead of time, pure-Rust loader (no libbpf)InstantCross-kernelRust shops; kernel + userspace in one language and one cargo build.
bpftraceawk-like DSLOn the fly (libbpf-based since 0.21)ModerateNeeds BTFOne-liners and ad-hoc tracing. See bpftrace.
Choosing Learning how eBPF works: BCC, because the iteration loop is seconds and the Python API keeps the ceremony low. Shipping a tool: libbpf (C), ebpf-go (Go), or aya (Rust) depending on the language of the surrounding project. Answering "what is this box doing right now": bpftrace, always.

ebpf-go: The Cilium Library

cilium/ebpf (ebpf-go) is the pure-Go library for loading and managing pre-compiled BPF programs. Write BPF C, compile it with clang to a .o at build time, then load, attach, and read maps from Go. No LLVM on the target machine; CO-RE + BTF handles cross-kernel portability.

workflow
# 1. write the kernel side
probe.bpf.c

# 2. generate Go bindings + embed bytecode (wraps clang -target bpf)
$ go generate ./...        # runs bpf2go

# 3. load and attach from Go
objs := probeObjects{}
loadProbeObjects(&objs, nil)
link.Kprobe("tcp_v4_connect", objs.TraceConnect, nil)

# 4. read events
rd, _ := ringbuf.NewReader(objs.Events)
record, _ := rd.Read()

This is what Cilium itself uses for Kubernetes networking, service mesh, and observability (Hubble), and what Cloudflare, Meta, and Datadog build production eBPF tooling with. If you are building an interactive eBPF tool in Go, this is the load-bearing dependency; the engine loads compiled programs, and bpf2go keeps the C and Go sides in sync at build time.

bpftrace One-Liners

bpftrace is the awk of kernel tracing: probes, predicates, and actions in a one-line DSL, compiled to eBPF under the hood. Since 0.21 it is built on libbpf and uses your kernel's BTF, so no headers are needed on a modern distro.

bpftrace
# count syscalls by process
$ bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'

# trace file opens with path
$ bpftrace -e 'tracepoint:syscalls:sys_enter_openat
  { printf("%s %s\n", comm, str(args.filename)); }'

# histogram of read() sizes
$ bpftrace -e 'tracepoint:syscalls:sys_exit_read /args.ret > 0/
  { @bytes = hist(args.ret); }'

# TCP connect with destination IP
$ bpftrace -e 'kprobe:tcp_v4_connect { printf("%s -> %s\n", comm,
    ntop(((struct sock *)arg0)->__sk_common.skc_daddr)); }'

# slow syscalls (>1ms)
$ bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @start[tid] = nsecs; }
  tracepoint:raw_syscalls:sys_exit /@start[tid]/
  { $d = nsecs - @start[tid];
    if ($d > 1000000) { printf("%s %dms\n", comm, $d / 1000000); }
    delete(@start[tid]); }'

# list every probe you could attach to
$ bpftrace -l 'tracepoint:syscalls:*'

Note the parallels: the slow-syscalls one-liner is slow_syscalls.py from Lab Tools in three lines, and the openat trace is the core of file_watcher.py. bpftrace is the fastest way to test a hypothesis before writing the full tool.

Modern eBPF: 5.x to 6.x

Everything earlier in this guide worked in 2020. This is what the kernel added since, roughly in order of appearance. If your kernel is 6.12 or newer (this repo's reference machine runs 6.17), all of it is available.

Feature Kernel What it unlocks
BPF trampoline, fentry/fexit5.5Function tracing with BTF-typed arguments at a fraction of kprobe cost.
struct_ops5.6Implement kernel subsystem callbacks in BPF; first user was TCP congestion control.
BPF LSM5.7Dynamic security policy on LSM hooks (the KRSI work).
Ring buffer5.8Ordered, low-overhead event stream replacing per-CPU perf buffers.
Sleepable programs5.10Programs that may fault-in user memory; required for many LSM uses.
bpf_loop()5.17Helper-driven loops with verifier-friendly bounds.
kfuncs5.18+Kernel functions exposed directly to BPF. The helper set is frozen; kfuncs are how new kernel functionality reaches BPF now.
uprobe multi-attach6.6Attach one program to thousands of userspace symbols cheaply.
BPF exceptions6.7bpf_throw(): assert invariants and bail out, instead of contorting code to satisfy the verifier.
BPF arena6.9Sparse memory region shared between BPF and userspace; build real data structures (trees, custom allocators) spanning both.
BPF token6.9Delegate scoped BPF privileges to unprivileged processes (containers) via the BPF filesystem, instead of granting CAP_BPF broadly.
sched_ext6.12Entire CPU scheduling policies as loadable BPF programs, with the kernel reverting to the default scheduler on any misbehavior.
The trajectory The pattern across these features: eBPF started as "safe observation" and is becoming "safe extension". struct_ops, kfuncs, and sched_ext let BPF programs implement kernel behavior, not just watch it. The verifier is what makes handing the CPU scheduler to a loadable program sane; the guarantees from Verifier & Maps are doing more work every release.

eBPF Evolution

Year Milestone Detail
1992BPF introducedSteven McCanne and Van Jacobson. Packet filter for BSD; 2 registers, 32-bit. The engine under tcpdump.
2014eBPF merged (Linux 3.15-3.18)Alexei Starovoitov, with Daniel Borkmann. 11 registers, 64-bit, maps, verifier, JIT; the bpf() syscall lands in 3.18.
2015kprobe attach + BCCBPF programs can attach to kernel functions (4.1). The BCC project starts at PLUMgrid.
2016XDP merged (4.8), Cilium appearsBPF at the network driver level. Thomas Graf and team start Cilium for container networking.
2018BTF (4.18)Type metadata in the kernel enables CO-RE: compile once, relocate at load time, run on any kernel.
2019Bounded loops (5.3), bpftrace 0.9The verifier learns to prove loop termination. High-level tracing goes mainstream.
2020fentry (5.5), LSM (5.7), ringbuf (5.8)Low-overhead tracing, dynamic security policy, and the modern event transport, all in one year.
2021eBPF Foundation, eBPF for WindowsMeta, Google, Isovalent, Microsoft, and Netflix found the eBPF Foundation under the Linux Foundation (August). Microsoft announces the eBPF-for-Windows runtime (May).
2022-23kfuncs era, exceptions (6.7)The helper interface freezes; kfuncs become the extension path. BPF exceptions land.
2024Arena + token (6.9), sched_ext (6.12)Shared memory with userspace, delegable privileges, and BPF-defined CPU scheduling. See Modern eBPF.

Further Rabbit Holes

Where to go once the lab tools feel small. Each of these leads to better questions.

Topic Why it matters Where to start
ebpf.io + docs.ebpf.io The ecosystem map and the best-organized reference for program types, helpers, and kfuncs. ebpf.io "What is eBPF", then docs.ebpf.io per program type
BPF Performance Tools Brendan Gregg's book. The methodology (USE, latency heat maps) matters as much as the tools. The book, plus the tool source in the BCC repo it documents
libbpf-bootstrap Minimal scaffolding for real CO-RE tools in C, Go, and Rust. The step after BCC. github.com/libbpf/libbpf-bootstrap
BPF features by kernel version The canonical table of which helper/map/program type landed when. Answers "can my target kernel do X". docs/kernel-versions.md in the BCC repo
Verifier internals Understanding register state tracking and pruning turns verifier errors from mysteries into compiler diagnostics. kernel Documentation/bpf/verifier.rst, LWN's BPF coverage
bpftool Inspect everything: loaded programs, maps, BTF, JIT output. The debugger you already have. bpftool prog list, bpftool prog dump xlated id N, bpftool btf dump file /sys/kernel/btf/vmlinux
sched_ext schedulers Real, running BPF CPU schedulers you can read, load, and break safely (the kernel falls back on error). github.com/sched-ext/scx
Tetragon & Falco Production security observability built on the tracing + LSM hooks from this guide. tetragon.io, falco.org
eunomia-bpf tutorials Worked examples for the newest features: arena, exceptions, tokens, GPU tracing experiments. eunomia.dev/tutorials
The meta-question to hold Every time an eBPF tool feels magical, ask: which hook fired, what context struct did the program receive, which map carried the data out. There is no hidden layer. Every tool in this repo, and Cilium itself, decomposes into programs, hooks, and maps. If you can name those three for a given tool, you understand it.
// verifier · maps · hooks · bcc · lab tools · toolchains · modern ebpf //