# How to Load an eBPF Program with bpftime

> Learn how to load an eBPF program with bpftime. Inject a syscall-server library via LD_PRELOAD to intercept bpf() calls and run your eBPF programs in userspace.

- Repository: [eunomia-bpf/bpftime](https://github.com/eunomia-bpf/bpftime)
- Tags: how-to-guide
- Published: 2026-03-01

---

**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`](https://github.com/eunomia-bpf/bpftime/blob/main/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`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/bpftime_prog.cpp). The `bpftime_prog` class encapsulates the VM state, instruction buffer, and JIT compilation status. When loading occurs:

1. `bpftime_prog::bpftime_prog_load(bool jit)` validates and loads the bytecode
2. The method calls `ebpf_load` from [`vm/vm-core/src/ebpf-vm.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/vm/vm-core/src/ebpf-vm.cpp) to populate the VM
3. If `jit` is true, it invokes `ebpf_compile` to generate native machine code

Configuration options defined in [`runtime/include/bpftime_config.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/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:

```bash
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:

```bash
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:

```bash
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):
  ```bash
  BPFTIME_DISABLE_JIT=true bpftime load ./example/malloc/malloc
  ```

- **Load into kernel for verification but run in userspace**:
  ```bash
  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.

```cpp
#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`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/bpftime_prog.cpp):

- **`bpftime_prog_load(bool jit)`**: Loads bytecode into the VM via `ebpf_load` and optionally calls `ebpf_compile` for 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_PRELOAD` interception, eliminating kernel dependencies.
- The **`bpftime load`** command injects `libbpftime-syscall-server.so` to intercept `bpf()` 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_prog`** class in [`runtime/src/bpftime_prog.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/bpftime_prog.cpp) exposes `bpftime_prog_load()` and `bpftime_prog_exec()` for programmatic control.
- The VM core in [`vm/vm-core/src/ebpf-vm.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/vm/vm-core/src/ebpf-vm.cpp) provides the low-level `ebpf_load` and `ebpf_compile` primitives 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`](https://github.com/eunomia-bpf/bpftime/blob/main/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`](https://github.com/eunomia-bpf/bpftime/blob/main/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`](https://github.com/eunomia-bpf/bpftime/blob/main/vm/vm-core/src/ebpf-vm.cpp) if you are implementing a custom loader without ELF parsing.