# eBPF: Running Your Code in the Kernel Without a Module

> For decades, changing kernel behavior meant writing a kernel module and risking a panic. eBPF lets you load sandboxed programs into a running kernel safely — and it's quietly become the backbone of modern observability and networking.

URL: https://harshith.in/blog/ebpf-explained/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: observability, linux

Traditionally, two options existed for kernel-level work: read what the kernel
chose to expose, or write a kernel module and accept that a bug means a panic.
eBPF adds a third: load your own program *into* the running kernel, attach it to
an event, and trust that the kernel won't let it crash anything. It's the
technology under `bpftrace`, Cilium, and most of the new observability stack.

## A Virtual Machine Inside the Kernel

eBPF is a small register-based virtual machine built into Linux. You compile a
restricted C program to eBPF bytecode, load it, and the kernel runs it in a
sandbox when a chosen event fires:

```text
   your .c  ──clang──▶  eBPF bytecode  ──load──▶  [ verifier ]
                                                       │ pass
                                                       ▼
                                     attached to an event, JIT-compiled,
                                     runs in kernel context
```

It is not a module. It can't call arbitrary kernel functions, can't loop
unboundedly, and can't touch memory it wasn't handed.

## The Verifier Is the Whole Trick

Before anything runs, the **verifier** statically analyzes every possible path
through the program and rejects it unless it can prove safety:

```text
   - all loops bounded (no infinite loops)
   - no out-of-bounds memory access
   - no uninitialized reads
   - bounded total instruction count
```

This is why eBPF is safe where modules aren't: a program that *might* misbehave
never loads. The cost is a constrained programming model — you write to please the
verifier, which can be maddening, but it's what makes running untrusted code in
ring 0 acceptable.

## Maps: Talking to User Space

An eBPF program is event-driven and short-lived, so it needs somewhere to keep
state and a way to report results. **Maps** are kernel-resident key-value stores
shared between the eBPF program and user space:

```text
   kernel: eBPF program ──writes──▶ [ map ] ◀──reads── user-space tool
           (e.g. count syscalls)     (hash, array, ring buffer, ...)
```

Counters, histograms, per-PID stats — all live in maps. A user-space program
polls them to draw the flame graph or latency histogram you actually see.

## Where It Attaches

The power is the breadth of attach points:

```text
   kprobes / tracepoints → trace any kernel function or event
   uprobes               → trace user-space functions too
   XDP                   → process packets at the NIC driver, pre-stack
   tc / cgroup hooks     → shape and filter traffic, enforce policy
   LSM hooks             → make security decisions
```

The same mechanism powers `bpftrace` one-liners, Cilium's networking, and runtime
security tools — one kernel feature, three industries.

```bash
# Count system calls by process, live, no kernel module:
sudo bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'
```

## Takeaways

- eBPF runs sandboxed programs inside the kernel, attached to events — a safe
  alternative to kernel modules.
- The verifier statically proves each program is bounded and memory-safe before it
  loads; that guarantee is the core of eBPF.
- Maps are shared key-value stores that pass state and results between kernel and
  user space.
- Broad attach points (kprobes, XDP, LSM) make one mechanism the basis of modern
  tracing, networking, and security tooling.
