Understanding the bpftime Architecture: Core Components and Design

The bpftime architecture consists of six primary modules—a modular VM for bytecode execution, a runtime for object management and shared memory, an attach system for event hooking, a userspace verifier for safety analysis, an LD_PRELOAD loader for process injection, and an optional daemon for kernel integration—that together enable high-performance userspace eBPF execution.

The bpftime project, hosted at eunomia-bpf/bpftime, provides a complete userspace eBPF runtime designed for attaching to arbitrary processes without kernel cooperation. Understanding the bpftime architecture is essential for developers extending the platform or optimizing eBPF program performance in userspace environments.

Core Components of the bpftime Architecture

The bpftime architecture is organized into distinct layers, each responsible for a specific aspect of the eBPF lifecycle. These components interact through well-defined APIs to provide a seamless execution environment.

1. VM (Virtual Machine)

The VM component executes eBPF bytecode and supports multiple backend implementations for different performance and compatibility requirements. Located in the vm/ directory, it abstracts the execution engine from the rest of the system.

Key implementations include:

  • LLVM-based JIT/AOT: Uses llvmbpf for native code generation
  • ubpf: Provides both interpreter and JIT compilation modes
  • Standalone library: For embedded scenarios requiring minimal dependencies

The VM interface is defined in vm/vm-core/include/ebpf-vm.h, which serves as the primary contract between execution engines and the runtime.

2. Runtime

The Runtime manages eBPF objects including maps, helpers, and user functions (ufuncs). Implemented primarily in the runtime/ directory, it provides the shared-memory infrastructure that enables zero-copy map access between processes.

Core responsibilities include:

  • Object lifecycle management for programs and maps
  • Safety checks and resource limits
  • Shared memory allocation for bpftime maps accessible across process boundaries

The public API is exposed through runtime/include/bpftime.hpp, while internal object tracking occurs in runtime/src/handler/handler_manager.hpp.

3. Attach Events

The Attach component provides the plugin system for hooking eBPF programs to event sources. Located in attach/, it abstracts the mechanism by which execution is triggered.

Supported attachment types include:

  • Uprobes: User-space function interception
  • Syscall tracepoints: System call monitoring
  • XDP: Network packet processing (userspace implementation)
  • GPU kernels: Accelerated computing event hooks

The reference implementation for simple uprobes resides in attach/simple_attach_impl/simple_attach_impl.cpp.

4. Verifier

The Verifier performs static safety analysis on eBPF programs before loading. Found in bpftime-verifier/, it ensures programs cannot crash the runtime or access unauthorized memory.

Key characteristics:

  • Uses the PREVAIL userspace verifier by default
  • Can delegate verification to the Linux kernel verifier when available
  • Validates memory access patterns and instruction sequences

The entry point is bpftime-verifier/src/bpftime-verifier.cpp.

5. Loader and Agent

The Loader injects the bpftime runtime into target processes without requiring application recompilation. It consists of:

  • LD_PRELOAD agent: A shared library (libbpftime-agent.so) that initializes the runtime when injected via LD_PRELOAD
  • CLI tools: The bpftime command-line interface for loading objects and attaching to processes

Implementation resides in runtime/syscall-server/loader.cpp.

6. Daemon

The optional Daemon enables interoperability between userspace bpftime programs and the kernel eBPF subsystem. Located in daemon/, it allows hybrid deployments where some programs run in kernel space and others in userspace.

How the bpftime Architecture Works

The components interact in a defined pipeline to execute eBPF programs:

  1. Compilation: Developers compile eBPF programs using standard clang and libbpf toolchains.
  2. Verification: The verifier validates bytecode safety using PREVAIL or kernel verification.
  3. Loading: The loader injects the runtime via LD_PRELOAD or explicit CLI commands.
  4. Execution: The VM executes bytecode, utilizing runtime-managed maps and helpers.
  5. Attachment: The attach system rewrites target binaries or intercepts syscalls to trigger program execution on events.
  6. Inter-process sharing: Maps reside in shared memory, enabling zero-copy data exchange between processes.

Implementation Details and Key Files

Understanding the source layout is crucial for extending the architecture:

Practical Examples

Loading and Running a Uprobe Program

Compile and execute an eBPF program targeting malloc calls:


# Build the example

make -C example/malloc

# Add bpftime to PATH

export PATH=$PATH:~/.bpftime/

# Load the eBPF object

bpftime load ./example/malloc/malloc

# Start the target program

bpftime start ./example/malloc/victim

Attaching to a Running Process

Inject bpftime into an already executing process:


# Start target in background and capture PID

./example/malloc/victim & echo $!  # Output: 101771

# Attach loaded eBPF program to PID

sudo bpftime attach 101771

Using the LD_PRELOAD Agent

Directly inject the runtime without CLI tools:


# Set the agent library path

export LD_PRELOAD=$HOME/.bpftime/libbpftime-agent.so

# Execute any binary; runtime initializes automatically

./my_app

Summary

  • The bpftime architecture comprises six modular components: VM, Runtime, Attach, Verifier, Loader/Agent, and Daemon.
  • The VM supports multiple backends (LLVM, ubpf) for flexible bytecode execution.
  • The Runtime manages shared-memory maps and eBPF objects, enabling inter-process communication.
  • The Attach system provides pluggable event sources from uprobes to GPU kernels.
  • The Verifier ensures safety using PREVAIL or kernel verification before execution.
  • The Loader uses LD_PRELOAD for non-invasive process injection, while the Daemon enables kernel interoperability.

Frequently Asked Questions

What is the bpftime architecture designed for?

The bpftime architecture is designed to execute eBPF programs entirely in userspace without kernel dependencies. It enables attaching eBPF programs to arbitrary user-space functions, system calls, and even GPU kernels while providing kernel-compatible map semantics and verification.

How does bpftime differ from kernel eBPF?

Unlike kernel eBPF, which requires root privileges and kernel version compatibility, bpftime runs in userspace using LD_PRELOAD injection. It uses shared memory for maps instead of kernel objects, supports attaching to already-running processes, and can operate without kernel eBPF support entirely.

What verifier does bpftime use for safety checks?

Bpftime uses the PREVAIL userspace verifier by default, located in bpftime-verifier/src/bpftime-verifier.cpp. It can also delegate verification to the Linux kernel verifier when available, providing flexibility in safety validation approaches.

Can bpftime attach to processes that are already running?

Yes, bpftime supports attaching to existing processes through the bpftime attach <PID> command. This uses the agent infrastructure in runtime/syscall-server/loader.cpp to inject the runtime into a running process without requiring restart or recompilation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →