# bpftime runs eBPF outside the kernel, and load must come before start

> bpftime is a userspace eBPF runtime built as a set of libraries rather than a single VM, with LLVM and ubpf backends and a verifier that can be PREVAIL or the kernel one. Its quick start has an ordering rule that silently produces no output if you break it.

**eunomia-bpf/bpftime** — Userspace eBPF runtime for Observability, Network, GPU & General Extensions Framework

- Repository: https://github.com/eunomia-bpf/bpftime
- Website: https://eunomia.dev/bpftime/
- Stars: 1,591 · Forks: 188
- Language: C++
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/eunomia-bpf-bpftime

## A framework, not just a virtual machine

The README is explicit about what bpftime is not. It is not a userspace eBPF VM; it is a userspace runtime framework that includes everything needed to run eBPF in userspace: a loader, a verifier, helpers, maps, ufunc and multiple event sources such as observability, network, policy or access control. If you want only the eBPF VM, the project points you at llvmbpf instead.

That framing explains the component list. vm holds the eBPF VM and JIT compiler, with a choice between the bpftime LLVM JIT and AOT compiler and the ubpf JIT or interpreter, and the LLVM VM can be built as a standalone library similar to ubpf. runtime holds the maps, helpers, ufuncs and runtime safety features. attach handles attaching programs to uprobes, syscall tracepoints, XDP and other events with bpf_link, plus driver event sources.

Two components have a choice built into them. The verifier can be PREVAIL as a userspace verifier, or the Linux kernel verifier for better results. The loader comes in two forms: an LD_PRELOAD loader library that works with the current eBPF toolchain and library without involving the kernel at all, or a daemon for when Linux eBPF is available.

The architecture is C++ built with CMake, with the source split across vm/, runtime/, attach/, bpftime-verifier/, daemon/, benchmark/, tools/ and third_party/.

## The quick start has an ordering rule you will break once

The example is the malloc program, which traces malloc calls through a uprobe and aggregates the counts in a hash map. The build and load steps run first:

```console
make -C example/malloc # Build the eBPF program example
export PATH=$PATH:~/.bpftime/
bpftime load ./example/malloc/malloc
```

Then, in another shell, the target program is started under the runtime:

```console
$ bpftime start ./example/malloc/victim
malloc called from pid 250215
continue malloc...
malloc called from pid 250215
```

The warning is stated plainly: you should always run the load command first and then the start command, or the eBPF program will not be attached. Nothing in that failure mode is loud. The program runs, the load command succeeded, and the output you expected simply never appears, so the debugging instinct should go straight to the ordering rather than to the program.

Attaching to a process that is already running is also supported, and the example shows it by backgrounding the victim, echoing its pid and then attaching. That is the capability the dynamic binary rewriting bullet in the README describes: no manual instrumentation, no restart, no recompilation.

For anything beyond the malloc example, the README points to the build and test document on eunomia.dev or to installation.md in the repository, and it also offers container images published as GitHub packages.

## LLVM 16 is what the container image pins

The Dockerfile in the repository root is the clearest statement of the build environment. It starts from ubuntu:24.04, installs the development libraries, and pins the compiler toolchain explicitly with clang-16, llvm-16 and llvm-16-dev, alongside binutils-dev, libyaml-cpp-dev, libboost1.74-all-dev and the standard build tools.

The environment variables then tell CMake what to do:

```dockerfile
ENV BPFTIME_VM_NAME=llvm 
ENV LLVM_DIR=/usr/lib/llvm-16/lib/cmake/llvm
ENV PATH="${PATH}:/usr/lib/llvm-16/bin"
```

BPFTIME_VM_NAME selects the LLVM backend, which is also what the Makefile's unit-test-runtime target exports before running the runtime tests. The cmake invocation passes RelWithDebInfo as the build type, enables the daemon with BUILD_BPFTIME_DAEMON=1, points the C and C++ compilers at clang and clang++ from the LLVM 16 tree, and sets both LLVM_CONFIG and LLVM_DIR.

Two things follow for anyone building outside the container. The C++ standard is C++20, added as a flag in the Makefile, so an older default toolchain may need updating. And because the JIT is LLVM based, the LLVM major version is not a detail you can ignore when you hit a compile error deep in the backend.

## Performance claims are goals, interoperability is a feature

The README separates these two deliberately, and the difference matters when you are deciding whether to adopt.

On interoperability, the claims are concrete. Existing eBPF toolchains such as clang, libbpf and bpftrace are used without modification, CO-RE is supported via BTF, userspace ufunc access is offered, and a userspace eBPF program can be loaded from the kernel to cooperate with kernel programs such as kprobes and network filters. Interprocess maps live in shared userspace memory, which is how you aggregate across processes or run a control plane.

On performance, the language is aspirational. The README says bpftime can experience up to a 10x speedup in uprobe overhead compared with kernel uprobe and uretprobe, and up to 10x faster than NVbit, and that read and write of userspace memory is faster than kernel eBPF. These are stated as outcomes of the design rather than as measurements you can reproduce from the repository, and the design goals section frames bypassing the kernel as the mechanism.

So treat the interoperability claims as features you can verify by writing a program and treat the speedup numbers as a hypothesis to test on your own workload. The XDP integration with AF_XDP or DPDK is labelled experimental, which is a useful signal about how much of the networking story is production ready.

## The examples are a catalogue of ideas

The example/ directory lists what has been built with the framework, and it doubles as a map of the current capability.

minimal is the starting point, malloc is the tutorial, and libbpftime_example shows the library embedding path. bpf_features collects the feature surface, tracing covers general tracing, attach_implementation shows how events are attached, and hotpatch is the one to look at if you are interested in changing execution rather than observing it. error-inject is the outlier and the most interesting for testing work, since injecting faults deliberately is a capability observability tooling usually lacks. gpu/ reflects the GPU support that arrived in the v0.2.0 release, and xdp-counter/ pairs with the AF_XDP and DPDK integration.

The otel-ebpf-profiler example connects bpftime to the OpenTelemetry profiling ecosystem, which is the clearest signal that the project wants to be a component in someone else's observability stack rather than a replacement for one.

Version 0.9.0 was released on 2026-08-14, with v0.2.0 for GPU support in October 2025 and v0.1.0 in August 2025, so the project is pre-1.0 and its version numbers will move. It carries an MIT licence, a DOI badge for an arXiv paper, and both a Linux Plumbers 2023 talk and an OSDI 2025 paper as references, which is the context you need to judge claims it makes about itself.

## Conclusion

bpftime fits a team that wants to trace or patch a userspace program with eBPF without kernel privileges, keep the existing clang and libbpf toolchain, or prototype a new event source before it exists in the kernel. It does not fit a production deployment that depends on the claimed ten times uprobe speedup, because the README states those numbers as design goals rather than as a reproducible benchmark. Before adopting, decide which verifier you will use, since PREVAIL and the kernel verifier give different results, and confirm your LLVM version matches what the Dockerfile pins.

## FAQ

### What is bpftime and is it an eBPF virtual machine?

The README says bpftime is not a userspace eBPF VM but a runtime framework that includes the loader, verifier, helpers, maps, ufunc and several event sources. For only the VM, the project points to the separate llvmbpf repository.

### How do I run an eBPF program under bpftime?

Build the program with `make -C example/malloc`, add ~/.bpftime to your PATH, then run `bpftime load` and, in another shell, `bpftime start`. The README warns that load must always come first, or the program will not be attached.

### Which LLVM version does bpftime build against?

The repository Dockerfile installs clang-16, llvm-16 and llvm-16-dev and sets LLVM_DIR to the LLVM 16 cmake directory, with BPFTIME_VM_NAME set to llvm to select that backend.

### Can bpftime work with the existing clang and libbpf toolchains?

Yes. The README states you can use existing eBPF toolchains such as clang, libbpf and bpftrace without modifications, with CO-RE support via BTF and userspace ufunc access.

## Sources

- [eunomia-bpf/bpftime on GitHub](https://github.com/eunomia-bpf/bpftime)
- [License: MIT](https://github.com/eunomia-bpf/bpftime/blob/master/LICENSE)
- [Project website](https://eunomia.dev/bpftime/)
- [README](https://github.com/eunomia-bpf/bpftime/blob/master/README.md)
- [Releases](https://github.com/eunomia-bpf/bpftime/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/eunomia-bpf-bpftime
