Skip to content

构建系统

目的与范围

本文档描述了 eCapture 的构建系统,包括 Makefile 目标、编译过程、交叉编译支持、资源嵌入以及 CI/CD 工作流。构建系统同时处理 CO-RE(一次编译 - 随处运行)和非 CO-RE eBPF 字节码的编译,生成多种包格式,并支持 x86_64 和 arm64 架构的跨平台构建。

有关 eBPF 程序开发和结构的信息,请参阅 eBPF 程序开发。有关发布和部署的详细信息,请参阅 CI/CD 与发布流程


构建系统架构

构建系统通过一个主 Makefile 进行协调,该 Makefile 将任务委托给专门的 makefile 以处理不同的构建目标。整体架构遵循多阶段流程:eBPF 编译、资源嵌入和 Go 二进制编译。

构建流程概览

来源: Makefile:1-245, functions.mk:1-76


主要构建目标

Makefile 为不同的构建场景提供了几个顶级目标。每个目标代表从源代码到二进制文件的完整构建流水线。

目标描述eBPF 模式使用场景
all完整构建,包含 CO-RE 和非 CO-RE 字节码两者都有在支持 BTF 的系统上进行开发和测试
nocore / noncore仅使用非 CO-RE 字节码构建仅非 CO-RE生产构建以获得最大兼容性
ebpf仅编译 CO-RE eBPF 程序CO-RE在不完整重新构建的情况下测试 eBPF 更改
ebpf_noncore仅编译非 CO-RE eBPF 程序非 CO-RE测试非 CO-RE eBPF 更改
assets将所有字节码文件嵌入到 Go 中两者都有手动资源生成
assets_noncore仅嵌入非 CO-RE 字节码非 CO-RE非 CO-RE 构建的手动资源生成
build使用 CO-RE 资源编译 Go 二进制文件CO-RE手动二进制编译
build_noncore使用非 CO-RE 资源编译 Go 二进制文件非 CO-RE手动二进制编译
clean删除构建产物不适用为全新构建清理环境
env显示构建环境变量不适用调试构建配置

来源: Makefile:4-11, Makefile:106-245

目标依赖关系图

来源: Makefile:6-11, Makefile:134-201


CO-RE 与非 CO-RE 编译

eCapture 支持两种 eBPF 编译模式:CO-RE(一次编译 - 随处运行)和非 CO-RE。构建系统使用不同的编译标志和内核头文件要求处理这两种模式。

CO-RE 编译

CO-RE 字节码使用 BTF(BPF 类型格式)支持进行编译,允许单个二进制文件在不同内核版本上无需重新编译即可工作。

编译命令:

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

关键特性:

  • 使用 -target bpfel 指定 BPF ELF 格式
  • 需要内核中的 BTF 支持(Linux 5.2+)
  • user/bytecode/ 中生成 *_core.o 文件
  • 包含结构体字段偏移量的 CO-RE 重定位
  • 需要 bpftool 生成的 vmlinux.h

来源: Makefile:117-127

非 CO-RE 编译

非 CO-RE 字节码针对特定的内核头文件进行编译,构建时需要提供内核源代码。此模式为旧内核提供更广泛的兼容性。

编译命令:

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

关键特性:

  • 两阶段编译:clang → LLVM IR → llc → BPF 对象
  • 构建时需要内核源代码
  • user/bytecode/ 中生成 *_noncore.o 文件
  • 没有 CO-RE 重定位;结构体偏移量是硬编码的
  • 适用于不支持 BTF 的内核

来源: Makefile:139-159

编译对比

方面CO-RE非 CO-RE
内核头文件vmlinux.h完整的内核源代码树
可移植性单个二进制文件适用于所有内核特定内核的二进制文件
内核要求Linux 5.2+ 带 BTFLinux 4.15+
构建输出*_core.o*_noncore.o
编译工具clangclang + llc
结构体偏移量CO-RE 重定位编译时硬编码

来源: Makefile:117-159


交叉编译支持

构建系统支持 arm64 和 amd64 架构的交叉编译。交叉编译由 CROSS_ARCH 环境变量控制。

交叉编译架构

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

交叉编译示例

在 x86_64 主机上为 arm64 构建:

bash
CROSS_ARCH=arm64 make all

在 arm64 主机上为 amd64 构建:

bash
CROSS_ARCH=amd64 make all

为 arm64 构建 Android 版本:

bash
ANDROID=1 CROSS_ARCH=arm64 make nocore

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

内核源代码要求

对于非 CO-RE 交叉编译,构建系统需要为目标架构准备内核头文件。prepare 目标处理此操作:

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

内核头文件使用架构特定的命令进行准备:

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

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


使用 go-bindata 进行资源嵌入

构建系统使用 go-bindata 将所有编译的 eBPF 字节码文件嵌入到 Go 二进制文件中。这使得 eCapture 可以作为单个可执行文件分发,无需依赖外部字节码文件。

资源嵌入流程

来源: Makefile:162-171

资源生成命令

完整资源生成(CO-RE + 非 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)

仅非 CO-RE 资源生成:

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)

IGNORE_LESS52 变量排除较旧的 OpenSSL 1.0.2 和 1.1.0 版本,以减小针对新系统的构建的二进制文件大小。

来源: Makefile:162-171

在代码中使用资源

生成的 assets/ebpf_probe.go 文件提供了一个 Asset() 函数,模块使用该函数检索嵌入的字节码:

go
// 模块代码中的使用示例
bytecode, err := assets.Asset("openssl_3_0_0_kern_core.o")

这允许模块系统在运行时根据检测到的库版本选择和加载适当的 eBPF 字节码。

来源: Makefile:162-171


libpcap 静态库编译

eCapture 需要 libpcap 来实现数据包捕获功能。构建系统将 libpcap 编译为静态库以避免运行时依赖。

libpcap 构建配置

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

关键配置点:

  • --disable-shared:仅构建静态库(.a
  • --with-pcap=linux:使用 Linux 原生数据包捕获
  • --host=$(LIBPCAP_ARCH):交叉编译目标三元组
  • CFLAGS 包含 -static-gdwarf-4 用于调试

来源: Makefile:175-184

CGO 集成

编译的 libpcap 静态库通过 CGO 标志链接到 Go 二进制文件:

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

这会生成一个完全静态链接的二进制文件,没有外部库依赖。

来源: functions.mk:47-54


构建变量与配置

构建系统使用在 variables.mk 中定义的众多变量,并可以通过环境变量或命令行参数覆盖。

关键构建变量

变量默认值/检测方式用途
CROSS_ARCH空(原生)目标架构:arm64amd64
ANDROID0启用 Android 特定的构建标志
DEBUG0启用调试符号和详细输出
SNAPSHOT_VERSIONGit describe发布构建的版本字符串
CLANG_VERSIONclang --version 检测Clang 主版本号
GO_VERSIONgo version 检测Go 版本(必须是 1.24+)
HOST_ARCHuname -m主机架构
TARGET_ARCHCROSS_ARCH 派生编译的目标架构
GOARCHTARGET_ARCH 派生Go 架构:amd64arm64
LINUX_ARCHTARGET_ARCH 派生Linux 内核架构:x86arm64
KERN_RELEASEuname -r原生构建的内核版本
LINUX_SOURCE_PATH/usr/src 中自动检测非 CO-RE 的内核源代码路径
BPF_NOCORE_TAG内核版本标签非 CO-RE 字节码版本标记

来源: Makefile:1-2, Makefile:20-63

环境显示

env 目标显示所有构建配置:

bash
make env

示例输出:

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
---------------------------------------

来源: Makefile:19-63


Makefile 函数

functions.mk 文件为构建逻辑提供可重用的函数。

版本检查函数

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

这些检查函数确保最低工具版本:

  • Clang 9+
  • Go 1.24+
  • bpftool(用于 CO-RE vmlinux.h 生成)

来源: functions.mk:12-40

Go 构建函数

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

此函数编译 Go 二进制文件,具有以下特性:

  • 静态链接(-linkmode=external -extldflags -static
  • PIE(位置无关可执行文件)以增强安全性
  • 通过 -ldflags -X 嵌入版本信息
  • 启用 CGO 并链接 libpcap

来源: functions.mk:47-54

发布 Tar 函数

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

此函数创建包含文档和二进制文件的发布归档。

来源: functions.mk:62-76


CI/CD 流水线

CI/CD 系统使用 GitHub Actions 实现,包含两个主要工作流:持续集成和发布。

持续集成工作流

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

关键 CI 步骤

  1. 编译器安装:安装 clang-14、llvm-14 和交叉编译工具链
  2. 内核头文件准备:提取并准备用于原生和交叉编译的 Linux 内核头文件
  3. CO-RE 构建:使用完整的 CO-RE 支持进行编译并运行代码检查工具
  4. 非 CO-RE 构建:编译内核特定的字节码
  5. 交叉编译:为相反的架构构建(x86→arm64,arm64→x86)
  6. Android 构建:创建与 Android 兼容的非 CO-RE 二进制文件
  7. 测试:使用竞态检测器运行单元测试

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


发布流程

发布工作流由版本标签触发,并生成多种类型的产物。

发布工作流

来源: .github/workflows/release.yml:1-129

发布 Makefile 目标

builder/Makefile.release 提供了用于创建发布产物的专门目标:

目标描述输出
snapshot构建 Linux 非 CO-RE 发布版ecapture-*-linux-*.tar.gz
snapshot_android构建 Android 非 CO-RE 发布版ecapture-*-android-*.tar.gz
build_deb创建 Debian 包ecapture_*_*.deb
publish将所有产物上传到 GitHub包含所有文件的 GitHub 发布

来源: builder/Makefile.release:1-151

发布归档结构

每个发布归档包含:

  • ecapture 二进制文件(静态链接)
  • LICENSE 文件
  • CHANGELOG.md
  • README.md
  • README_CN.md

来源: builder/Makefile.release:69-75


包格式

DEB 包

构建系统创建具有以下结构的 Debian 包:

ecapture_<version>_<arch>.deb
├── DEBIAN/
│   └── control (元数据)
└── usr/local/bin/
    └── ecapture (二进制文件)

Control 文件字段:

  • Package: ecapture
  • Version: 语义版本(例如 1.0.0)
  • Architecture: amd64 或 arm64
  • Maintainer: 包维护者信息
  • Description: eCapture 描述
  • Homepage: https://ecapture.cc

来源: builder/Makefile.release:134-151

RPM 包

可以使用带有 spec 文件的 rpm 目标构建 RPM 包:

bash
make rpm VERSION=0.0.0 RELEASE=1

spec 文件位于 builder/rpmBuild.spec

来源: Makefile:65-71

Docker 镜像

使用两阶段 Dockerfile 构建多架构 Docker 镜像:

阶段 1:构建器

  • 基础镜像:ubuntu:22.04
  • 安装编译器和 Go
  • 使用 make all 构建 eCapture

阶段 2:运行时

  • 基础镜像:alpine:latest
  • 仅包含 ecapture 二进制文件
  • 入口点:/ecapture

来源: builder/Dockerfile:1-39


构建环境设置

对于设置构建环境的开发人员,builder/init_env.sh 脚本自动安装所有依赖项。

自动化设置脚本

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

此脚本执行以下操作:

  1. 检测 Ubuntu 版本并选择适当的 Clang 版本
  2. 检测主机架构(x86_64 或 aarch64)
  3. 通过 apt-get 安装构建依赖项
  4. 安装交叉编译工具链
  5. 下载并解压 Linux 内核源代码
  6. 为原生和交叉编译准备内核头文件
  7. 下载并安装 Go 1.24.6
  8. 克隆 eCapture 仓库

支持的 Ubuntu 版本:

  • 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)

来源: builder/init_env.sh:1-106


构建验证

版本检查

构建系统通过 .checkver_* 目标强制执行最低工具版本,这些目标必须在编译开始前通过:

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

来源: functions.mk:13-35

构建测试

CI 系统运行全面的测试:

bash
# 使用竞态检测器的单元测试
go test -v -race ./... -coverprofile=coverage.out

# 特定模块的端到端测试
bash ./test/e2e/tls_e2e_test.sh
bash ./test/e2e/gnutls_e2e_test.sh
bash ./test/e2e/gotls_e2e_test.sh

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


常见构建场景

原生构建(开发)

bash
# 完整构建,包含 CO-RE 和非 CO-RE
make clean
make all

# 仅非 CO-RE(更快,更小的二进制文件)
make clean
make nocore

交叉编译

bash
# 在 x86_64 上为 arm64 构建
make clean
CROSS_ARCH=arm64 make all

# 在 arm64 上为 amd64 构建
make clean
CROSS_ARCH=amd64 make all

Android 构建

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

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

调试构建

bash
# 启用调试符号和详细输出
make clean
DEBUG=1 make all

发布构建

bash
# 创建带版本的发布归档
make clean
SNAPSHOT_VERSION=v1.0.0 make -f builder/Makefile.release release

# 发布到 GitHub
SNAPSHOT_VERSION=v1.0.0 make -f builder/Makefile.release publish

来源: Makefile:1-245, builder/Makefile.release:1-151

构建系统 has loaded