[eCapture] GoTLS Perf 事件有序下发

关键词:eBPF / Perf / 时间序重排 / GoTLS / 单调时间

本文说明 eCapture GoTLS 探针在 Perf 事件路径上,为缓解多 CPU / 合并读序与用户态探针时间序不一致而采用的设计思想、实现要点、验证方式与结论。

PR:https://github.com/gojue/ecapture/pull/978


1. 设计思想

1.1 要解决什么问题

GoTLS 明文事件通过 bpf_perf_event_output 进入 Perf 环形缓冲区,用户态程序通过 perf.Reader 读取。读取顺序由内核调度和用户态读取逻辑共同决定,并不保证 与探针内 bpf_ktime_get_ns() 记录的事件发生时间一致。

如果直接按照读取顺序分发事件,输出的文本、pcap 文件或下游处理逻辑会看到时间错乱或因果颠倒的片段,给问题排查和协议重组带来困难。

1.2 不解决什么问题(边界)

本机制不承诺「同一进程内任意两条事件在输出时间线上严格等于物理发生顺序」。原因在于:重排器采用有界缓冲区加固定滞后窗口的设计,那些严重迟到且时间戳较旧的事件,仍可能出现在已经发出的较新事件之后。

本机制也不替代上层按连接、文件描述符或数据流方向所做的应用层字节流重组。HTTP/1、HTTP/2、gRPC 等协议的重组仍由上层按业务键分桶后自行处理。

简而言之:保证批内有序,而非全局单调。

1.3 核心手段

在内核生成的事件中携带三个稳定的排序键:

  • mono_ns :单调纳秒时间戳(与 bpf_ktime_get_ns 同源)
  • emit_cpu:事件输出时的 CPU 编号
  • seq:每个 CPU 上独立递增的序号

在用户态分发事件之前,增加一个有界重排步骤:按照 (mono_ns, emit_cpu, seq) 的字典序排序后,再依次交给 Dispatcher。


2. 实现方案(概要)

层次 内容
eBPF go_tls_event 结构体中增加 seqemit_cpu 字段;写入事件时填充 bpf_ktime_get_ns()bpf_get_smp_processor_id() 以及 per-CPU 递增序号
解码 GoTLSDataEvent::DecodeFromBytes 保持与 C 结构体布局一致;保留 BpfMonoNs 字段,该字段与线路上 ts_ns 同源,避免后续被墙钟换算覆盖后丢失排序依据
比较器 LessGoTLSDataEventByPerfOrder(a, b):优先比较 BpfMonoNs,其次比较 EmitCPU,最后比较 Seq
重排器 goTLSPerfReorder 维护一个缓冲区,设置滞后窗口(lag,默认 10 毫秒)。当缓冲区内最大单调时间与最小单调时间之差达到 lag 时,将满足 mono_ns ≤ max_mono − lag 的事件划出,排序后作为一批返回。缓冲区过长时触发压力 flush(例如发掉一半)。读取循环退出时调用 flushAll 清空剩余事件
读循环 独立 goroutine 执行:Read → Decode → reorder.push → 对非空 batch 记录 Debug 日志 → 按序 Dispatch

3. 逻辑说明

3.1 数据流

用户态
内核
uprobe/uretprobe
填充 ts_ns seq emit_cpu 等
bpf_perf_event_output
perf.Reader.Read
Decode GoTLSDataEvent
reorder.push
按 mono cpu seq 排序一批
Dispatcher.Dispatch

3.2 「一批」的定义

一批指的是:某次调用 reorder.push(event) 后,缓冲区满足 flush 条件,重排器将若干事件按 LessGoTLSDataEventByPerfOrder 排序后,一次性连续 Dispatch 出去的那组事件。

触发 flush 的典型条件:

  • 基于滞后窗口 :缓冲区中最早与最晚事件的 BpfMonoNs 差值达到 lag(默认 10 毫秒)时,将所有满足 mono_ns ≤ max_mono − lag 的事件划为一批发出,其余事件保留在缓冲区中
  • 压力 flush:缓冲区事件数量超过阈值(如 1024)时,排序后发掉一半
  • 退出时 flush :读取 Perf 的循环退出时调用 flushAll,将剩余事件全部排序后发出

一批是重排器内部的一次输出动作,不代表一个 TCP 连接、一个 HTTP 请求或其他业务概念。

3.3 批内有序与批间回跳

  • 批内顺序 :严格遵循 LessGoTLSDataEventByPerfOrder 定义的字典序
  • 批间关系 :不保证上一批的 last_mono ≤ 下一批的 first_mono

滞后窗口与迟到事件共同作用下,可能出现跨批的时间戳回跳现象。这属于设计边界,而非缺陷。若业务需要严格全局全序,需采用更大滞后窗口、按序阻塞直至超时等更强策略。

3.4 与墙钟时间的关系

展示或落盘时可能将 ts_ns 转换为墙钟时间,但排序必须以 BpfMonoNs(单调时间)为准 ,不能使用可能被改写后的 Timestamp 字段。


4. 配置说明

重排功能默认关闭,需要显式开启。

配置项 说明 默认值
--perf-reorder 是否开启 Perf 事件排序 false(关闭)
--perf-reorder-lag-ms 滞后窗口大小(毫秒) 10

配置示例:

bash 复制代码
# 开启重排,使用默认滞后窗口 10ms
./ecapture gotls --elfpath=/proc/2998/root/usr/bin/caddy --perf-reorder

# 开启重排,自定义滞后窗口为 20ms
./ecapture gotls --elfpath=/proc/2998/root/usr/bin/caddy --perf-reorder --perf-reorder-lag-ms 20

JSON 配置方式:

json 复制代码
{
  "perf_reorder": true,
  "perf_reorder_lag_ms": 10
}

5. 验证方案

5.1 测试环境

  • 测试对象:Nextcloud(网盘)+ Caddy(GoTLS 反向代理)
  • 测试操作:上传 1MB 的 pdf、xlsx、docx 文件 → 删除所有文件 → 重复三次
  • 选择原因:HTTP/2 多路复用 + 文件传输容易复现 Perf 乱序问题

5.2 验证命令

bash 复制代码
# 开启重排功能
./ecapture gotls --elfpath=/proc/2998/root/usr/bin/caddy --btf=1 --debug --perf-reorder -m text 2>&1 | tee ./new-ecapture.txt
# 关闭重排功能
./ecapture gotls --elfpath=/proc/2998/root/usr/bin/caddy --btf=1 --debug -m text 2>&1 | tee ./new-ecapture.txt

5.3 验证脚本

verify_gotls_reorder_log.py

python 复制代码
#!/usr/bin/env python3
# Verify GoTLS ordering from eCapture debug logs and compare baseline vs optimized runs.
#
# Metrics:
#   - Adjacent descents: count of i with keys[i-1] > keys[i] (rare on raw perf; order is
#     already mostly monotone). NOT the same as total disorder.
#   - Global inversion pairs: all i<j with keys[i] > keys[j] (O(n^2); capped by _GLOBAL_INV_N_MAX).
#
# Fair A/B (same load, two captures):
#   python3 scripts/verify_gotls_reorder_log.py \\
#     --baseline baseline.txt --compare optimized.txt
#
# baseline.txt: ecapture gotls ... --debug  (no --perf-reorder)
# optimized.txt: ecapture gotls ... --debug --perf-reorder
#
# Single file (legacy):
#   python3 scripts/verify_gotls_reorder_log.py capture.txt

from __future__ import annotations

import argparse
import re
import sys
from typing import List, Tuple

RE_BATCH = re.compile(
    r".*batch_size=(?P<bs>\d+)\s+"
    r"first_emit_cpu=(?P<fcpu>\d+)\s+first_mono_ns=(?P<fmono>\d+)\s+first_seq=(?P<fseq>\d+)\s+"
    r"last_emit_cpu=(?P<lcpu>\d+)\s+last_mono_ns=(?P<lmono>\d+)\s+last_seq=(?P<lseq>\d+)"
)

RE_DISPATCH = re.compile(
    r"gotls perf dispatch \((?P<label>[^)]+)\)\s+"
    r"mono_ns=(?P<mono>\d+)\s+"
    r"emit_cpu=(?P<cpu>\d+)\s+"
    r"seq=(?P<seq>\d+)"
)

RE_ANSI = re.compile(r"\x1b\[[0-9;]*m")


def strip_ansi(s: str) -> str:
    return RE_ANSI.sub("", s)


def key(mono: int, cpu: int, seq: int) -> tuple:
    return (mono, cpu, seq)


def parse_batches(lines: List[str]):
    batches = []
    for i, line in enumerate(lines, 1):
        line = strip_ansi(line)
        if "gotls reorder: emitting batch" not in line:
            continue
        m = RE_BATCH.search(line)
        if not m:
            continue
        batches.append(
            {
                "line": i,
                "batch_size": int(m["bs"]),
                "first": key(int(m["fmono"]), int(m["fcpu"]), int(m["fseq"])),
                "last": key(int(m["lmono"]), int(m["lcpu"]), int(m["lseq"])),
            }
        )
    return batches


def parse_dispatch_by_label(
    lines: List[str], want_label: str
) -> Tuple[List[tuple], List[int]]:
    keys: List[tuple] = []
    line_nums: List[int] = []
    for i, line in enumerate(lines, 1):
        line = strip_ansi(line)
        if "gotls perf dispatch (" not in line:
            continue
        m = RE_DISPATCH.search(line)
        if not m or m.group("label") != want_label:
            continue
        keys.append(
            key(int(m["mono"]), int(m["cpu"]), int(m["seq"]))
        )
        line_nums.append(i)
    return keys, line_nums


def count_violations(keys: List[tuple], line_nums: List[int]):
    violations = []
    for i in range(1, len(keys)):
        if keys[i - 1] > keys[i]:
            violations.append(
                (line_nums[i - 1], line_nums[i], keys[i - 1], keys[i])
            )
    return violations


# Max n for O(n^2) global inversion count (adjust if too slow on huge logs).
_GLOBAL_INV_N_MAX = 12_000


def count_global_inversion_pairs(keys: List[tuple]) -> Tuple[int, int]:
    """Count pairs (i, j) with i < j and keys[i] > keys[j] (total inversions vs sorted order).

    This can be far larger than *adjacent* descents: one element out of place may create
    only one adjacent descent but many global inversion pairs.
    Returns (inversion_count, total_pairs) where total_pairs = n*(n-1)//2.
    """
    n = len(keys)
    if n < 2:
        return 0, 0
    total_pairs = n * (n - 1) // 2
    inv = 0
    for i in range(n):
        ki = keys[i]
        for j in range(i + 1, n):
            if ki > keys[j]:
                inv += 1
    return inv, total_pairs


def analyze_file(path: str, print_batches: bool) -> int:
    raw = open(path, encoding="utf-8", errors="replace").read()
    lines = raw.splitlines()

    batches = parse_batches(lines)
    no_reorder_k, no_reorder_ln = parse_dispatch_by_label(lines, "no reorder")
    after_k, after_ln = parse_dispatch_by_label(lines, "after reorder")

    exit_code = 0

    if print_batches and batches:
        bad_intra = [b for b in batches if b["first"] > b["last"]]
        bad_cross = []
        for i in range(1, len(batches)):
            prev_last = batches[i - 1]["last"]
            cur_first = batches[i]["first"]
            if prev_last > cur_first:
                bad_cross.append(
                    (i, batches[i - 1]["line"], batches[i]["line"], prev_last, cur_first)
                )

        print(f"[reorder batches] parsed {len(batches)} lines")
        print(
            f"  intra-batch (first <= last): {'OK' if not bad_intra else 'FAIL'} ({len(bad_intra)} bad)"
        )
        for b in bad_intra[:10]:
            print(
                f"    line {b['line']} batch_size={b['batch_size']} first={b['first']} last={b['last']}"
            )
        print(
            f"  cross-batch (prev_last <= next_first): "
            f"{'OK' if not bad_cross else 'WARN'} ({len(bad_cross)} inversions)"
        )
        for item in bad_cross[:15]:
            idx, la, lb, pl, cf = item
            print(
                f"    between batch idx {idx - 1}->{idx} (lines {la}->{lb}): last={pl} first={cf}"
            )
        if bad_intra:
            exit_code = 1

    def report_dispatch(keys: List[tuple], line_nums: List[int], title: str) -> int:
        if not keys:
            return 0
        viol = count_violations(keys, line_nums)
        n = len(keys)
        adj_pairs = max(1, n - 1)
        adj_rate = 100.0 * len(viol) / adj_pairs
        print(f"{title} parsed {n} dispatches")
        print(
            f"  adjacent descents (keys[i-1] > keys[i] only): "
            f"{'OK' if not viol else 'FAIL vs probe key'} "
            f"--- {len(viol)} / {adj_pairs} adjacent pairs ({adj_rate:.4f}%)"
        )
        print(
            "    note: raw perf order is already mostly time-ordered; only a few adjacent "
            "steps need CPU/interleave fixes, so this count stays small."
        )
        if n <= _GLOBAL_INV_N_MAX:
            g_inv, g_tot = count_global_inversion_pairs(keys)
            g_rate = 100.0 * g_inv / g_tot if g_tot else 0.0
            print(
                f"  global inversion pairs (all i<j with keys[i]>keys[j]): "
                f"{g_inv} / {g_tot} unordered pairs ({g_rate:.4f}% of all pairs)"
            )
            print(
                "    this matches intuition better for 'how messy' the whole stream is "
                "(sorted sequence would be 0 / total)."
            )
        else:
            print(
                f"  global inversion pairs: skipped (n={n} > {_GLOBAL_INV_N_MAX}; "
                "would be O(n^2); use a smaller log or raise _GLOBAL_INV_N_MAX)"
            )
        for la, lb, ka, kb in viol[:15]:
            print(f"    lines {la}->{lb}: {ka} > {kb}")
        if len(viol) > 15:
            print(f"    ... and {len(viol) - 15} more")
        return 1 if viol else 0

    if no_reorder_k:
        if report_dispatch(
            no_reorder_k,
            no_reorder_ln,
            "[no userland reorder --- raw perf dispatch order]",
        ):
            exit_code = 1

    if after_k:
        if report_dispatch(
            after_k,
            after_ln,
            "[after userland reorder --- dispatch order]",
        ):
            exit_code = 1

    if (
        not batches
        and not no_reorder_k
        and not after_k
    ):
        print(
            "no parsable GoTLS ordering lines.\n"
            "  Need DBG lines:\n"
            "    gotls perf dispatch (no reorder) mono_ns=... emit_cpu=... seq=...\n"
            "    and/or gotls perf dispatch (after reorder) ...\n"
            "  Run: sudo ./ecapture gotls ... --debug [--perf-reorder]"
        )
        return 1

    return exit_code


def compare_files(baseline: str, optimized: str, strict: bool) -> int:
    def load_dispatch(path: str, label: str):
        lines = open(path, encoding="utf-8", errors="replace").read().splitlines()
        keys, lnums = parse_dispatch_by_label(lines, label)
        viol = count_violations(keys, lnums)
        n = len(keys)
        pairs = max(1, n - 1)
        rate = 100.0 * len(viol) / pairs
        if n <= _GLOBAL_INV_N_MAX:
            g_inv, g_tot = count_global_inversion_pairs(keys)
        else:
            g_inv, g_tot = -1,0
        return n, len(viol), rate, g_inv, g_tot

    b_n, b_v, b_r, b_gi, b_gt = load_dispatch(baseline, "no reorder")
    o_n, o_v, o_r, o_gi, o_gt = load_dispatch(optimized, "after reorder")

    print("=== Fair compare (same workload recommended) ===")
    print(f"baseline (no --perf-reorder): {baseline}")
    print(f"  dispatches={b_n}  adjacent descents={b_v}  rate={b_r:.4f}% of adjacent pairs")
    if b_gi >= 0 and b_gt > 0:
        print(
            f"  global inversion pairs={b_gi} / {b_gt} "
            f"({100.0 * b_gi / b_gt:.4f}% of all pairs)"
        )
    print(f"optimized (--perf-reorder):      {optimized}")
    print(f"  dispatches={o_n}  adjacent descents={o_v}  rate={o_r:.4f}% of adjacent pairs")
    if o_gi >= 0 and o_gt > 0:
        print(
            f"  global inversion pairs={o_gi} / {o_gt} "
            f"({100.0 * o_gi / o_gt:.4f}% of all pairs)"
        )
    print("---")
    if b_n == 0:
        print("ERROR: baseline log has no 'gotls perf dispatch (no reorder)' lines.", file=sys.stderr)
        return 2
    if o_n == 0:
        print(
            "ERROR: optimized log has no 'gotls perf dispatch (after reorder)' lines.",
            file=sys.stderr,
        )
        return 2
    delta = b_v - o_v
    if delta > 0:
        print(
            f"Conclusion: violations reduced by {delta} (baseline {b_v} → optimized {o_v})."
        )
    elif delta == 0:
        print("Conclusion: same violation count (try longer/heavier load or check logs).")
    else:
        print(
            f"Note: optimized has more violations ({o_v} vs {b_v}); lag reorder can still invert across batches."
        )

    if strict and o_v >= b_v:
        return 1
    return 0


def main() -> int:
    ap = argparse.ArgumentParser(description="Verify GoTLS perf dispatch ordering from eCapture logs.")
    ap.add_argument("logfile", nargs="?", help="single log to analyze")
    ap.add_argument("--baseline", metavar="FILE", help="log from run without --perf-reorder")
    ap.add_argument("--compare", metavar="FILE", help="log from run with --perf-reorder")
    ap.add_argument(
        "--strict",
        action="store_true",
        help="with --baseline/--compare, exit 1 unless optimized violations < baseline",
    )
    ap.add_argument(
        "--no-batch-report",
        action="store_true",
        help="with single file, skip gotls reorder batch line report",
    )
    args = ap.parse_args()

    if args.baseline and args.compare:
        return compare_files(args.baseline, args.compare, args.strict)

    if args.logfile:
        return analyze_file(args.logfile, print_batches=not args.no_batch_report)

    ap.print_help()
    return 2


if __name__ == "__main__":
    raise SystemExit(main())

5.4 测试结果

指标 old(无 reorder) new(有 reorder)
dispatch 条数 2774 2833
相邻逆序 10(0.36%) 6(0.21%)
全局逆序对占比 3.43% 2.44%

6. 总结

维度 内容
目标 缓解 GoTLS Perf 路径上读序与探针单调时间序不一致的问题,提升默认输出的可读性与下游处理的友好度
手段 内核侧为事件附加 (ts_ns, emit_cpu, seq) 三元组,用户态通过滞后有界重排后分发
保证 批内顺序与 LessGoTLSDataEventByPerfOrder 一致;不保证任意两条事件在全时间线上严格有序
使用建议 上层使用方应按连接、方向、数据流等业务键分桶,桶内使用同一套键进行协议级重组。强合规场景需自行评估滞后窗口边界,或通过增大缓冲区、调整策略来满足需求

7. 上层应用建议(eCapture 之外)

  1. 不要假设 eCapture 输出为严格全局全序
  2. 按业务键分桶 (五元组、pid+fd、读写方向等),桶内按 mono_ns → emit_cpu → seq 排序或归并
  3. READ 与 WRITE 事件使用独立的状态机或缓冲区,避免将双向 TLS 明文混在一起处理
  4. 若需要可审计的因果顺序 ,应在架构层面显式接受或缓解迟到事件的风险,例如:
    • 增大滞后窗口
    • 设置超时强制 flush
    • 引入端到端的序列号

8. Mac 打包capture踩坑记录

8.1 背景

测试环境为 Linux AMD64,但由于 AWS 上的 Linux 机器无法访问 Docker Hub,且 clang、Go 版本老旧难以升级,因此选择在 Mac 上用 Docker Ubuntu 进行交叉打包。

提示:eCapture 最好在原生 Linux 上打包。Mac 打包会因缺少头文件和 Docker 网络导致各种问题

8.2 搭建 Docker 镜像环境

bash 复制代码
# 在本地 ecapture 目录下执行
docker run -it \
  --name ebpf-builder \
  -v $(pwd):/workspace \
  --platform linux/amd64 \
  --privileged \
  ubuntu:22.04 \
  bash

# 安装依赖
apt-get update
apt-get install -y --no-install-recommends \
    make git wget \
    clang-14 llvm-14 \
    libbpf-dev \
    linux-tools-common linux-tools-generic \
    linux-headers-generic \
    gcc libelf-dev libz-dev pkg-config
		file libpacp-dev

# 创建符号链接
ln -sf /usr/bin/clang-14 /usr/bin/clang
ln -sf /usr/bin/llc-14 /usr/bin/llc
ln -sf /usr/bin/clang++-14 /usr/bin/clang++

# 安装 Go(需匹配 ecapture go.mod 中指定的版本)
wget --no-check-certificate https://go.dev/dl/go1.24.3.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.24.3.linux-amd64.tar.gz
rm go1.24.3.linux-amd64.tar.gz

# 设置环境变量
export PATH=$PATH:/usr/local/go/bin
export GOARCH=amd64
export ARCH=x86_64

# 打包
make all

# 打包后的二进制文件位于bin/ecapture

8.2.2 copy core btf文件vmlinux.h

复制代码
# 在真实的 AMD64 Linux 机器上执行

sudo apt-get install -y linux-tools-common linux-tools-generic bpftool
# 生成 vmlinux.h
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
# 查看文件大小确认生成成功
ls -lh vmlinux.h
wc -l vmlinux.h

# 拷贝放到/kern/bpf/x86/vmlinux.h

8.3 遇到的问题及解决方案

8.3.1 问题一:找不到 Linux 内核头文件(nocore 版本依赖)

现象make all 时报错找不到 kconfig.h 等头文件

原因:Mac Orbstack Ubuntu 22.04 使用的是宿主机提供的自定义内核,而非 Ubuntu 官方内核

解决方案 :由于我们使用的是 core 版本(不需要 nocore 的头文件依赖),可以跳过 nocore 编译

8.3.2 问题二:缺少 bison/yacc/flex等libpacp.a的依赖

现象

解决方案

bash 复制代码
apt-get install -y libpcap-dev

在搭建镜像环境时已经下载了,正常不会再出现这个问题

8.3.3 问题三:Go bindata 工具缺失

现象

解决方案

我有vpn,在容器外:go install github.com/shuLhan/go-bindata/cmd/go-bindata@latest

容器内因为网络问题会下载失败,也有其他的解决方案。

8.3.4 问题四:go版本不匹配

现象

解决方案

卸载go1.20.0,在容器中通过wget下载解压使用ecapture go.mod中的版本,即go1.24.3

在搭建镜像环境时已经下载了,正常不会再出现这个问题;如果下的不对,参考搭建镜像环境中的go下载和配置

8.4 core 与 nocore 版本区别

Core(默认 make ebpf / make all 里的 CO-RE 路径)(core: Compile Once -- Run Everywhere)

  • 头文件:用 vmlinux.h(一般由 bpftool btf dump ... /sys/kernel/btf/vmlinux 生成),配合 bpf_core_read.h 等。
  • 读内核结构:用 BPF_CORE_READ / PT_REGS_*_CORE(见 kern/go_argument.h),带 BTF 重定位,加载时按实际运行内核的类型信息做适配。
  • 编译:clang -target bpfel 直接产出 CO-RE 用的 *_core.o
  • 适用:运行机内核 带 BTF(常见 CONFIG_DEBUG_INFO_BTF=y),希望同一份字节码尽量跨小版本内核跑。

Nocore(make nocore / ebpf_noncore

  • 宏:编译时定义 DNOCOREkern/ecapture.h 走 内核源码/构建目录里的 linux/types.h 等,不包含 vmlinux.h 那套 CO-RE 头。
  • 读寄存器/结构:直接按头文件里的布局访问,例如 ((x)->ax)PT_REGS_PARM1(x),没有 BPF_CORE_READ 那层重定位。
  • 编译:依赖 KERN_BUILD_PATH / KERN_SRC_PATH 下的一串 include(Makefile 里那一长串 -I),再 llc -march=bpf 生成 *_noncore.o
  • 适用:没有 BTF 或必须和某套固定内核头文件严格对齐的老内核/特殊环境;代价是更绑编译时的内核版本。

8.5 犟种尝试:编译 nocore 版本(失败)

问题的起因是 make all 时报错缺少 kconfig.h(详见 8.3.1)。但即便搞定这个文件,还有一整套头文件依赖等着填坑。

8.5.1 尝试一:官方初始化脚本(AI 推荐的)

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

结果:报错依旧,问题没解决。

8.5.2 尝试二:安装 Ubuntu 内核头文件

复制代码
apt-get update
apt-get install -y linux-headers-$(uname -r)

结果:失败。原因是 Mac Orbstack Ubuntu 22.04 使用的是宿主机提供的自定义内核,不是 Ubuntu 官方内核,所以找不到对应的头文件包

8.5.3 尝试三:安装 Ubuntu 默认通用头文件

复制代码
apt-get install -y linux-headers-generic

# 查找头文件位置
find /usr/src -name "kconfig.h" 2>/dev/null

# 拷贝放到/kern/bpf/x86/linux/kconfig.h
# 创建软链到其他问题没用哈
8.5.3.1 补上 kconfig.h 依赖的 generated/autoconf.h
复制代码
mkdir -p ./kern/bpf/x86/generated

# 创建空的 autoconf.h
cat > ./kern/bpf/x86/generated/autoconf.h << 'EOF'
#ifndef __AUTOCONF_H__
#define __AUTOCONF_H__

/* Empty autoconf.h for eBPF compilation */
#define CONFIG_X86_64 1
#define CONFIG_64BIT 1

#endif
EOF
8.5.3.2 解决完 kconfig.h,又来 types.h

types.h 背后还挂着一堆头文件依赖。我也想过直接从 Linux 源码里把需要的头文件都拷过来,但考虑到版本不匹配的风险和投入产出比,最终放弃编译 nocore 版本

结论:在非原生 Linux 环境(尤其是 Mac Docker)上强行编译 nocore 版本,性价比极低。有这功夫,直接打 core 版本就完事了。

8.6 另一种编译 core 版本的方式(未走完)

这趟走到 make lib/libpcap.a 成功了,理论上能走通。但既然 make all 更省事,就不折腾手动了

Bash 复制代码
make clean
make ebpf    # 只生成 bytecode/*_kern_core.o

# 只把 *_core.o 打进 assets(官方 Makefile 的 assets 会强制 ebpf_noncore,所以要手写一步)
go run github.com/shuLhan/go-bindata/cmd/go-bindata \
  -ignore '.*_less52\.o' \
  -pkg assets -o assets/ebpf_probe.go \
  ./bytecode/*_core.o

sed -i '1s/^\/\/ Code generated/\/\/go:build ebpfassets\n\/\/ Code generated/' assets/ebpf_probe.go
# 先编 libpcap(与 Makefile 里一致)
make lib/libpcap.a

# 再手动 go build(照 functions.mk 里参数),并指定只用 core字节码:
# BYTECODE_FILES 需与 cli 里约定一致,常见是 core 或 all,看你仓库 cli 实现
CGO_ENABLED=1 \
CGO_CFLAGS='-O2 -g -gdwarf-4 -I$(pwd)/lib/libpcap/' \
CGO_LDFLAGS='-O2 -g -L$(pwd)/lib/libpcap/ -lpcap -static' \
go build -trimpath -buildmode=pie -mod=readonly \
  -tags 'linux,netgo,ebpfassets,dynamic' \
  -ldflags "-w -s -X 'github.com/gojue/ecapture/cli/cmd.ByteCodeFiles=core' ..." \
  -o bin/ecapture ./cli
相关推荐
mounter62517 小时前
【内核前沿】从 veth 到 netkit:深度解析 TCP devmem 穿透容器屏障的“队列租赁”黑科技
网络·ebpf·linux kernel·devmem tcp·netkit·队列租赁
程序猿编码8 天前
eBPF代理:让SSH进程“溯源”,找到背后的客户端IP
linux·tcp/ip·ssh·ebpf
淡泊if1 个月前
eBPF 实战:一次诡异的 Nginx 高延迟,我用 5 分钟在内核里找到了真凶
java·运维·nginx·微服务·ebpf
geshifei2 个月前
Sched ext回调3——select_cpu(linux 6.15.7)
linux·ebpf
JiMoKuangXiangQu3 个月前
Linux eBPF 案例:sk_filter 读取 IP 地址崩溃
linux·ebpf·sk_filter
geshifei3 个月前
Sched ext回调1——init_task (linux 6.15.7)
linux·ebpf
JiMoKuangXiangQu3 个月前
Linux eBPF 错误:invalid bpf_context access
linux·ebpf
晨欣4 个月前
后 Sidecar 时代:深度解析 eBPF 与 Sidecar 模式的架构之争(Gemini 3 Pro Preview 回答)
网络安全·云原生·架构·ebpf
晨欣4 个月前
[eBPF硬核] Gemini阿吉学习笔记:Tetragon企业版两类核心日志 & 冷热数据分流架构设计 & 学习资源推荐
笔记·学习·云原生·云安全·ebpf·谷歌gemini