
How I Run eBPF on Raspberry Pi: A Step-by-Step Guide
I wanted to run eBPF on Raspberry Pi to get real tracing in my small home lab — not an academic demo but usable data from real services.
I hit a hard stop: the stock kernel lacked BTF, so Grafana Beyla-style CO-RE tracing failed with relocation errors.
My goal was simple: a Pi that boots normally, keeps its device tree, and accepts ebpf programs without breaks.
I focus only on kernel changes that make tracing work. No distro debates, no hand-waving. I use Bookworm-era Raspberry Pi OS specifics and follow the official kernel documentation as the baseline.
Key Takeaways
- I needed a custom kernel to enable required tracing features.
- Stock kernels may handle basic tasks but can fail CO-RE without BTF.
- The method preserves the device tree and normal boot behavior.
- I show repeatable steps to reapply after kernel updates.
- Official Raspberry Pi kernel documentation is the starting point; I note the exact tweaks I applied.
What breaks on the stock Raspberry Pi kernel and how to confirm it
Tracing failed early — the loader could not find kernel type information. I spot this fast by hunting for a precise Beyla error log.
Exact error to look for:
time=2024-11-15T16:03:32.999Z level=ERROR msg=”couldn’t trace process. Stopping process tracer” component=ebpf.ProcessTracer path=/usr/local/bin/cephcsi pid=2675 error=”loading and assigning BPF objects: field UprobeServeHTTP: program uprobe_ServeHTTP: apply CO-RE relocations: load kernel spec: no BTF found for kernel version 6.6.51+rpt-rpi-2712: not supported”
Plain language: the loader needs a BTF file with type information. It fails to read that file and aborts the program load. CO-RE relocations need those types to adapt to layout changes.
Quick checks I run first:
- Confirm the board model via /proc/device-tree and uname.
- Verify OS release (Bookworm) using /etc/os-release.
- Check the running kernel version string — note suffixes like +rpt-rpi-2712 when you compare builds.
Important point: some simple probes may work without BTF. In my case, Beyla fails specifically because CO-RE and uprobes need that information — not because eBPF is entirely broken.
Kernel features you need for eBPF tracing and why they matter
Getting reliable tracing meant enabling a handful of kernel features and avoiding defaults that silently block probes.
Minimum config flags to enable
I enable these in my build config because tools like Beyla expect them:
- CONFIG_ARCH_SUPPORTS_UPROBES=y
- CONFIG_UPROBES=y
- CONFIG_KPROBES=y and CONFIG_KRETPROBES=y
- CONFIG_DEBUG_INFO_BTF=y
- Support flags: CONFIG_HAVE_KPROBES, CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE
Uprobes vs kprobes for tracing
Uprobes attach to user-space functions. Kprobes attach to kernel functions. If you want function-level events in a user process, uprobes must be set.
Where ebpf programs run
The verifier checks safety first. That prevents bad memory access and infinite loops.
After verification, the linux kernel runs code in the interpreter or via JIT. The ebpf program executes at the hook point.
Sharing data with user space
Maps hold counters, histograms, and state. User space reads maps to export metrics or logs. No maps — no persistent event data.
| Capability | Why it matters | Config symbol |
|---|---|---|
| User-space probes | Attach to app functions for latency and errors | CONFIG_UPROBES |
| Kernel probes | Trace kernel events and stackframes | CONFIG_KPROBES / CONFIG_KRETPROBES |
| BTF debug info | CO-RE relocations and type layout | CONFIG_DEBUG_INFO_BTF |
| XDP | High-rate packet handling at device boundary — may need custom kernel | CONFIG_XDP_SOCKETS (consider enabling) |
Build prep on Raspberry Pi: source code, tools, and a clean baseline config
I build the kernel directly on the board to keep variables predictable and the workflow simple.
Why local? Cross-compilation adds toolchain quirks. A local build uses the native compiler and shell behavior you will boot with. Less mystery. Fewer broken assumptions.

Clone the kernel source
Copy the official source with a shallow clone to save time and space:
git clone --depth=1 https://github.com/raspberrypi/linux
cd linuxSet the target and generate the default config
Export the kernel name so the build system matches /boot/firmware expectations:
export KERNEL=kernel_2712
make bcm2712_defconfigGenerating bcm2712_defconfig applies board defaults—Wi‑Fi, overlays, and device support—so you don’t miss essential settings.
Install menuconfig dependencies and open the UI
Install the one package I always forget:
sudo apt-get install libncurses5-devThen run make menuconfig and confirm you’re editing the generated .config, not an old file from a prior build.
- I keep a copy of the working .config in my home directory as a fallback.
- This way I can reapply a known-good config after updates.
- Small, repeatable steps make the build and testing loop fast.
| Action | Why | Example command |
|---|---|---|
| Clone source | Fast checkout; minimal files | git clone --depth=1 ... |
| Set target | Match bootloader naming | export KERNEL=kernel_2712 |
| Generate default | Apply board defaults | make bcm2712_defconfig |
How to run eBPF on Raspberry Pi by compiling a BTF- and uprobe-ready kernel
I start by setting a clear local version so I can spot my custom build after reboot. In menuconfig go to General setup > Local version and append -v8-beyla-compatible (or your suffix).
Next: enable debug info. Open Kernel hacking > Compile-time checks and compiler options > Debug information and turn on the options that expose CONFIG_DEBUG_INFO_BTF. Save—then close and reopen menuconfig to avoid accidental resets.
Then enable uprobes support: Kernel hacking > Tracers > enable “Enable uprobes-based dynamic events”. Save again.
- Compile targets:
make Image.gz modules dtbs. - Install modules:
sudo make modules_install— this ensures /lib/modules matches your version. - Backup existing boot files, then copy new files into /boot/firmware: Image.gz, the Broadcom dtbs and overlays.
Reboot. Verify with uname -r and check /lib/modules for the same version directory. Keep bcc tools installed for quick sanity checks if probes fail.
| Step | Command / Menu | Why it matters |
|---|---|---|
| Set local version | General setup > Local version | Identifies custom kernel in logs and uname |
| Enable BTF | Kernel hacking > Debug information | Allows CO-RE loaders to read kernel types |
| Enable uprobes | Kernel hacking > Tracers | User-space function tracing for Beyla |
| Build & Install | make Image.gz modules dtbssudo make modules_install | Produces bootable image with matching modules |
Validate the setup with a real eBPF workload and basic tracing tests
The first step is a smoke test that proves the kernel exposes the debug types my tools expect.
Confirm BTF availability. Use bpftool to list BTF for the running kernel. If bpftool shows a BTF blob, the CO-RE loaders have the information they need to relocate types. If you need bpftool, follow the quick guide to install bpftool.
Run a small program to test hooks and maps
Pick a known-good example: a kprobe or tracepoint counter from bcc. Start the example and watch output for counts. A working program attaches, increments a map, and prints lines over time.
What success looks like: probes attach without CO-RE errors, the example prints periodic counts, and maps show live data. Failure signs: the loader errors with “no BTF found” or probes silently fail to attach.
Verify uprobes and Beyla attachment
Next, attach an uprobe to a short-lived user binary. bcc tools fail fast and will tell you if uprobe support is broken.
Finally, start Beyla and watch logs. If the earlier CO-RE relocation error is gone, the process tracer stays running and events stream without the tracer stopping.
| Check | Command / Tool | Success indicator |
|---|---|---|
| BTF present | bpftool btf dump | BTF blob listed for current uname -r |
| Smoke test program | bcc example (trace.py) | Counts printed; map values increase |
| Uprobe attach | bcc/uprobe test | Probe attaches; events for user process observed |
| Beyla probe load | Beyla process tracer | No CO-RE relocation errors; tracer remains active |
Keeping your Pi stable after the custom kernel: common pitfalls on Bookworm
Keeping a custom kernel healthy on Bookworm takes deliberate file and naming hygiene. I make small, repeatable steps to avoid surprises.
I keep the kernel name suffix aligned with Raspberry Pi conventions so boot scripts and update tooling find the right version. I copy Image.gz, dtbs, and overlays into /boot/firmware together: treat that directory as an API.
After each build I verify /lib/modules matches the running kernel version. Mismatched modules cause silent device failures—Wi‑Fi and USB are usual victims.
I check /usr/lib for extra kernel-related files, run post-install hooks, and update-initramfs when needed. I keep a fallback image and the old modules dir until the system proves stable.
Maintenance: rebase to new upstream releases, rebuild with my config, then revalidate BTF and uprobes before trusting tracing again.
