DEV Community

Cover image for What is eBPF, Really? A Builder's Mental Model
Corey Lai
Corey Lai

Posted on

What is eBPF, Really? A Builder's Mental Model

TL;DR: eBPF lets you run small, sandboxed programs inside the Linux kernel — to observe or change what the system is doing — without patching the kernel or modifying your apps. If you've ever run tcpdump, you've already used its ancestor.

The problem: you can't easily change the kernel, and a new capability can take years to land

There's a famous cartoon by Liz Rice that nails the old pain:

  • An app developer wants a new capability from the kernel.
  • The kernel maintainer says: "Sure — give me a year to convince the whole community."
  • A year later it's upstream. A few years after that, your distro finally ships it.
  • By then… your requirements have changed.

A four-panel cartoon: a developer asks for a new kernel feature, the maintainer says
Source: Learning eBPF — Liz Rice

The kernel sees almost everything that happens on the system — every syscall, every packet, every file operation. And it's open source: you can read the code in as much detail as you like. So the hard part was never seeing inside — it's changing it: safely growing the exact capability you need into a kernel that's already serving production traffic. Historically there were only two awkward paths. One: patch the kernel source, recompile, reboot — slow, risky, and you still have to convince upstream to take your patch. Two: bolt on a user-space agent that pokes from the outside by polling /proc and /sys — easy to install, but blind to first-hand events inside the kernel. eBPF is the third way.

What you'll learn

  • What eBPF actually is, in one sentence
  • Why it exists — told through its own history (tcpdump → today)
  • How it works in 30 seconds, and when to reach for it (and when not to)

A one-sentence mental model

eBPF is a safe, sandboxed virtual machine inside the Linux kernel that runs your tiny programs in response to events — a packet arrives, a syscall fires, a function is called — letting you observe or control the system without changing kernel source or your application.

It's a mechanism for safely loading kernel extensions at runtime. Every tiny program you attach is a hook bound to some event — but unlike the "write whatever you want" scripts in a browser, every eBPF program must first pass the kernel's built-in verifier: a static check that strictly bounds which instructions it may use, how many loop iterations it may run, and what memory it may touch. That constraint isn't red tape — it's the very thing that lets it run safely inside a live kernel.

Why it exists: from Classic BPF to eBPF

1992 — Classic BPF (you've already used it). BPF was born as a packet filter. Every time you run:

tcpdump -i eth0 tcp port 80
Enter fullscreen mode Exit fullscreen mode

…this happens under the hood:

  1. tcpdump hands your filter (tcp port 80) to libpcap
  2. libpcap compiles it into Classic BPF bytecode
  3. A syscall pushes that bytecode down into the kernel
  4. The kernel runs it at the network layer and only hands matching packets back up

That's the whole idea, already present in 1992: push a small program down into the kernel so the work happens there, not in your app. (You can even see the compiled bytecode with tcpdump -ddd.)

~2014 — extended BPF (eBPF). The same idea got generalized far beyond packets. Now you can attach tiny programs to almost anything in the kernel — syscalls, function entry/exit, the scheduler, memory, the whole network stack. Brendan Gregg's well-known BPF performance tools map shows how far it spread: there's an eBPF-powered tool for nearly every layer of the kernel.

How it works (the 30-second version)

   Your C code
      │  clang / LLVM
      ▼
   eBPF bytecode (.o, an ELF object)
      │  bpf() syscall  (via libbpf / bpftool / BCC)
      ▼
   ┌──────────────────── Kernel ────────────────────┐
   │  1. Verifier  →  checks it's safe                │
   │       (no crashes, no infinite loops)           │
   │  2. (JIT)     →  runs in the BPF virtual machine │
   │  3. Attached to a hook:                         │
   │       tracepoint / kprobe / XDP / tc / ...       │
   └───────────────────┬─────────────────────────────┘
                       │  maps / ring buffer
                       ▼
              Your user-space app reads the data
Enter fullscreen mode Exit fullscreen mode

Two things make this both safe and practical:

  • The verifier rejects any program that could crash or hang the kernel before it runs. This is the reason eBPF is safe to run in production.
  • maps & ring buffers are how the kernel-side program and your user-space program share data.

Where you can attach — the SEC(...) you'll see in real code:

Hook Level Used for
xdp NIC driver fastest packet processing, DDoS drop, load balancing
tc traffic control packet shaping, classification
tracepoint/... kernel tracepoints syscalls, file ops, scheduling events
fentry/fexit kernel functions (CO-RE) low-overhead function tracing
usdt user space trace inside apps (e.g. SQL query time)

How people actually write it (pick your entry point)

You rarely touch raw bytecode. The common toolchains:

Toolchain Language Best for
C + libbpf C production; CO-RE (vmlinux.h + BTF) → portable, compile-once-run-anywhere
BCC Python + C quick prototypes; heavier dependencies
bpftrace a small DSL (awk-like) one-liners and ad-hoc tracing
cilium/ebpf Go embedding eBPF in Go services
aya Rust type-safe kernel + user space in Rust

Just starting? Use bpftrace to explore, then C + libbpf when you're ready to build something real.

When to reach for eBPF (and when not to)

eBPF shines for observability, networking, security, and performance analysis — anywhere you need kernel-level visibility or control with minimal overhead. Here's where it sits versus the usual approaches:

Approach How it sees your system Trade-off
Agent-based (e.g. Prometheus exporters) polls /proc, /sys simple, but constant overhead & limited depth
Library-based (e.g. OpenTelemetry SDKs) you instrument your code rich, but you must change every app
Kernel-based (eBPF) hooks the kernel directly deep + low overhead, no app changes — but Linux-only, steeper learning curve

Don't reach for eBPF when: a simple existing tool already does the job; you're on an old kernel (many features need 5.x+, ring buffer needs 5.8+); or you're not on Linux (it's a Linux kernel technology — on a Mac, you run it inside a Linux VM).

Try it yourself (no compiler needed)

The fastest way to see eBPF work is to trace every program your machine launches — live. On any Linux box, with bpftrace:

sudo apt install -y bpftrace
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
    printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}'
Enter fullscreen mode Exit fullscreen mode

Open a second terminal and run anything (ls, date). It shows up instantly:

3310   bash             /usr/bin/ls
3310   bash             /usr/bin/date
Enter fullscreen mode Exit fullscreen mode

That's a real eBPF program: attached to a kernel tracepoint, checked by the verifier, streaming events to user space — no kernel patching, no app changes.

Want the grown-up version (C + libbpf, CO-RE, ring buffer, a proper make build)? The full runnable example — hello-ebpf — is in the repo linked below.

On a Mac? eBPF is Linux-only — run the snippet inside a quick Ubuntu VM (Multipass/Lima). The repo README has a one-command setup.

Takeaways

  • eBPF = safe, event-driven programs inside the kernel — observe or control the system without patching it or your apps.
  • It turns the kernel from something you could only read but couldn't easily change into a platform you can safely extend at runtime — and the verifier is why that's safe in production.
  • It's powerful, not free: right problem (deep visibility, low overhead, Linux), right time (modern kernel). Don't use it where a simpler tool will do.

Resources

中文版 / Chinese version: https://hyperredstart.github.io/posts/what-is-ebpf/

New to eBPF? Tell me the one concept that still feels fuzzy, and I'll cover it.

Top comments (0)