# How to Add New Event Sources to bpftime: A Complete Developer's Guide

> Learn how to add new event sources to bpftime with this developer's guide. Implement base_attach_impl, define identifiers, create private data, and register with bpf_attach_ctx for seamless integration.

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

---

**To add new event sources to bpftime, you must implement a concrete subclass of `base_attach_impl`, define a unique attach-type identifier, create a private-data class for per-instance configuration, and register the implementation with `bpf_attach_ctx`.**

The `bpftime` runtime provides an extensible attach framework that allows developers to integrate custom event sources—ranging from kernel tracepoints to hardware-specific signals—while maintaining the project's zero-copy shared-memory architecture. This guide walks through the core architecture, implementation steps, and registration process required to extend bpftime with new event sources.

## Understanding the bpftime Attach Architecture

The attach system in bpftime is organized into three distinct layers that separate event capture mechanisms from program lifecycle management.

### Layer 1: Attach Implementations

Concrete event sources inherit from `bpftime::attach::base_attach_impl` defined in [`attach/base_attach_impl/base_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/base_attach_impl/base_attach_impl.hpp). This abstract base class mandates three core operations: creating an attach link, detaching by ID, and optionally registering custom BPF helpers.

### Layer 2: Attach Manager (`bpf_attach_ctx`)

The manager class defined in [`runtime/include/bpf_attach_ctx.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/runtime/include/bpf_attach_ctx.hpp) maintains a registry of all available attach implementations. It handles instantiation of links between eBPF programs and event sources through methods like `register_attach_impl` and `instantiate_attach`.

### Layer 3: Private Data Configuration

Each event source requires per-instance configuration stored in classes deriving from `bpftime::attach::attach_private_data`. These objects parse user-provided strings (such as PID numbers or memory addresses) through the `initialize_from_string` method.

## Step-by-Step Guide to Adding a New Event Source

Follow this checklist to implement and register a custom event source in the bpftime runtime.

### 1. Define the Attach Type ID

Select a unique integer identifier not used by existing implementations. Standard bpftime sources use values like `ATTACH_SYSCALL_TRACE = 2` or `ATTACH_FRIDA_UPROBE`. Define your constant in a public header:

```cpp
// attach/my_source_impl/my_source_impl.hpp
constexpr size_t ATTACH_MY_SOURCE = 1000;

```

### 2. Create the Private Data Class

Derive from `attach_private_data` to store configuration specific to your event source. Implement the string parsing constructor:

```cpp
// attach/my_source_impl/my_source_impl.hpp
#include "attach_private_data.hpp"
#include <string>

struct my_source_private_data : public bpftime::attach::attach_private_data {
    int target_pid = -1;
    std::string custom_param;
    
    int initialize_from_string(const std::string_view &sv) override {
        // Parse configuration string (e.g., "1234:debug")
        auto sep = sv.find(':');
        target_pid = std::stoi(std::string(sv.substr(0, sep)));
        if (sep != std::string_view::npos) {
            custom_param = sv.substr(sep + 1);
        }
        return 0;
    }
};

```

### 3. Implement the Base Attach Class

Create a concrete implementation of `base_attach_impl` in [`attach/my_source_impl/my_source_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/my_source_impl/my_source_attach_impl.hpp). You must override the three pure virtual methods:

```cpp
#include "base_attach_impl.hpp"
#include "my_source_impl.hpp"
#include <unordered_map>
#include <memory>

class my_source_attach_impl final : public bpftime::attach::base_attach_impl {
public:
    int create_attach_with_ebpf_callback(
        ebpf_run_callback &&cb,
        const bpftime::attach::attach_private_data &private_data,
        int attach_type) override
    {
        const auto &pd = static_cast<const my_source_private_data&>(private_data);
        int id = allocate_id();
        
        auto entry = std::make_unique<link_entry>();
        entry->id = id;
        entry->cb = std::move(cb);
        entry->pid = pd.target_pid;
        
        // Hook into your event source here (e.g., register callback with library)
        register_native_callback(pd.target_pid, id);
        
        links.emplace(id, std::move(entry));
        return id;
    }

    int detach_by_id(int id) override {
        auto it = links.find(id);
        if (it == links.end()) return -1;
        
        // Clean up OS resources
        unregister_native_callback(it->second->pid);
        links.erase(it);
        return 0;
    }

    void register_custom_helpers(ebpf_helper_register_callback reg) override {
        // Optional: register custom BPF helpers specific to this source
        reg(5000, "my_source_get_pid", (void*)+[](unsigned long long *ctx){
            // Implementation here
            return 0;
        });
    }

private:
    struct link_entry {
        int id;
        ebpf_run_callback cb;
        int pid;
    };
    
    std::unordered_map<int, std::unique_ptr<link_entry>> links;
    
    void register_native_callback(int pid, int link_id) {
        // Implementation-specific: hook into kernel, library, or hardware
    }
    
    void unregister_native_callback(int pid) {
        // Cleanup implementation
    }
    
    int allocate_id() { return next_id++; }
    int next_id = 1;
};

```

### 4. Register with the Attach Manager

During runtime initialization, register your implementation with `bpf_attach_ctx`. This typically occurs in [`main.cpp`](https://github.com/eunomia-bpf/bpftime/blob/main/main.cpp) or a dedicated initialization function:

```cpp
#include "bpf_attach_ctx.hpp"
#include "attach/my_source_impl/my_source_attach_impl.hpp"

void initialize_custom_source(bpftime::bpf_attach_ctx &ctx) {
    auto impl = std::make_unique<my_source_attach_impl>();
    
    ctx.register_attach_impl(
        { ATTACH_MY_SOURCE },  // Attach type IDs this implementation handles
        std::move(impl),
        [](const std::string_view &sv, int &err) 
            -> std::unique_ptr<bpftime::attach::attach_private_data> {
            auto pd = std::make_unique<my_source_private_data>();
            err = pd->initialize_from_string(sv);
            if (err != 0) return nullptr;
            return pd;
        }
    );
}

```

### 5. Integrate with the Event Source

The final step involves connecting your implementation to the actual event generation mechanism. Depending on your source type, this may involve:

- Opening a `perf_event_open` fd for kernel tracepoints
- Using `ptrace` or `process_vm_writev` for user-space probes
- Registering callbacks with third-party libraries (similar to `frida_uprobe_attach_impl`)
- Polling hardware performance counters

When the native event fires, your implementation must retrieve the stored callback using the link ID and invoke it:

```cpp
void on_native_event_fires(int link_id, void *ctx) {
    auto it = links.find(link_id);
    if (it != links.end()) {
        // Prepare memory buffer with event context
        it->second->cb(memory_buffer, buffer_size, &return_value);
    }
}

```

## Reference Implementations in the Source Tree

Study these existing implementations to understand specific patterns for different event types:

- **[`attach/simple_attach_impl/simple_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/simple_attach_impl/simple_attach_impl.hpp)** – Minimal reference showing basic structure without OS dependencies
- **[`attach/syscall_trace_attach_impl/include/syscall_trace_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/syscall_trace_attach_impl/include/syscall_trace_attach_impl.hpp)** – Complex example with custom helper registration and global singleton pattern for system call interception
- **[`attach/frida_uprobe_attach_impl/include/frida_uprobe_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/frida_uprobe_attach_impl/include/frida_uprobe_attach_impl.hpp)** – Integration with external library (Frida) showing how to proxy events through third-party instrumentation
- **[`attach/nv_attach_impl/nv_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/attach/nv_attach_impl/nv_attach_impl.hpp)** – GPU-specific source demonstrating multiple custom helpers for CUDA event tracing

## Summary

- **bpftime** extends its event capabilities through a pluggable attach framework centered on `base_attach_impl`.
- To add new event sources to bpftime, implement three core components: a unique attach-type constant, a private-data class for configuration, and a concrete attach implementation.
- The `bpf_attach_ctx` manager handles registration through `register_attach_impl`, which accepts the implementation instance, supported attach types, and a factory function for parsing private data.
- Event sources must implement `create_attach_with_ebpf_callback` to store the eBPF runner and `detach_by_id` to clean up resources.
- Optional methods `register_custom_helpers` and `call_attach_specific_function` enable extended functionality for specialized event sources.

## Frequently Asked Questions

### What is the minimum code required to add a new event source?

At minimum, you need a class inheriting from `bpftime::attach::base_attach_impl` that overrides `create_attach_with_ebpf_callback` and `detach_by_id`, plus a private-data class derived from `attach_private_data`. You must also call `bpf_attach_ctx::register_attach_impl` during initialization to register your attach type ID and factory function.

### How do I handle custom BPF helpers for my event source?

Override the `register_custom_helpers` method in your `base_attach_impl` subclass. This method receives a callback function that you invoke with the helper index, name, and function pointer. For example, the CUDA implementation in [`nv_attach_impl.hpp`](https://github.com/eunomia-bpf/bpftime/blob/main/nv_attach_impl.hpp) registers multiple GPU-specific helpers using this mechanism to expose hardware metrics to eBPF programs.

### Can I add multiple attach types in a single implementation?

Yes. The `register_attach_impl` method accepts an `std::initializer_list<int>` for attach types, allowing one implementation class to handle multiple related event types. This is useful when different event types share the same underlying mechanism but require different configuration parameters or trigger conditions.

### How do I test my new event source implementation?

Place unit tests in `runtime/test/` or `attach/test/`. Verify that `create_attach_with_ebpf_callback` correctly stores the callback and returns a valid ID, that the native event trigger invokes the stored callback with proper memory context, and that `detach_by_id` successfully removes the link and releases OS resources. Integration tests should validate the full lifecycle through the `bpf_attach_ctx` API.