How to Load an eBPF Program with bpftime
You load an eBPF program in bpftime by running bpftime load <program>, which injects a syscall-server library via LD_PRELOAD to intercept bpf() calls, parse the ELF bytecode, and optionally JIT-compile it for userspace execution.
The bpftime repository provides a userspace eBPF runtime that executes eBPF programs without requiring kernel support. Loading an eBPF program with bpftime involves parsing ELF bytecode into a virtual machine, loading the instruction array, and optionally compiling to native code for performance. This process can be executed through the command-line interface or directly via the C++ API.
Architecture of the bpftime Program Loader
Understanding the internal components helps diagnose loading issues and optimize performance. The architecture consists of three primary layers: the CLI parser, the syscall interception library, and the runtime VM.
CLI Parser and Syscall Interception
The entry point resides in tools/cli/main.cpp, which parses the load subcommand and prepares the execution environment. When you invoke bpftime load, the CLI builds the path to libbpftime-syscall-server.so and calls run_command(...) to launch your target binary.
The syscall-server library uses LD_PRELOAD to intercept bpf() syscalls from the target process. This interception layer creates a shared-memory manager and forwards program-load requests to the runtime, allowing unmodified eBPF programs to run in userspace.
Runtime Components and VM
The core loading logic lives in runtime/src/bpftime_prog.cpp. The bpftime_prog class encapsulates the VM state, instruction buffer, and JIT compilation status. When loading occurs:
bpftime_prog::bpftime_prog_load(bool jit)validates and loads the bytecode- The method calls
ebpf_loadfromvm/vm-core/src/ebpf-vm.cppto populate the VM - If
jitis true, it invokesebpf_compileto generate native machine code
Configuration options defined in runtime/include/bpftime_config.hpp control this behavior through environment variables like BPFTIME_DISABLE_JIT and BPFTIME_RUN_WITH_KERNEL.
Loading eBPF Programs via the Command Line
The CLI workflow is the fastest way to load and test eBPF programs. The process involves building your eBPF object file, loading it into the bpftime runtime, and running the target application.
Step 1: Build the Example Program
Compile your eBPF program using the standard libbpf toolchain:
make -C example/malloc
This produces an executable containing the eBPF bytecode that bpftime will load.
Step 2: Load the Program into the Runtime
Execute the load command to inject the syscall server and prepare the runtime:
bpftime load ./example/malloc/malloc
This command sets LD_PRELOAD=~/.bpftime/libbpftime-syscall-server.so and starts your program. The runtime automatically parses the ELF sections, loads the bytecode via ebpf_load, and JIT-compiles it by default.
Step 3: Run the Target Process
Start the application you wish to instrument:
bpftime start ./example/malloc/victim
You should see output indicating the eBPF program is actively tracing events:
pid=247299 malloc calls: 10
pid=247322 malloc calls: 10
Controlling Load Behavior with Environment Variables
You can modify the loading behavior without changing code:
-
Disable JIT compilation (use interpreter mode):
BPFTIME_DISABLE_JIT=true bpftime load ./example/malloc/malloc -
Load into kernel for verification but run in userspace:
BPFTIME_RUN_WITH_KERNEL=true BPFTIME_NOT_LOAD_PATTERN=start_.* bpftime load ./example/malloc/malloc
Programmatic Loading with the C++ API
For custom tooling, bypass the CLI and use the bpftime_prog class directly. This approach gives you explicit control over the load sequence and execution parameters.
#include <bpftime/runtime/bpftime_prog.hpp>
#include <fstream>
#include <vector>
#include <iostream>
int main() {
// Read ELF object file
std::ifstream f("example/malloc/malloc.o", std::ios::binary);
std::vector<char> buf((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
// Initialize program object
bpftime::bpftime_prog prog;
prog.set_name("malloc_tracer");
prog.load_from_elf(buf); // Parses ELF and fills instruction buffer
// Load into VM with JIT enabled (true) or interpreter (false)
if (prog.bpftime_prog_load(true) != 0) {
std::cerr << "Failed to load program\n";
return 1;
}
// Execute the program with a memory context
uint8_t mem[4096] = {};
uint64_t ret = 0;
prog.bpftime_prog_exec(mem, sizeof(mem), &ret);
std::cout << "Program returned " << ret << "\n";
return 0;
}
Key methods in runtime/src/bpftime_prog.cpp:
bpftime_prog_load(bool jit): Loads bytecode into the VM viaebpf_loadand optionally callsebpf_compilefor JIT.bpftime_prog_exec(void *memory, size_t size, uint64_t *ret): Executes the loaded program against the provided memory buffer.
Summary
- bpftime provides a userspace eBPF runtime that loads programs via
LD_PRELOADinterception, eliminating kernel dependencies. - The
bpftime loadcommand injectslibbpftime-syscall-server.soto interceptbpf()syscalls and manages the ELF parsing and VM initialization. - Environment variables (
BPFTIME_DISABLE_JIT,BPFTIME_RUN_WITH_KERNEL) control JIT compilation and kernel verification without code changes. - The
bpftime_progclass inruntime/src/bpftime_prog.cppexposesbpftime_prog_load()andbpftime_prog_exec()for programmatic control. - The VM core in
vm/vm-core/src/ebpf-vm.cppprovides the low-levelebpf_loadandebpf_compileprimitives used during the loading sequence.
Frequently Asked Questions
What is the difference between bpftime load and bpftime start?
bpftime load injects the syscall-server library into a target process and prepares the eBPF runtime, but the program executes immediately. bpftime start is typically used to launch a separate victim process that the loaded eBPF program will trace or instrument. According to the source in tools/cli/main.cpp, both commands set up the LD_PRELOAD environment, but load expects the eBPF program itself as the argument, while start expects the target workload.
How do I disable JIT compilation when loading a program?
Set the BPFTIME_DISABLE_JIT=true environment variable before running the load command. This forces the runtime to use the interpreter mode in ebpf_load rather than calling ebpf_compile, which is useful for debugging or when running on architectures without JIT support. You can verify this behavior in runtime/include/bpftime_config.hpp where the configuration flags are defined.
Can I load eBPF programs compiled for the kernel into bpftime?
Yes. bpftime is designed to run standard eBPF ELF objects compiled with libbpf or clang. The load_from_elf() method in the C++ API parses the standard ELF sections (.text, maps, etc.) and loads them into the userspace VM. When using BPFTIME_RUN_WITH_KERNEL=true, the runtime can also load the program into the kernel for verification purposes while still executing it in userspace.
What file formats does bpftime support for loading programs?
bpftime primarily supports ELF object files (.o) containing eBPF bytecode, which is the standard output from the eBPF LLVM backend or libbpf. The bpftime_prog::load_from_elf() method handles the parsing. Raw bytecode arrays can also be loaded directly via the ebpf_load function in vm/vm-core/src/ebpf-vm.cpp if you are implementing a custom loader without ELF parsing.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →