# How to Run a Target Program with bpftime Attached: CLI, Injection, and Execution Flow

> Learn how to run a target program with bpftime attached. Load eBPF objects, inject the agent, and execute bytecode in user space for powerful program analysis and manipulation.

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

---

**You run a target program with bpftime attached by first loading the eBPF object into shared memory with `bpftime load`, then launching the target under the agent's control via `bpftime start`, which injects the syscall-server and agent libraries using `LD_PRELOAD` to intercept probe points and execute eBPF bytecode in user space.**

bpftime is a high-performance userspace eBPF runtime that enables attaching eBPF programs to unmodified target binaries without kernel modifications. Understanding how to run a target program with bpftime attached requires knowledge of its shared-memory architecture, library injection mechanism, and the CLI workflow implemented in the **eunomia-bpf/bpftime** repository.

## The bpftime Execution Architecture

bpftime attaches to targets through a coordinated three-layer injection system. When you run a target program with bpftime attached, you are actually spawning a managed process where eBPF programs execute natively in userspace.

### The Three Core Components

The runtime consists of tightly-coupled libraries that handle different stages of the eBPF lifecycle:

- **CLI Driver** – Parses sub-commands in [`tools/cli/main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/tools/cli/main.cpp) (lines 62–99) and prepares the `LD_PRELOAD` environment strings necessary for injection.
- **Syscall-Server Library** (`runtime/syscall-server/libbpftime-syscall-server.so`) – The first injected library loaded via `LD_PRELOAD`. It initializes the Boost-interprocess shared memory region via `bpftime_initialize_global_shm` (defined in [`runtime/include/bpftime_shm.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/include/bpftime_shm.hpp)), registers eBPF handlers, and forwards intercepted syscalls to the bpftime VM.
- **Agent Library** (`runtime/agent/libbpftime-agent.so`) – The second injected library that rewrites the target binary's text segment, registers per-process helpers, and executes the loaded eBPF program within the target's address space.

### The Two-Phase Workflow

Running a target with bpftime attached follows a strict load-then-execute pattern:

1. **Load Phase**: The eBPF object is registered in the shared-memory manager using `bpftime load`, which populates the handler registry in [`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp).
2. **Run Phase**: The CLI forks and execs the target with modified environment variables that inject the runtime libraries, enabling immediate eBPF execution at probe points.

## How to Run a Target Program with bpftime Attached

### Step 1: Load the eBPF Object into Shared Memory

Before running any target, you must load the compiled eBPF program into bpftime's global shared memory. This step registers the program IDs and map definitions that the agent will later reference.

```bash
bpftime load ./example/malloc/malloc

```

According to the source code in [`tools/cli/main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/tools/cli/main.cpp), this command initializes the **Boost-interprocess shared memory region** that allows communication between the CLI, syscall-server, and agent components. The `handler_manager` stores the eBPF program metadata for later retrieval by the agent.

### Step 2: Start the Target with Library Injection

Use the `bpftime start` command to launch your target program with the eBPF runtime attached. This command builds the injection environment and executes the target via `run_command()` (lines 78–99 of [`tools/cli/main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/tools/cli/main.cpp)).

```bash
bpftime start ./example/malloc/victim

```

The CLI constructs the environment strings exactly as implemented in the source:

```cpp
std::string ld_preload_str("LD_PRELOAD=");
ld_preload_str += ld_preload;          // e.g., ~/.bpftime/libbpftime-agent.so
std::string agent_so_str("AGENT_SO=");
agent_so_str += agent_so;              // optional transformer for syscall tracing

```

The **syscall-server** becomes the first library in `LD_PRELOAD`, spawning the **agent** (or the transformer plus agent when using `-s/--enable-syscall-trace`). When the target hits a uprobe or syscall entry point, the agent executes the corresponding eBPF bytecode from the shared memory region.

### Step 3: Attach to a Running Process (Alternative)

To run a target program with bpftime attached without restarting it, use Frida-based injection. This requires root privileges because it injects into an existing process.

```bash
sudo bpftime attach 6789

```

The `inject_by_frida()` function (lines 40–61 of [`main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/main.cpp)) calls `frida_injector_inject_library_file_sync` to load the agent library into the target PID. The output confirms successful attachment: `"Injecting to 6789 … Successfully injected. ID: 1"`.

## Understanding the Injection and Execution Mechanism

### LD_PRELOAD and AGENT_SO Environment Variables

When you run a target program with bpftime attached via `bpftime start`, the CLI forks a child process that builds specific environment strings before calling `execvpe`. The syscall-server library must appear first in `LD_PRELOAD` to initialize the shared memory region before the agent loads.

For syscall tracing scenarios, the CLI additionally sets `AGENT_SO` to load `libbpftime-agent-transformer.so` alongside the base agent. This is triggered by the `-s` flag:

```bash
bpftime start -s ./example/opensnoop/victim

```

### Binary Rewriting and Text Segment Transformation

Once injected, the agent library rewrites the target program's text segment using the technique described in [`attach/text_segment_transformer/README.md`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/text_segment_transformer/README.md). This rewriting redirects execution flow at probe points (uprobes, syscall entries) to the eBPF VM running in userspace.

### Shared Memory Communication

All components communicate through a **Boost-interprocess shared memory region** created by `bpftime_initialize_global_shm` (see [`runtime/include/bpftime_shm.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/include/bpftime_shm.hpp)). This region stores:
- eBPF program IDs and bytecode
- Map definitions and shared data
- Link registrations between probe points and programs

## Practical Code Examples

### Example 1: Running a malloc Tracer from Scratch

```bash

# Build the example eBPF program

make -C example/malloc

# Load the eBPF object into bpftime's shared memory

bpftime load ./example/malloc/malloc

# Run the target program with the eBPF program attached

bpftime start ./example/malloc/victim

# Output:

# Hello malloc!

# malloc called from pid 12345

# continue malloc...

```

### Example 2: Attaching to a Running Process

```bash

# Launch the target in the background and capture its PID

./example/malloc/victim &
PID=$!

# Inject the agent using Frida (requires root)

sudo bpftime attach $PID

# → "Injecting to 6789 … Successfully injected. ID: 1"

```

### Example 3: Enabling Syscall Tracing

```bash

# The -s flag loads both the transformer and agent libraries

bpftime start -s ./example/opensnoop/victim

```

## Key Source Files and Their Roles

| File | Purpose |
|------|---------|
| **[`tools/cli/main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/tools/cli/main.cpp)** | Command-line front-end implementing `run_command()` (lines 78–99) and `inject_by_frida()` (lines 40–61) for environment preparation and process injection. |
| **[`runtime/include/bpftime_shm.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/include/bpftime_shm.hpp)** | Defines the shared-memory region and `bpftime_initialize_global_shm` for inter-process map and program sharing. |
| **[`runtime/src/handler/handler_manager.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/src/handler/handler_manager.hpp)** | Central registry for eBPF objects (programs, maps, links) loaded during the `bpftime load` phase. |
| **`runtime/src/syscall-server/`** | Implementation of the preload library that creates shared memory and forwards syscalls to the VM. |
| **`runtime/src/agent/`** | Contains the in-process agent that rewrites binary text segments and executes eBPF programs. |
| **[`attach/text_segment_transformer/README.md`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/text_segment_transformer/README.md)** | Documents the binary-rewriting technique used for uprobe and syscall interception. |

## Summary

- **bpftime uses a two-phase workflow**: First `bpftime load` registers the eBPF object in shared memory, then `bpftime start` or `bpftime attach` injects the runtime.
- **Injection relies on LD_PRELOAD**: The CLI constructs environment strings in [`main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/main.cpp) (lines 78–99) to preload the syscall-server and agent libraries.
- **Three components coordinate execution**: The CLI driver, syscall-server library, and agent library work together to rewrite the target binary and execute eBPF bytecode in userspace.
- **Frida enables live attachment**: The `bpftime attach` command uses `frida_injector_inject_library_file_sync` to inject the agent into running processes without restart.
- **Shared memory enables state sharing**: Boost-interprocess shared memory (defined in [`bpftime_shm.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/bpftime_shm.hpp)) allows eBPF maps and programs to persist across the CLI and target process boundaries.

## Frequently Asked Questions

### What is the difference between `bpftime start` and `bpftime attach`?

**`bpftime start`** launches a new process with the agent preloaded via `LD_PRELOAD` before `execve` executes the target binary. **`bpftime attach`** uses Frida to inject the agent library into an already-running process identified by PID, which requires root privileges and calls `frida_injector_inject_library_file_sync` as implemented in [`tools/cli/main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/tools/cli/main.cpp) lines 40–61.

### Why does bpftime use LD_PRELOAD for injection?

bpftime uses `LD_PRELOAD` to ensure the **syscall-server library** initializes before the target program's main function executes. This allows the runtime to establish the Boost-interprocess shared memory region and register syscall handlers before the target begins execution, ensuring immediate interception of probe points.

### How does bpftime share eBPF maps between processes?

All bpftime components communicate through a **Boost-interprocess shared memory region** created by `bpftime_initialize_global_shm` and defined in [`runtime/include/bpftime_shm.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/include/bpftime_shm.hpp). When you run `bpftime load`, the CLI registers maps and programs in this region; the agent then accesses these structures when executing eBPF bytecode in the target's address space.

### Can I attach bpftime to a process without restarting it?

Yes. Use **`bpftime attach <pid>`** to inject the agent into a running process. This method uses the Frida framework to call `frida_injector_inject_library_file_sync`, loading `libbpftime-agent.so` into the target's memory without requiring a process restart. Note that this operation typically requires root privileges or appropriate capabilities.