Skip to content

Development Guide

Relevant source files

The following files were used as context for generating this wiki page:

This guide is intended for developers who want to contribute to eCapture, add new capture modules, modify existing functionality, or understand the codebase architecture. It covers the development environment setup, key interfaces, build system, and extension points.

For detailed information about specific development tasks, see:


Development Environment Requirements

eCapture requires specific tools and dependencies for development. The project provides an automated setup script for Ubuntu-based systems.

Required Tools

ToolMinimum VersionPurpose
clang9+ (14 recommended)eBPF bytecode compilation
llvm9+ (14 recommended)eBPF toolchain
golang1.24+Application compilation
gccAny recentCross-compilation support
linux-headersMatching kernelNon-CO-RE compilation
libelf-dev-ELF file parsing
bpftool-eBPF bytecode generation

Automated Setup

The project provides builder/init_env.sh:1-106 which automatically installs dependencies on Ubuntu 20.04-24.04 for both x86_64 and aarch64 architectures. It:

  1. Detects Ubuntu version and selects appropriate clang version
  2. Installs compilation toolchain and cross-compilation tools
  3. Extracts and prepares Linux kernel headers
  4. Installs Go 1.24.6
  5. Clones the repository with submodules

Sources: builder/init_env.sh:1-106, .github/workflows/go-c-cpp.yml:16-33


Core Development Interfaces

eCapture's architecture is built around three key interfaces that developers must understand to extend functionality.

IModule Interface

The IModule interface defines the contract for all capture modules. Every module (OpenSSL, GoTLS, Bash, etc.) implements this interface.

IModule Method Contract

MethodPurposeTypical Implementation
Init()Initialize module state, parse config, setup mapsLoad eBPF bytecode selection logic, initialize caches
Start()Attach eBPF programs to hooksCall bpfManager.Start(), attach uprobes/kprobes/TC
Run()Begin event processingStart event readers, run processor
Events()Return eBPF maps for event readingReturn perf/ringbuf maps
Decode()Deserialize raw bytes to event structsParse event type, deserialize fields
DecodeFun()Map eBPF map to event decoderReturn appropriate IEventStruct factory
Dispatcher()Handle decoded eventsRoute to output, update state, save keys
Close()Cleanup resourcesStop readers, close files, detach probes

Sources: user/module/imodule.go:47-75, user/module/imodule.go:83-108

IConfig Interface

All modules receive configuration through the IConfig interface, which provides both common settings and module-specific options.

Common Configuration Fields

FieldTypePurposeExample
Piduint64Target process ID (0 = all)1234
Uiduint64Target user ID (0 = all)1000
BtfModeuint8BTF mode (0=auto, 1=core, 2=non-core)0
DebugboolEnable debug loggingtrue
IsHexboolHex output modefalse
PerCpuMapSizeinteBPF map size per CPU (pages)1024

Sources: user/config/iconfig.go:24-70, user/config/iconfig.go:95-112

IEventStruct Interface

Events flowing from eBPF to userspace implement IEventStruct, enabling polymorphic event handling.

MethodReturn TypePurpose
Decode([]byte)errorDeserialize from raw bytes
String()stringHuman-readable text format
StringHex()stringHexadecimal text format
Clone()IEventStructCreate new instance for decoding
EventType()EventTypeEvent classification
ToProtobufEvent()*pb.EventConvert to protobuf

Sources: user/event/event.go (referenced in imports)


Module Development Lifecycle

Understanding the lifecycle of a module from initialization to shutdown is essential for development.

Lifecycle Phase Details

1. Registration Phase

2. Initialization Phase

  • Init(ctx, logger, config, eventCollector) called by CLI
  • Module parses configuration: user/module/probe_openssl.go:109-176
  • Sets up internal state: connection maps, key caches, etc.
  • Determines BTF mode and kernel version

3. Bytecode Selection Phase

4. Probe Attachment Phase

  • Start() method attaches eBPF programs to hooks
  • Uses ebpfmanager library for lifecycle management
  • Configures constant editors for PID/UID filtering

5. Event Reading Phase

6. Event Processing Phase

  • EventProcessor aggregates, filters, and formats events
  • Handles connection lifecycle and protocol parsing
  • Outputs to configured destinations (file, websocket, stdout)

7. Cleanup Phase

  • Close() stops readers, detaches probes, closes files
  • Triggered by context cancellation or OS signal

Sources: user/module/imodule.go:110-171, user/module/imodule.go:236-262, user/module/probe_openssl.go:109-176, user/module/probe_openssl.go:280-350


Development Workflow

The eCapture development workflow integrates local development, testing, and CI/CD automation.

Local Build Commands

CommandPurposeOutput
make envDisplay build environment variablesConfiguration info
make or make allBuild CO-RE + non-CO-RE bytecode and binarybin/ecapture
make nocoreBuild non-CO-RE bytecode onlybin/ecapture (non-CO-RE)
make cleanRemove build artifactsClean workspace
CROSS_ARCH=arm64 makeCross-compile for ARM64bin/ecapture (arm64)
DEBUG=1 makeBuild with debug symbolsDebug-enabled binary
make test-raceRun tests with race detectorTest results
make e2eRun end-to-end testsIntegration test results
make formatFormat C code with clang-formatFormatted code

CI/CD Automation

The project uses GitHub Actions for continuous integration:

Pull Request Checks .github/workflows/go-c-cpp.yml:1-128

  1. Build on Ubuntu 22.04 x86_64

    • Install toolchain (clang-14, gcc-aarch64-linux-gnu)
    • Build CO-RE mode
    • Run golangci-lint
    • Build non-CO-RE mode
    • Cross-compile to arm64 (CO-RE and Android non-CO-RE)
    • Run race detector tests
  2. Build on Ubuntu 22.04 ARM64

    • Mirror of x86_64 workflow
    • Cross-compile to x86_64

Release Automation .github/workflows/release.yml:1-129

  1. Trigger on tag push (v*)
  2. Build for amd64 and arm64
  3. Generate release notes from previous tag
  4. Create tar.gz archives and checksums
  5. Build multi-arch Docker images
  6. Publish to GitHub Releases and Docker Hub

Sources: .github/workflows/go-c-cpp.yml:1-128, .github/workflows/release.yml:1-129, Makefile:1-269


Build System Architecture

The eCapture build system is sophisticated, handling multiple architectures, kernel versions, and compilation modes.

Key Build Concepts

1. Dual Compilation Mode

  • CO-RE (Compile Once - Run Everywhere): Uses BTF, works on any kernel with BTF enabled
    • Compiled with clang -target bpfel to *_core.o
    • Portable across kernel versions
  • Non-CO-RE: Requires kernel headers, specific to kernel version
    • Compiled with kernel headers to *_noncore.o
    • Necessary for kernels without BTF or Android

2. Kernel Version Variants

  • Kernel < 5.2 has different eBPF helpers
  • Separate *_less52.o files compiled with -DKERNEL_LESS_5_2
  • Runtime selection based on kernel.HostVersion()

3. Cross-Compilation Support

  • CROSS_ARCH variable triggers cross-compilation
  • Requires cross-toolchain: gcc-aarch64-linux-gnu or gcc-x86-64-linux-gnu
  • Kernel headers for target architecture: /usr/src/linux-source-*/arch/{x86,arm64}

4. Asset Embedding

  • All eBPF bytecode embedded into Go binary via go-bindata
  • No runtime dependency on .o files
  • Runtime selects appropriate bytecode from assets.Asset()

5. Version Injection

  • Git version injected via ldflags: functions.mk:47-54
  • Format: os_arch:vX.Y.Z-date-commit:kernel_version
  • Bytecode mode injected: ByteCodeFiles=core|noncore|all

Sources: Makefile:1-269, variables.mk:1-200, functions.mk:1-76, builder/Makefile.release:1-151


Key Code Patterns for Developers

Pattern 1: Module Registration

All modules register themselves in init() using a factory pattern:

go
// user/module/probe_openssl.go:777-786
func init() {
    RegisteFunc(NewOpenSSLProbe)
}

func NewOpenSSLProbe() IModule {
    mod := &MOpenSSLProbe{}
    mod.name = ModuleNameOpenssl
    mod.mType = ProbeTypeUprobe
    return mod
}

The CLI retrieves modules via GetModuleFunc(modName).

Pattern 2: eBPF Bytecode Selection

Modules implement version detection and bytecode selection:

go
// 1. Detect library version
verString, err := m.detectOpenssl(soPath)

// 2. Map version to bytecode file
bpfFile, found := m.sslVersionBpfMap[verString]

// 3. Apply CO-RE/non-CO-RE suffix
filename := m.geteBPFName("user/bytecode/" + bpfFile)
// Result: user/bytecode/openssl_3_0_kern_core.o

// 4. Load from embedded assets
byteBuf, err := assets.Asset(filename)

Reference: user/module/probe_openssl.go:179-278, user/module/imodule.go:191-214

Pattern 3: Event Dispatching

Events flow through a type-switching dispatcher:

go
// user/module/probe_openssl.go:733-754
func (m *MOpenSSLProbe) Dispatcher(eventStruct event.IEventStruct) {
    switch ev := eventStruct.(type) {
    case *event.ConnDataEvent:
        if ev.IsDestroy == 0 {
            m.AddConn(ev.Pid, ev.Fd, ev.Tuple, ev.Sock)
        } else {
            m.DelConn(ev.Sock)
        }
    case *event.MasterSecretEvent:
        m.saveMasterSecret(ev)
    case *event.TcSkbEvent:
        m.dumpTcSkb(ev)
    case *event.SSLDataEvent:
        m.dumpSslData(ev)
    }
}

Pattern 4: eBPF Manager Setup

Modules use ebpfmanager for probe lifecycle:

go
m.bpfManager = &manager.Manager{
    Probes: []*manager.Probe{
        {Section: "uprobe/SSL_write", ElfFuncName: "SSL_write"},
        {Section: "uretprobe/SSL_write", ElfFuncName: "SSL_write"},
        // ... more probes
    },
    Maps: []*manager.Map{
        {Name: "events"},
        {Name: "mastersecret_events"},
    },
}

m.bpfManagerOptions = manager.Options{
    ConstantEditors: m.constantEditor(), // PID/UID filtering
}

// Load and start
byteBuf, _ := assets.Asset(bpfFileName)
m.bpfManager.InitWithOptions(bytes.NewReader(byteBuf), m.bpfManagerOptions)
m.bpfManager.Start()

Sources: user/module/probe_openssl.go:733-754, user/module/imodule.go:191-214


Testing and Debugging

Unit Tests

Run unit tests with race detection:

bash
make test-race

This executes go test -v -race ./... with proper CGO flags for libpcap.

End-to-End Tests

Run module-specific E2E tests:

bash
make e2e-tls      # TLS module tests
make e2e-gnutls   # GnuTLS module tests
make e2e-gotls    # GoTLS module tests
make e2e          # All E2E tests

Reference: Makefile:240-268

Debug Mode

Enable debug logging:

bash
./ecapture tls -d          # Debug to stdout
./ecapture tls -d -l /tmp/debug.log  # Debug to file

Debug mode enables:

  • Verbose eBPF verifier output
  • Connection tracking logs
  • Event decode details
  • Processor state information

Common Development Issues

IssueCauseSolution
eBPF verifier errorsBytecode incompatible with kernelCheck kernel version, try -b 2 for non-CO-RE
Module not foundFactory not registeredAdd init() with RegisteFunc() call
Events not capturedWrong hooks/offsetsVerify library version detection logic
Compilation errorsMissing headersRun make env to check KERN_SRC_PATH
Cross-compile failsMissing toolchainInstall gcc-aarch64-linux-gnu or gcc-x86-64-linux-gnu

Sources: Makefile:240-268, user/module/imodule.go:110-171


Next Steps for Developers

For detailed information on specific development tasks:

Additional resources:

Sources: cli/cmd/root.go:80-403, user/module/imodule.go:47-480, user/config/iconfig.go:1-212, Makefile:1-269

Development Guide has loaded