Skip to content

Build System

Relevant source files

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

Purpose and Scope

This document describes the eCapture build system, including Makefile targets, compilation processes, cross-compilation support, asset embedding, and CI/CD workflows. The build system handles both CO-RE (Compile Once - Run Everywhere) and non-CO-RE eBPF bytecode compilation, produces multiple package formats, and supports cross-platform builds for x86_64 and arm64 architectures.

For information about eBPF program development and structure, see eBPF Program Development. For release and deployment details, see CI/CD and Release Process.


Build System Architecture

The build system is orchestrated through a main Makefile that delegates to specialized makefiles for different build targets. The overall architecture follows a multi-stage process: eBPF compilation, asset embedding, and Go binary compilation.

Build Flow Overview

Sources: Makefile:1-245, functions.mk:1-76


Primary Build Targets

The Makefile provides several top-level targets for different build scenarios. Each target represents a complete build pipeline from source to binary.

TargetDescriptioneBPF ModeUse Case
allFull build with CO-RE and non-CO-RE bytecodeBothDevelopment and testing on systems with BTF support
nocore / noncoreBuild with non-CO-RE bytecode onlyNon-CO-RE onlyProduction builds for maximum compatibility
ebpfCompile CO-RE eBPF programs onlyCO-RETesting eBPF changes without full rebuild
ebpf_noncoreCompile non-CO-RE eBPF programs onlyNon-CO-RETesting non-CO-RE eBPF changes
assetsEmbed all bytecode files into GoBothManual asset generation
assets_noncoreEmbed non-CO-RE bytecode onlyNon-CO-REManual asset generation for non-CO-RE builds
buildCompile Go binary with CO-RE assetsCO-REManual binary compilation
build_noncoreCompile Go binary with non-CO-RE assetsNon-CO-REManual binary compilation
cleanRemove build artifactsN/AClean slate for fresh builds
envDisplay build environment variablesN/ADebugging build configuration

Sources: Makefile:4-11, Makefile:106-245

Target Dependency Graph

Sources: Makefile:6-11, Makefile:134-201


CO-RE vs Non-CO-RE Compilation

eCapture supports two eBPF compilation modes: CO-RE (Compile Once - Run Everywhere) and non-CO-RE. The build system handles both modes with different compilation flags and kernel header requirements.

CO-RE Compilation

CO-RE bytecode is compiled with BTF (BPF Type Format) support, allowing a single binary to work across different kernel versions without recompilation.

Compilation Command:

clang -D__TARGET_ARCH_$(LINUX_ARCH) \
    -target bpfel \
    -O2 -g -Wall -Werror \
    -c kern/source.c \
    -o user/bytecode/source_core.o

Key characteristics:

  • Uses -target bpfel for BPF ELF format
  • Requires BTF support in the kernel (Linux 5.2+)
  • Generates *_core.o files in user/bytecode/
  • Includes CO-RE relocations for structure field offsets
  • Requires vmlinux.h generated by bpftool

Sources: Makefile:117-127

Non-CO-RE Compilation

Non-CO-RE bytecode is compiled against specific kernel headers, requiring kernel source to be available at build time. This mode provides broader compatibility with older kernels.

Compilation Command:

clang \
    -I $(KERN_SRC_PATH)/arch/$(LINUX_ARCH)/include \
    -I $(KERN_BUILD_PATH)/arch/$(LINUX_ARCH)/include/generated \
    -I $(KERN_SRC_PATH)/include \
    -c kern/source.c \
    -o - | llc -march=bpf -filetype=obj \
    -o user/bytecode/source_noncore.o

Key characteristics:

  • Two-stage compilation: clang → LLVM IR → llc → BPF object
  • Requires kernel source at build time
  • Generates *_noncore.o files in user/bytecode/
  • No CO-RE relocations; structure offsets are hardcoded
  • Works on kernels without BTF support

Sources: Makefile:139-159

Compilation Comparison

AspectCO-RENon-CO-RE
Kernel Headersvmlinux.h onlyFull kernel source tree
PortabilitySingle binary for all kernelsKernel-specific binary
Kernel RequirementLinux 5.2+ with BTFLinux 4.15+
Build Output*_core.o*_noncore.o
Compilation Toolclang onlyclang + llc
Structure OffsetsCO-RE relocationsHardcoded at compile time

Sources: Makefile:117-159


Cross-Compilation Support

The build system supports cross-compilation for arm64 and amd64 architectures. Cross-compilation is controlled by the CROSS_ARCH environment variable.

Cross-Compilation Architecture

Sources: Makefile:99-104, .github/workflows/go-c-cpp.yml:56-65

Cross-Compilation Examples

Build for arm64 on x86_64 host:

bash
CROSS_ARCH=arm64 make all

Build for amd64 on arm64 host:

bash
CROSS_ARCH=amd64 make all

Android build for arm64:

bash
ANDROID=1 CROSS_ARCH=arm64 make nocore

Sources: Makefile:92-96, .github/workflows/go-c-cpp.yml:56-65

Kernel Source Requirements

For non-CO-RE cross-compilation, the build system requires prepared kernel headers for the target architecture. The prepare target handles this:

makefile
prepare:
	if [ -d "$(LINUX_SOURCE_PATH)" ]; then \
		$(CMD_CD) $(LINUX_SOURCE_PATH) && $(KERNEL_HEADER_GEN) || exit 1; \
	elif [ -n "$(CROSS_ARCH)" ]; then \
		echo "linux source not found"; exit 1; \
	fi

The kernel headers are prepared with architecture-specific commands:

  • For arm64: make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- prepare
  • For x86_64: make ARCH=x86 CROSS_COMPILE=x86_64-linux-gnu- prepare

Sources: Makefile:98-104, .github/workflows/go-c-cpp.yml:25-32, .github/workflows/go-c-cpp.yml:86-92


Asset Embedding with go-bindata

The build system uses go-bindata to embed all compiled eBPF bytecode files into the Go binary. This allows eCapture to be distributed as a single executable without external dependencies on bytecode files.

Asset Embedding Process

Sources: Makefile:162-171

Asset Generation Commands

Full asset generation (CO-RE + non-CO-RE):

makefile
assets: ebpf ebpf_noncore
	$(CMD_GO) run github.com/shuLhan/go-bindata/cmd/go-bindata \
		$(IGNORE_LESS52) -pkg assets -o "assets/ebpf_probe.go" \
		$(wildcard ./user/bytecode/*.o)

Non-CO-RE only asset generation:

makefile
assets_noncore: ebpf_noncore
	$(CMD_GO) run github.com/shuLhan/go-bindata/cmd/go-bindata \
		$(IGNORE_LESS52) -pkg assets -o "assets/ebpf_probe.go" \
		$(wildcard ./user/bytecode/*.o)

The IGNORE_LESS52 variable excludes older OpenSSL 1.0.2 and 1.1.0 versions to reduce binary size for builds targeting newer systems.

Sources: Makefile:162-171

Asset Usage in Code

The generated assets/ebpf_probe.go file provides an Asset() function that modules use to retrieve embedded bytecode:

go
// Example usage in module code
bytecode, err := assets.Asset("openssl_3_0_0_kern_core.o")

This allows the module system to select and load the appropriate eBPF bytecode based on detected library versions at runtime.

Sources: Makefile:162-171


libpcap Static Library Compilation

eCapture requires libpcap for packet capture functionality. The build system compiles libpcap as a static library to avoid runtime dependencies.

libpcap Build Configuration

makefile
$(TARGET_LIBPCAP):
	test -f ./lib/libpcap/configure || git submodule update --init
	cd lib/libpcap && \
		CC=$(CMD_CC_PREFIX)$(CMD_CC) AR=$(CMD_AR_PREFIX)$(CMD_AR) \
		CFLAGS="-O2 -g -gdwarf-4 -static -Wno-unused-result" \
		./configure \
			--disable-rdma \
			--disable-shared \
			--disable-usb \
			--disable-netmap \
			--disable-bluetooth \
			--disable-dbus \
			--without-libnl \
			--without-dpdk \
			--without-dag \
			--without-septel \
			--without-snf \
			--without-gcc \
			--with-pcap=linux \
			--without-turbocap \
			--host=$(LIBPCAP_ARCH) && \
		CC=$(CMD_CC_PREFIX)$(CMD_CC) AR=$(CMD_AR_PREFIX)$(CMD_AR) make

Key configuration points:

  • --disable-shared: Build only static library (.a)
  • --with-pcap=linux: Use Linux native packet capture
  • --host=$(LIBPCAP_ARCH): Cross-compilation target triplet
  • CFLAGS includes -static and -gdwarf-4 for debugging

Sources: Makefile:175-184

CGO Integration

The compiled libpcap static library is linked into the Go binary via CGO flags:

makefile
CGO_ENABLED=1
CGO_CFLAGS='-O2 -g -I$(CURDIR)/lib/libpcap/'
CGO_LDFLAGS='-O2 -g -L$(CURDIR)/lib/libpcap/ -lpcap -static'

This produces a fully statically-linked binary with no external library dependencies.

Sources: functions.mk:47-54


Build Variables and Configuration

The build system uses numerous variables defined in variables.mk and can be overridden via environment variables or command-line arguments.

Key Build Variables

VariableDefault / DetectionPurpose
CROSS_ARCHEmpty (native)Target architecture: arm64 or amd64
ANDROID0Enable Android-specific build flags
DEBUG0Enable debug symbols and verbose output
SNAPSHOT_VERSIONGit describeVersion string for release builds
CLANG_VERSIONDetected from clang --versionClang major version number
GO_VERSIONDetected from go versionGo version (must be 1.24+)
HOST_ARCHuname -mHost machine architecture
TARGET_ARCHDerived from CROSS_ARCHTarget architecture for compilation
GOARCHDerived from TARGET_ARCHGo architecture: amd64 or arm64
LINUX_ARCHDerived from TARGET_ARCHLinux kernel architecture: x86 or arm64
KERN_RELEASEuname -rKernel version for native builds
LINUX_SOURCE_PATHAuto-detected in /usr/srcPath to kernel source for non-CO-RE
BPF_NOCORE_TAGKernel version tagTag for non-CO-RE bytecode versioning

Sources: Makefile:1-2, Makefile:20-63

Environment Display

The env target displays all build configuration:

bash
make env

Example output:

eCapture Makefile Environment:
---------------------------------------
PARALLEL                 -j8
----------------[ from args ]---------------
CROSS_ARCH               arm64
ANDROID                  0
DEBUG                    1
---------------------------------------
HOST_ARCH                x86_64
CLANG_VERSION            14
GO_VERSION               1.24.6
TARGET_ARCH              arm64
GOARCH                   arm64
LINUX_ARCH               arm64
---------------------------------------

Sources: Makefile:19-63


Makefile Functions

The functions.mk file provides reusable functions for build logic.

Version Check Functions

makefile
.checkver_$(CMD_CLANG):
	@if [ ${CLANG_VERSION} -lt 9 ]; then
		echo "you MUST use clang 9 or newer"
		exit 1
	fi
	
.checkver_$(CMD_GO):
	@if [ ${GO_VERSION_MAJ} -eq 1 ]; then
		if [ ${GO_VERSION_MIN} -lt 24 ]; then
			echo "you MUST use golang 1.24 or newer"
			exit 1
		fi
	fi

These check functions ensure minimum tool versions:

  • Clang 9+
  • Go 1.24+
  • bpftool (for CO-RE vmlinux.h generation)

Sources: functions.mk:12-40

Go Build Function

makefile
define gobuild
	CGO_ENABLED=1 \
	CGO_CFLAGS='-O2 -g -gdwarf-4 -I$(CURDIR)/lib/libpcap/' \
	CGO_LDFLAGS='-O2 -g -L$(CURDIR)/lib/libpcap/ -lpcap -static' \
	GOOS=linux GOARCH=$(GOARCH) CC=$(CMD_CC_PREFIX)$(CMD_CC) \
	$(CMD_GO) build -trimpath -buildmode=pie -mod=readonly \
		-tags '$(TARGET_TAG),netgo' \
		-ldflags "-w -s \
			-X 'github.com/gojue/ecapture/cli/cmd.GitVersion=$(TARGET_TAG)_$(GOARCH):$(VERSION_NUM):$(VERSION_FLAG)' \
			-X 'github.com/gojue/ecapture/cli/cmd.ByteCodeFiles=$(BYTECODE_FILES)' \
			-linkmode=external -extldflags -static" \
		-o $(OUT_BIN)
endef

This function compiles the Go binary with:

  • Static linking (-linkmode=external -extldflags -static)
  • PIE (Position Independent Executable) for security
  • Version information embedded via -ldflags -X
  • CGO enabled with libpcap linkage

Sources: functions.mk:47-54

Release Tar Function

makefile
define release_tar
	$(call allow-override,TAR_DIR,ecapture-$(DEB_VERSION)-$(1)-$(GOARCH)$(CORE_PREFIX))
	$(call allow-override,OUT_ARCHIVE,$(OUTPUT_DIR)/$(TAR_DIR).tar.gz)
	$(CMD_MAKE) clean
	ANDROID=$(ANDROID) $(CMD_MAKE) $(2)
	$(CMD_MKDIR) -p $(TAR_DIR)
	$(CMD_CP) LICENSE $(TAR_DIR)/
	$(CMD_CP) CHANGELOG.md $(TAR_DIR)/
	$(CMD_CP) README.md $(TAR_DIR)/
	$(CMD_CP) $(OUTPUT_DIR)/ecapture $(TAR_DIR)/
	$(CMD_TAR) -czf $(OUT_ARCHIVE) $(TAR_DIR)
endef

This function creates release archives with documentation and the binary.

Sources: functions.mk:62-76


CI/CD Pipeline

The CI/CD system is implemented using GitHub Actions with two primary workflows: continuous integration and release.

Continuous Integration Workflow

Sources: .github/workflows/go-c-cpp.yml:1-128

Key CI Steps

  1. Compiler Installation: Installs clang-14, llvm-14, and cross-compilation toolchains
  2. Kernel Header Preparation: Extracts and prepares Linux kernel headers for both native and cross-compilation
  3. CO-RE Build: Compiles with full CO-RE support and runs linter
  4. Non-CO-RE Build: Compiles kernel-specific bytecode
  5. Cross-Compilation: Builds for opposite architecture (x86→arm64, arm64→x86)
  6. Android Build: Creates Android-compatible non-CO-RE binaries
  7. Testing: Runs unit tests with race detector

Sources: .github/workflows/go-c-cpp.yml:12-67, .github/workflows/go-c-cpp.yml:69-127


Release Process

The release workflow is triggered by version tags and produces multiple artifact types.

Release Workflow

Sources: .github/workflows/release.yml:1-129

Release Makefile Targets

The builder/Makefile.release provides specialized targets for creating release artifacts:

TargetDescriptionOutput
snapshotBuild Linux non-CO-RE releaseecapture-*-linux-*.tar.gz
snapshot_androidBuild Android non-CO-RE releaseecapture-*-android-*.tar.gz
build_debCreate Debian packageecapture_*_*.deb
publishUpload all artifacts to GitHubGitHub Release with all files

Sources: builder/Makefile.release:1-151

Release Archive Structure

Each release archive includes:

  • ecapture binary (statically linked)
  • LICENSE file
  • CHANGELOG.md
  • README.md
  • README_CN.md

Sources: builder/Makefile.release:69-75


Package Formats

DEB Package

The build system creates Debian packages with the following structure:

ecapture_<version>_<arch>.deb
├── DEBIAN/
│   └── control (metadata)
└── usr/local/bin/
    └── ecapture (binary)

Control file fields:

  • Package: ecapture
  • Version: Semantic version (e.g., 1.0.0)
  • Architecture: amd64 or arm64
  • Maintainer: Package maintainer info
  • Description: eCapture description
  • Homepage: https://ecapture.cc

Sources: builder/Makefile.release:134-151

RPM Package

RPM packages can be built using the rpm target with a spec file:

bash
make rpm VERSION=0.0.0 RELEASE=1

The spec file is located at builder/rpmBuild.spec.

Sources: Makefile:65-71

Docker Image

Multi-architecture Docker images are built using a two-stage Dockerfile:

Stage 1: Builder

  • Base: ubuntu:22.04
  • Installs compilers and Go
  • Builds eCapture with make all

Stage 2: Runtime

  • Base: alpine:latest
  • Contains only the ecapture binary
  • Entrypoint: /ecapture

Sources: builder/Dockerfile:1-39


Build Environment Setup

For developers setting up a build environment, the builder/init_env.sh script automates installation of all dependencies.

Automated Setup Script

bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/gojue/ecapture/master/builder/init_env.sh)"

This script:

  1. Detects Ubuntu version and selects appropriate Clang version
  2. Detects host architecture (x86_64 or aarch64)
  3. Installs build dependencies via apt-get
  4. Installs cross-compilation toolchain
  5. Downloads and extracts Linux kernel source
  6. Prepares kernel headers for native and cross-compilation
  7. Downloads and installs Go 1.24.6
  8. Clones the eCapture repository

Supported Ubuntu versions:

  • 20.04 (Clang 10)
  • 20.10 (Clang 10)
  • 21.04 (Clang 11)
  • 21.10, 22.04, 22.10 (Clang 12)
  • 23.04, 23.10 (Clang 15)
  • 24.04 (Clang 18)

Sources: builder/init_env.sh:1-106


Build Verification

Version Checks

The build system enforces minimum tool versions through .checkver_* targets that must pass before compilation begins:

makefile
.checkver_$(CMD_CLANG): | .check_$(CMD_CLANG)
	@if [ ${CLANG_VERSION} -lt 9 ]; then
		echo "you MUST use clang 9 or newer"
		exit 1
	fi

.checkver_$(CMD_GO): | .check_$(CMD_GO)
	@if [ ${GO_VERSION_MIN} -lt 24 ]; then
		echo "you MUST use golang 1.24 or newer"
		exit 1
	fi

Sources: functions.mk:13-35

Build Testing

The CI system runs comprehensive tests:

bash
# Unit tests with race detector
go test -v -race ./... -coverprofile=coverage.out

# E2E tests for specific modules
bash ./test/e2e/tls_e2e_test.sh
bash ./test/e2e/gnutls_e2e_test.sh
bash ./test/e2e/gotls_e2e_test.sh

Sources: Makefile:216-244, .github/workflows/go-c-cpp.yml:66-67


Common Build Scenarios

Native Build (Development)

bash
# Full build with CO-RE and non-CO-RE
make clean
make all

# Non-CO-RE only (faster, smaller binary)
make clean
make nocore

Cross-Compilation

bash
# Build for arm64 on x86_64
make clean
CROSS_ARCH=arm64 make all

# Build for amd64 on arm64
make clean
CROSS_ARCH=amd64 make all

Android Build

bash
# Android arm64
make clean
ANDROID=1 CROSS_ARCH=arm64 make nocore

# Android amd64
make clean
ANDROID=1 CROSS_ARCH=amd64 make nocore

Debug Build

bash
# Enable debug symbols and verbose output
make clean
DEBUG=1 make all

Release Build

bash
# Create versioned release archives
make clean
SNAPSHOT_VERSION=v1.0.0 make -f builder/Makefile.release release

# Publish to GitHub
SNAPSHOT_VERSION=v1.0.0 make -f builder/Makefile.release publish

Sources: Makefile:1-245, builder/Makefile.release:1-151

Build System has loaded