Fundamentals 17 min read

How Does eBPF Work? A Deep Dive into Its VM, Instruction Set, and JIT

This article explains eBPF from a developer's perspective, covering the eBPF virtual machine, instruction encoding, bytecode generation with LLVM/Clang, loading via the bpf() syscall, verifier rules, execution flow, JIT compilation, and how Android builds and packages eBPF programs.

OPPO Kernel Craftsman
OPPO Kernel Craftsman
OPPO Kernel Craftsman
How Does eBPF Work? A Deep Dive into Its VM, Instruction Set, and JIT

Overview of eBPF

eBPF (extended Berkeley Packet Filter) is the modern incarnation of the original BPF virtual machine. It runs programs inside a small VM that provides 11 registers (R0‑R10, where R10 is the frame pointer).

BPF Virtual Machine

The VM fetches, decodes, and executes 32‑bit eBPF instructions. Each instruction consists of an 8‑bit opcode, 8‑bit register specifier, 16‑bit offset, and a 32‑bit immediate value. Wide (64‑bit) instructions extend the immediate field with a reserved area and a second immediate.

BPF VM overview
BPF VM overview

Opcode Class

The low three bits of the opcode encode the class (type) of operation; the remaining five bits have class‑specific meanings.

Opcode class diagram
Opcode class diagram

Load/Store Instructions

Load/store opcodes are composed of a 3‑bit mode, a 3‑bit size, and a 3‑bit class field.

Load/store opcode fields
Load/store opcode fields

Arithmetic and Jump Instructions

Arithmetic/JMP opcodes contain a 4‑bit code, a 1‑bit source selector (K for immediate, X for register), and a 3‑bit class.

Arithmetic/JMP opcode layout
Arithmetic/JMP opcode layout

Compiling eBPF Bytecode

eBPF programs are compiled to ELF objects with machine type EM_BPF using Clang/LLVM. Example source bpfRingbufProg.c produces bpfRingbufProg.o.

llvm-readelf -h bpfRingbufProg.o
llvm-objdump -d bpfRingbufProg.o
Disassembly output
Disassembly output

Loading and Verifying BPF Programs

Loading

int prog_fd = bpf(BPF_PROG_LOAD, &attr, sizeof(attr));
Loading flow
Loading flow

Verification

The kernel verifier performs:

DAG analysis to ensure a directed‑acyclic control‑flow graph (no loops, unreachable code).

Path simulation to guarantee registers are initialized before use and to enforce register lifetimes across helper calls (R1‑R5 become unreadable after a call, R6‑R9 may be preserved).

Valid example:

bpf_mov R6, 1
bpf_call foo
bpf_mov R0, R6
bpf_exit

Using R1 after a call would fail verification because R1 is clobbered.

Executing eBPF Bytecode

Interpreter

The kernel executes eBPF via ___bpf_prog_run, which builds a 256‑entry jump table (one entry per possible opcode) and dispatches to the appropriate handler.

Jump table initialization
Jump table initialization

Example decoding of instruction 07 01 00 00 44 33 22 11:

code = 0 (ADD)

source = 0 (K = imm)

class = 7 (ALU64)

dst_reg = 1 (R1)

src_reg = 0

imm = 0x11223344

Resulting operation: R1 += 0x11223344.

JIT Compilation

For performance, the kernel can JIT‑compile eBPF bytecode to native ISA (e.g., AArch64). The core JIT routine resides in bpf_jit_comp.c; the function build_insn translates each eBPF instruction into target machine code.

JIT compilation flow
JIT compilation flow

Android eBPF Program Layout

Typical Android build workflow:

Compile cpufreq.bpf.c with Clang/LLVM to cpufreq.bpf.o (EM_BPF ELF).

Generate a C skeleton header with bpftool gen skeleton cpufreq.bpf.o > cpufreq.skel.h. The header defines const void *cpufreq_bpf__elf_bytes containing the bytecode as a constant array.

Link the skeleton header with the native program source ( cpufreq.c) and cross‑compile the whole package for the target architecture (e.g., AArch64).

The resulting executable embeds the eBPF ELF in its .rodata section. It can be extracted with dd and inspected with llvm-readelf or llvm-objdump:

dd if=cpufreq of=bpf_elf.bin bs=1 count=13561 skip=7269
llvm-readelf -h bpf_elf.bin
llvm-objdump -S bpf_elf.bin
Extracted ELF header
Extracted ELF header

This workflow demonstrates the full lifecycle of an eBPF program on Android: source → ELF bytecode → skeleton header → native binary → embedded ELF → runtime loading via bpf() system call.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidbytecodeJITeBPFLinux kernelBPF VM
OPPO Kernel Craftsman
Written by

OPPO Kernel Craftsman

Sharing Linux kernel-related cutting-edge technology, technical articles, technical news, and curated tutorials

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.