Linux 内核 eBPF BTF 与 bpftool 深度实战:从类型基因组到跨内核可观测架构

当 eBPF 程序需要读取内核结构体字段时,最朴素的做法是硬编码偏移量——这在 struct task_struct 字段重排时立即崩溃。2018 年引入的 BPF Type Format(BTF)以及配套的 bpftool 工具链,从根本上解决了这个可移植性难题。本文将深入 BTF 的二进制编码格式、CO-RE 重定位机制、bpftool 的高级用法,以及生产环境中利用 BTF 进行内核结构分析和跨版本调试的完整工作流。

一、BTF 的动机与设计

eBPF 程序运行在内核态,却常需要访问内核数据结构。面对如下现实:

  • 不同内核版本的 struct sock 布局不同
  • 各家发行版对同版本内核打了不同补丁
  • 内核配置项(CONFIG_*)开关影响结构体成员是否有条件编译
  • 同一字段在不同架构上的偏移量可能不同

传统方案必须为每个目标内核编译专属 eBPF 二进制,这意味着观测工具需要持续跟踪数千个内核变体。BTF 让 vmlinux 自带完整的类型描述信息,eBPF 程序在加载时才"知道"字段在哪里。

BTF 的核心思路是:将 DWARF 调试信息中的类型部分精简后,以一种紧凑的二进制格式嵌入到 .BTF 和 .BTF.ext ELF Section 中。内核在加载 eBPF 程序时,程序自带的 BTF 与目标内核的 vmlinux BTF 进行"重定位"(relocation),自动修正所有结构体字段访问的偏移量。

二、BTF 二进制格式详解

2.1 文件结构一览

BTF 二进制由一个 header、type section 和 string section 组成:

[ BTF Header (16 bytes) ]
[ Type Section (可变长度的 type 记录链) ]
[ String Section (NUL 分隔的名称字符串表) ]

struct btf_header(来自 include/uapi/linux/btf.h):

struct btf_header {
    __u16 magic;          // 0xeB9F (little endian)
    __u8  version;        // 当前为 1
    __u8  flags;          // 0 = 无附加信息
    __u32 hdr_len;        // sizeof(struct btf_header)
    __u32 type_off;       // type section 相对 header 的偏移
    __u32 type_len;       // type section 的长度(字节)
    __u32 str_off;        // string section 偏移
    __u32 str_len;        // string section 长度
};

2.2 BTF Type 记录链

BTF 的类型系统用一条链式编码表示。每个 type 记录的前缀是:

struct btf_type {
    __u32 name_off;       // 在 string section 中的偏移(0 = 匿名类型)
    __u32 info;           // [31:24]=vlen, [23:16]=kind_flag, [15:0]=kind
    union {
        __u32 type;       // 指向另一个 type 的 ID (作为引用)
        __u32 size;       // 类型大小(仅 BTF_KIND_INT/ENUM/STRUCT/UNION/DATASEC)
    };
};

// info 的编码方式:
// kind = (info >> 16) & 0x3f;  // 14 种基本类型种类
// vlen = (info >>> 24);         // 变长字段数
// kind_flag = (info >>> 23) & 1; // 对 struct/union 表示 fxx_0 是 signed unsigned

14 种 btf_kind:

BTF_KIND_UNKN    (0)  — 未知类型(占位)
BTF_KIND_INT     (1)  — 整型, 含编码(无符号/有符号/布尔/字符)
BTF_KIND_PTR     (2)  — 指针
BTF_KIND_ARRAY   (3)  — 数组(索引类型+元素类型+元素个数)
BTF_KIND_STRUCT  (4)  — 结构体(vlen 个成员)
BTF_KIND_UNION   (5)  — 联合体
BTF_KIND_ENUM    (6)  — 枚举
BTF_KIND_FWD     (7)  — 前向声明
BTF_KIND_TYPEDEF (8)  — typedef
BTF_KIND_VOLATILE(9)  — volatile 限定
BTF_KIND_CONST   (10) — const 限定
BTF_KIND_RESTRICT(11) — restrict 限定
BTF_KIND_FUNC    (12) — 函数(返回类型 ID)
BTF_KIND_FUNC_PROTO(13) — 函数原型(参数链)
BTF_KIND_VAR     (14) — 全局变量
BTF_KIND_DATASEC (15) — 数据段(vlen 个 VAR)

2.3 BTF_KIND_INT 的编码细节

BTF_KIND_INT 单独编码整型的"编码"语义:

// btf_type.size 字段存放:
//   [15:0]    = 以 bit 为单位的大小
//   [18:16]   = 编码方式
//   [31:24]   = 从 LSB 开始的 bit offset(用于位域)

// BTF_INT_ENCODING 枚举:
#define BTF_INT_SIGNED  (1 << 0)
#define BTF_INT_CHAR    (1 << 1)
#define BTF_INT_BOOL    (1 << 2)

例如 __u64 的 BTF 编码为: size=64 bits, encoding=0。char 的 BTF 编码为: size=8, encoding=BTF_INT_CHAR。

2.4 结构体成员的 BTF 表示

以一个 struct sock 的子集为例,假设内核中该结构体定义了前两个字段:

// BTF 的伪二进制表示:
BTF_KIND_STRUCT "sock" vlen=2
  member[0]: name="__sk_common", type_id=15, offset=0
  member[1]: name="sk_type",     type_id=20, offset=1152

其中 offset 以 bit 为单位(除以 8 即为字节偏移)。每个成员额外记录 name_off(指向 string section)、type_id(指向实际类型节点)、offset(在结构体中的 bit offset)。

2.5 String Section 的设计

String section 是一张以 NUL 字节分隔的扁平字符串表。所有跨类型引用的名称(类型名、成员名)都通过 name_off 索引到该 section。这种设计将名称与类型结构解耦,多个类型可以共享一个字符串。

使用 bpftool btf dump raw 可以直接阅读 BTF 的二进制细节:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format raw | head -30
[1] TYPEDEF 'list_head' type_id=4
[4] STRUCT 'list_head' size=16 vlen=2
        'next' type_id=8 bits_offset=0
        'prev' type_id=8 bits_offset=64
[8] TYPEDEF 'list_head' type_id=11
[11] PTR '(anon)' type_id=12
[12] STRUCT 'list_head' ...

三、BTF 在内核中的生成机制

3.1 pahole 工具

BTF 的生成由 pahole(dwarves 包中的工具)完成。编译内核时启用 CONFIG_DEBUG_INFO_BTF,构建系统调用 pahole -J vmlinux 将 DWARF 调试信息转换为 BTF 格式。

pahole 的工作流程:

  1. 读取 vmlinux ELF 中的 .debug_info DWARF section
  2. 遍历每个 DIE(Debugging Information Entry)
  3. 将 DWARF 的递归类型展开为 BTF 的扁平 type chain(处理 typedef 链展开、前向声明、循环引用)
  4. 如遇到 DWARF 中不完整的类型(如 Opacity pointer 外部符号),插入 BTF_KIND_FWD 节点
  5. 输出为 .BTF ELF section,并更新原始的 vmlinux

大多数现代发行版(Ubuntu 22.04+、RHEL 9+、Arch)默认开启 BTF。可通过以下命令验证:

$ ls -la /sys/kernel/btf/vmlinux
# 若文件存在,表示内核已内嵌 BTF

3.2 BTF 在 eBPF 程序中的嵌入

使用 libbpf 编译 eBPF C 源码时,头文件 vmlinux.h(由 bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 生成)导出所有内核类型定义。

编译产物中 .BTF section 存放该程序引用的类型信息,.BTF.ext section 存放每个 eBPF 指令的重定位记录。

// 示例: eBPF CO-RE 程序
#include "vmlinux.h"
#include "bpf_helpers.h"

SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(trace_tcp_sendmsg, struct sock *sk) {
    // 使用 BPF_CORE_READ 宏通过 BTF 安全读取字段
    u16 family = BPF_CORE_READ(sk, skc_family);
    bpf_printk("tcp_sendmsg: family=%d\n", family);
    return 0;
}

char _license[] SEC("license") = "GPL";

编译后的二进制中,skc_family 字段的读取会通过一条带有 BTF 重定位记录的指令完成。这条记录告诉 libbpf 在加载时需要"查询运行中内核中 struct sock 的 skc_family 字段偏移"。

四、CO-RE 重定位机制深度解析

4.1 重定位记录结构

每个包含 BPF_CORE_READ 的指令在 .BTF.ext section 中有一条对应的 bpf_core_relo 记录:

struct bpf_core_relo {
    __u32 insn_idx;       // 需要重定位的指令索引
    __u32 type_id;        // 被访问类型的 BTF type_id
    __u32 access_str_off; // 重定位字符串表中的键(如 "skc_family")
    enum bpf_core_relo_kind kind; // FIELD_BYTE_OFFSET 等
};

重定位字符串是一个由类型 ID 和字段名组成的表达式,例如 "0::sock::skc_family" 表示"类型 ID 为 0 的 struct,其子结构体 sock 的 skc_family 字段"。

4.2 运行时重定位流程

当 bpf_object__load() 执行时,libbpf 执行以下步骤:

  1. 向内核发送 BPF_BTF_LOAD system call 加载程序 BTF 到内核
  2. 遍历每个重定位记录
  3. 在内核 vmlinux BTF 中查找匹配的类型和字段
  4. 计算该字段在内核中相对于结构体首地址的 bit offset
  5. 将该 offset 直接 patch 到对应 eBPF 指令的 imm 字段中
  6. 最后通过 BPF_PROG_LOAD 加载 eBPF 程序

关键的是第三步的匹配逻辑:libbpf 同时维护用户侧和内核侧的两棵 BTF 类型树,执行字段名、类型大小、整型编码的三重匹配。编译时可配合 -g 生成更精确的重定位信息。

4.3 BPF_CORE_READ 系列宏

libbpf 的 bpf_core_read.h 提供了一组强大宏:

// 简单读取
#define BPF_CORE_READ(dst, src, a) \
    bpf_probe_read_kernel(dst, sizeof(*(dst)), &(src)->a)

// 复杂层级访问: BPF_CORE_READ_INTO
BPF_CORE_READ_INTO(&pid, task, tgid);

// 指针追踪: BPF_CORE_READ_PTR_INTO
BPF_CORE_READ_PTR_INTO(&addr, sk_common, skc_v6_rcv_saddr);

// 跨版本条件访问: BPF_CORE_READ_BITFIELD_PROBED
u16 flags = BPF_CORE_READ_BITFIELD_PROBED(xdp, flags);

// 字段存在性检查(替代 ifdef __KERNEL__ 的条件编译)
if (bpf_core_field_exists(task->real_cred)) { ... }
if (bpf_core_enum_value_exists(enum bpf_func_id, BPF_FUNC_ringbuf_output)) { ... }

bpf_core_field_exists() 在内核 BTF 中执行即时查找——返回 true/false 表示当前运行内核中该字段是否存在。这是跨内核兼容性的基石。

4.4 处理内核字段重命名的案例

实际工程中常见场景:某字段从 task->real_parent 重命名。CO-RE 提供了两种处理策略:

// 策略 1: 字段存在性探测
if (bpf_core_field_exists(task->real_parent))
    parent = BPF_CORE_READ(task, real_parent);
else
    parent = BPF_CORE_READ(task, parent);

// 策略 2: 使用 BTF 的 SKIP 重定位失败模式
// 通过 bpf_core_relo_failure_handler 回调处理
struct external_override {
    __u32 new_offset __kconfig;
};

五、bpftool 工具链详解

bpftool 是 eBPF 生态中最强大的运维诊断工具。以下按场景介绍核心子命令。

5.1 透视已加载的 eBPF 程序

# 列出所有 BPF 程序
$ bpftool prog show
123: kprobe  name trace_tcp_sendmsg  tag abc123def  gpl
        loaded_at 2024-11-15T08:30:00+0000  uid 0
        xlated 384B  jited 296B  memlock 4096B  map_ids 45,67
        btf_id 89

# 查看 JIT 机器码
$ bpftool prog dump xlated id 123
   0: *(u64 *)(r10 -8) = r1      // ctx 保存栈
   1: r1 = *(u64 *)(r1 +0)       // struct sock *sk
   2: r2 = *(u16 *)(r1 +1152)    // sk->skc_family (BTF relocated!)
   ...

# 查看 JIT 后的原生机器码(x86_64)
$ bpftool prog dump jited id 123
   0:  push %rbp
   1:  mov %rsp,%rbp
   ...

# 附加 BTF 信息的源码级反汇编
$ bpftool prog dump xlated id 123 visual > out.dot
$ dot -Tpng out.dot -o prog_flow.png

visual 输出一个 Graphviz dot 文件,可渲染为控制流图(CFG),对分析验证器决策与逻辑路径极为有用。

5.2 操作 BPF Map

# 列出所有 Map
$ bpftool map show
45: hash  name conn_track  flags 0x0
        key 16B  value 8B  max_entries 16384  memlock 524288B
        btf_id 89

# 查看 BTF 类型化的 key/value 信息
$ bpftool map dump id 45
key:
00 1f 90 ac d1 01 00 00  00 1f 90 18 9c 06 00 00
value:
1c 00 00 00 00 00 00 00

# 使用 BTF-friendly human readable 输出(需 vmlinux BTF)
$ bpftool map dump id 45 -p
{
    "key": {
        "saddr": "172.16.1.208",
        "daddr": "10.144.31.0"
    },
    "value": {
        "bytes": 28
    }
}

-p 选项结合 BTF 信息,自动将 Map 的 key/value 按类型结构解析输出,对 eBPF Map 调试意义重大。

5.3 检查 BTF 类型信息

# 从内核 vmlinux 导出全部 C 头文件
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# 查看某个具体的 BTF 类型
$ bpftool btf dump id 891 type sock
[456] STRUCT 'sock' size=912 vlen=67
        'skc_family' type_id=20 bits_offset=6688
        ...

# 查看 eBPF 程序附带的 BTF
$ bpftool btf dump id 89
[1] PTR '(anon)' type_id=2
[2] FUNC_PROTO '(anon)' ...
[3] FUNC 'trace_tcp_sendmsg' type_id=2 linkage=global

# 过滤特定类型前缀
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c | grep struct sock

5.4 进程级别的 eBPF 状态

# 查看某进程加载了哪些 BPF 程序
$ bpftool net show
xdp:
ens33 generic  id 234

cgroup:
cgroup2 /sys/fs/cgroup/unified  id 235

perf:
pid 1843  prog_id 123  kprobe tcp_sendmsg
pid 5678  prog_id 456  tracepoint sched_switch

# cgroup attach 详情
$ bpftool cgroup tree
/sys/fs/cgroup/unified
    egress: id 235  name limit_egress

# 全系统网络挂钩
$ bpftool net show --dev ens33

5.5 批量操作与运行态直方图

# 导出 Map 数据到 JSON(含时序分析)
$ bpftool map dump id 45 --json > map_dump.json

# 直方图 Map 的特有展示
$ bpftool map dump id 46 --histogram
     latency           : count     distribution
         0 - 1         : 2856     |****************************************|
         2 - 3         :  342     |****                                    |
         4 - 7         :  198     |**                                      |
         8 - 15        :   97     |*                                       |
        16 - 31        :   45     |                                        |
        32 - 63        :   23     |                                        |
        64 - 127       :   12     |                                        |
       128 - 255       :    5     |                                        |

# 结合 jq 的实时过滤
$ bpftool prog show --json | jq '.[] | select(.type=="xdp")'

5.6 内核 BTF 信息查询

# 搜索内核中的结构体定义
$ bpftool btf dump file /sys/kernel/btf/vmlinux format c -j | \
    jq '.types[] | select(.name == "struct task_struct")'

# 获取 __ksym 导出的内核符号
$ bpftool kallsyms show
ffffffff818b3560 T tcp_sendmsg
ffffffff819f1230 T __netif_receive_skb_core

# 计算某符号在 BTF 函数原型中的参数类型
$ bpftool btf dump id 0 | grep -A5 "tcp_sendmsg"

六、生产级调试与诊断场景

6.1 定位内核结构体字段变化

当 eBPF 程序迁移到新内核版本后字段访问失败时:

# Step 1: 确认源内核的字段 offset
$ bpftool btf dump id 891 type sock | grep skc_family
        'skc_family' type_id=20 bits_offset=6688

# Step 2: 确认目标内核的 struct 定义
$ bpftool btf dump file /sys/kernel/btf/vmlinux-B type_id=456 format raw | grep -i "family"

# Step 3: 使用重定位字符串定位问题
# 若目标内核中 skc_family 被移动到 6768 bits,则需更新 CO-RE 代码

6.2 快速生成 vmlinux.h

开发新 eBPF 工具前,先为当前运行内核生成 vmlinux.h:

# 在线生成(仅需 /sys/kernel/btf/vmlinux)
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

# 验证生成的头文件包含所需类型
grep -c "struct sock" vmlinux.h

# 针对离线 vmlinux 文件生成
bpftool btf dump file ./vmlinux-5.15.0-91-generic format c > vmlinux-5.15.h

6.3 分析验证器拒绝原因

当 eBPF 程序被验证器拒绝时,BTF 可辅助分析:

$ bpftool prog load kprobe.o /sys/fs/bpf/prog
libbpf: prog 'trace_func': BPF program load failed: Permission denied
libbpf: -- BEGIN DUMP LOG ---
...
R1 type=fp expected=ctx
-- END DUMP LOG --

# 使用 BTF-based visual dump 分析
$ bpftool prog dump xlated pinned /sys/fs/bpf/prog visual > fail.dot
dot -Tpng fail.dot -o fail.png

虽然这不是 BTF 的直接应用,但 .visual dump 依赖 BTF 信息输出可读的字段名(而非裸寄存器偏移)。

6.4 跨内核性能采样对比

BTF 驱动的 Map 输出可以精确对比不同内核版本的延迟分布差异:

# 内核 5.15
$ bpftool map dump id 46 --histogram > latency_5.15.txt

# 内核 6.1
$ bpftool map dump id 78 --histogram > latency_6.1.txt

# 两者使用相同的 eBPF CO-RE 代码编译产物,BTF 重定位在不同带宽上生效

6.5 排查 eBPF 程序内存泄漏

# 检查每个程序的 memlock 占比
$ bpftool prog show | awk '{print $1, $(NF-3)}' | sort -k2 -rn | head

# 检查 Map 的元素数量与占用
for id in $(bpftool map show -j | jq -r '.[].id'); do
    echo "Map $id:"
    bpftool map dump id $id 2>/dev/null | grep -c "^key:"
done

七、高级场景:BTF 数据段与自定义类型

7.1 定义自定义 BTF 类型供内核使用

eBPF 程序可以自带 BTF 数据段(DATASEC),用于传递给内核 BPF 子系统或辅助 Map 值的类型推断:

// 在 BSS 段声明内核可见的全局变量
volatile const u32 MY_THRESHOLD = 1000;  // const 编译器会放入 .rodata (DATASEC)

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __type(key, u32);
    __type(value, u64);
} perf_output_map SEC(".maps");

// perf_submit_skb() 利用 BTF 中的 DATASEC 类型记录自定义 metadata
bpf_perf_event_output(ctx, &perf_output_map, BPF_F_CURRENT_CPU,
                      &data, sizeof(data));

BTF.ext 中的 line_info 行号映射可以将 eBPF 指令与源码对应,在验证器日志中直接显示触发拒绝的源代码行号。

7.2 利用 BTF 注释向 Map 注入类型信息

Map 创建时附带 BPF_F_BTF flag 可以让 BPF Map 的 key/value 共享 BTF 类型 ID:

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __uint(map_flags, BPF_F_NO_PREALLOC);
    __type(key, struct conn_key);
    __type(value, struct conn_stats);
    __uint(btf_key_type_id, 1);
    __uint(btf_value_type_id, 2);
} conn_map SEC(".maps");

// 之后 bpftool 会自动使用这些 type_id 解析 Map 内容

7.3 Kubernetes 环境中的 BTF 分发

在 K8s 上运行 CO-RE eBPF 工具时,需确保节点的 /sys/kernel/btf/vmlinux 可用:

# DaemonSet spec:
volumeMounts:
  - name: kernel-btf
    mountPath: /sys/kernel/btf
    readOnly: true
volumes:
  - name: kernel-btf
    hostPath:
      path: /sys/kernel/btf

# Cilium/Hubber/Pixie 等工具均依赖此挂载
# 若内核未编译 CONFIG_DEBUG_INFO_BTF,则需降级到 BTFHub 预编译文件

BTFHub(后由 Aquasec 维护)为常见发行版内核提供了预生成的 BTF 文件,工具可以回退到此路径:/var/lib/btfhub/。

八、BTF 的局限性与未来演进

8.1 已知限制

  • 内核必须启用 CONFIG_DEBUG_INFO_BTF——这是前提条件,旧内核或精简内核可能关闭它
  • BTF 开发者工具链必须安装 pahole ≥ 1.18——老版本不支持 BTF 生成
  • 编译器优化可能省略 DWARF 信息——-g 选项必须配合,否则 pahole 无源数据可用
  • 重定位仅解决字段偏移——不解决逻辑变化(如字段类型从 int 变为 enum)

8.2 未来方向

  • BTF ext info 增强 (kernel 6.x+):携带源码行号、列号信息,调试体验逼近 DWARF
  • BTF 的 ringbuf 数据通道类型化:将 BTF 类型 ID 嵌入 ringbuf 事件,实现 receiver 端的自动类型解析
  • 用户态 BTF 生成 (ubtf):在容器环境中无法 /sys/kernel/btf/vmlinux 时的替代方案
  • BTF 与 Rust eBPF 的集成:Aya 库的 CO-RE 实现正在借鉴 BTF 思路,并探索独立于 libbpf 的 BTF 重定位逻辑
  • 跨架构 BTF:从 ARM64 主机加载 X86_64 目标的重定位记录(CO-RE 已支持跨架构)

九、完整实战案例:编写 BTF 驱动的网络延迟分析器

以下是一个完整的 CO-RE eBPF 程序,利用 BTF 在不同内核版本上可靠地读取 TCP 连接的 RTT 信息:

// tcp_rtt_tracker.bpf.c
#include "vmlinux.h"
#include "bpf_helpers.h"
#include "bpf_tracing.h"
#include "bpf_core_read.h"

struct rtt_key {
    __u32 saddr;
    __u32 daddr;
};

struct rtt_value {
    __u64 rtt_us;
    __u64 rtt_min;
    __u64 timestamp;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 16384);
    __type(key, struct rtt_key);
    __type(value, struct rtt_value);
} rtt_map SEC(".maps");

SEC("kprobe/tcp_ack_update_rtt")
int BPF_KPROBE(trace_tcp_ack, struct sock *sk, __u32 seq_rtt_us) {
    struct rtt_key key = {};
    struct rtt_value val = {};

    // BTF 驱动的安全字段读取——自动重定位
    key.saddr = BPF_CORE_READ(sk, sk_rcv_saddr);
    key.daddr = BPF_CORE_READ(sk, sk_daddr);

    // tcp_ack_update_rtt 的采样 rtt_min 在不同内核位置不同
    // 旧内核: tcp_sock.rtt_min
    // 新内核: tcp_sock.rtt_est.rtt_min
    if (bpf_core_field_exists(struct tcp_sock, rtt)) {
        val.rtt_us = BPF_CORE_READ((struct tcp_sock *)sk, rtt_us);
    } else if (bpf_core_field_exists(struct tcp_sock, rtt_est)) {
        val.rtt_us = BPF_CORE_READ((struct tcp_sock *)sk, rtt_est.rtt_us);
    } else {
        val.rtt_us = seq_rtt_us;
    }

    // rtt_min 字段跨版本通过 BTF 自动定位
    val.rtt_min = BPF_CORE_READ((struct tcp_sock *)sk, rtt_min);
    val.timestamp = bpf_ktime_get_ns();

    bpf_map_update_elem(&rtt_map, &key, &val, BPF_ANY);
    return 0;
}

char _license[] SEC("license") = "GPL";

编译与加载:

$ clang -O2 -g -target bpf -c tcp_rtt_tracker.bpf.c -o tcp_rtt_tracker.bpf.o
$ bpftool prog load tcp_rtt_tracker.bpf.o /sys/fs/bpf/tcp_rtt
$ bpftool prog attach pinned /sysfs/bpf/tcp_rtt kprobe tcp_ack_update_rtt

运行时通过 BTF 感知输出 Map 内容:

$ bpftool map dump pinned /sys/fs/bpf/rtt_map -p
{
    "key": {"saddr": "10.0.1.5", "daddr": "10.0.2.10"},
    "value": {"rtt_us": 142, "rtt_min": 98, "timestamp": 1699991234567}
}

这个例子展示了 BTF 的核心价值:一份编译产物、跨内核运行、类型安全读取。无须静态 offset、无须 #ifdef,无须重编译。

十、总结

BTF 不仅仅是 eBPF 的"元数据"——它是整个 Linux 可观测性生态的基因组。理解 BTF 的二进制格式、重定位机制和 bpftool 工具链,是编写可移植、可维护、可在生产环境长期运行的 eBPF 工具的必备能力。

推荐学习路径:

  1. 运行 bpftool btf dump id 0 浏览内核 BTF 全貌
  2. 生成 vmlinux.h,尝试修改字段名触发 CO-RE 失败体验
  3. 编写一个使用 BPF_CORE_READ 读取 struct tcp_sock 字段的 kprobe 程序
  4. 在两个不同内核版本上加载同一 CO-RE 二进制,体会 BTF 重定位的威力
  5. 研究 bpftool 输出可视化,分析自己程序的验证器路径

未来随着用户态 BTF 解码库(如 Aya-go、rust-btf)的成熟,BTF 的影响将远超 eBPF——它正在成为 Linux 内核自描述性的新标准。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部