如果你曾经在内核模块里用过
container_of(),就知道 C
代码依赖编译期的类型信息——offsetof(struct task_struct, pid)
在编译时就已经固定。BPF 程序在内核中运行时没有
libc、没有头文件、没有
<stddef.h>,但它需要读
struct task_struct 的某个字段、需要知道
struct sock 的 sk_state 是
unsigned char 还是
int、需要理解一个函数签名来判断 helper
调用的类型匹配。
这些信息都来自 BTF(BPF Type Format)——一种为 BPF
定制的二进制类型编码格式。它小到可以放在内核镜像里(vmlinux
BTF 经 btf_dedup() 去重后通常在 1–2 MB
量级;下文涉及 vmlinux 体积的数字均来自典型 x86_64
构建且开启 CONFIG_DEBUG_INFO_BTF
时的经验值,具体大小因内核配置和架构而异),紧凑到可以快速解析(单遍扫描),表达能力足够覆盖
C
语言的类型系统(结构体、联合体、枚举、函数原型、typedef、类型修饰符)。
本文从 struct btf_header
的二进制布局开始,逐步拆解 20 种 BTF type kind
的编码方式,讲清 BTF.ext 的 func_info 和 line_info
如何连接指令偏移与类型信息,最后追踪内核构建流程中 pahole 的
DWARF-to-BTF 转换和 btf_dedup()
的去重算法。源码基于 kernel 6.6 的
include/uapi/linux/btf.h 和
tools/lib/bpf/btf.c。
一、为什么 BTF 取代了 DWARF
DWARF(Debugging With Attributed Record Formats)是 C/C++
编译器生成的标准调试信息格式。Linux 内核的
CONFIG_DEBUG_INFO=y 会在 vmlinux 中生成 DWARF
调试信息。但 DWARF 在 BPF 场景下有三个硬伤:
| 维度 | DWARF | BTF |
|---|---|---|
| 大小 | vmlinux DWARF 可达 ~120 MB(典型 x86_64 +
CONFIG_DEBUG_INFO_BTF 构建) |
vmlinux BTF ~1–2 MB(去重后,同上构建条件) |
| 解析复杂度 | 需要处理复杂的 DIE 树、跨 CU 引用、属性缩写 | 单一结构体数组,单遍线性扫描 |
| 内核内解析 | 不可行(内存占用 + 复杂度) | 可行(kernel/bpf/btf.c 在加载时解析) |
| 表示能力 | C/C++ 所有特性 | C 语言子集(足以覆盖内核类型系统) |
| 自包含性 | 需要调试信息解析库(libdw/libdwarf) | 自描述文件头,无需外部库 |
BPF 需要类型信息在内核中可用——verifier
通过 BTF 验证 fentry 的 callback
签名、bpftool map dump 通过 BTF 将 map value
人性化展示、CO-RE 重定位通过 BTF
跨内核版本计算字段偏移。这些场景都要求一个比 DWARF 小 100
倍、解析简单得多的格式。
BTF 的设计目标不是通用的调试格式,而是 BPF 程序的元数据格式。它只编码 BPF 程序需要的那部分类型信息:基本类型的大小和符号属性、复合类型的成员偏移、函数签名、类型名称映射。
二、BTF 二进制布局
BTF 在 ELF 文件中是一个独立的
section(.BTF),在内存中是一个连续字节块。整个格式由三个顺序部分构成:header、type
section、string section。
graph TD
subgraph BTF_FILE["BPF ELF .o 文件中的 .BTF 节"]
HDR["struct btf_header<br/>magic: 0xeb9f<br/>version: 1<br/>hdr_len: 24<br/>type_off: 24<br/>type_len<br/>str_off<br/>str_len"]
TYPES["Type Section<br/>struct btf_type 数组<br/>每个 12 字节<br/>可能后跟 variable-length data"]
STRINGS["String Section<br/>\0 分隔的字符串表<br/>type entries 以 offset 引用"]
end
HDR --> TYPES --> STRINGS
2.1 btf_header
struct btf_header 定义在
include/uapi/linux/btf.h,固定 24 字节(按
32-bit 对齐),出现在 BTF 数据块的最前部:
/* include/uapi/linux/btf.h (kernel 6.6) */
struct btf_header {
__u16 magic; /* 魔数:0xeb9f */
__u8 version; /* 版本:当前为 1 */
__u8 flags; /* 标志位,当前为 0 */
__u32 hdr_len; /* sizeof(struct btf_header) + 可变长度数据(若有) */
__u32 type_off; /* 类型节的起始偏移(从 BTF 数据起始算起) */
__u32 type_len; /* 类型节的字节长度 */
__u32 str_off; /* 字符串节的起始偏移 */
__u32 str_len; /* 字符串节的字节长度 */
};关键语义:
magic = 0xeb9f:这个魔数的选择来自 Alexei Starovoitov 提交时的 arbitrary 值,区分于其他数据格式。hdr_len >= 24:当 BTF 新增 header 字段时为大于 24 的值,老解析器按hdr_len跳过自己不认识的部分保持前向兼容。type_off = hdr_len:当前实现中类型节紧接 header 之后。str_off = type_off + type_len:字符串节紧接类型节之后。
2.2 btf_type
每个类型以 struct btf_type 12
字节的固定头部开始,后面可能跟随变长数据(成员列表、枚举值列表、参数列表等):
/* include/uapi/linux/btf.h (kernel 6.6) */
struct btf_type {
__u32 name_off; /* 类型名称在字符串表中的偏移(0 表示匿名类型) */
__u32 info; /* 编码:kind + vlen + kind_flag */
__u32 size; /* 类型大小(结构体/联合体/枚举等),或 "type" 字段 */
};info 字段的位布局需要解码:
/* tools/lib/bpf/btf.h */
#define BTF_INFO_KIND(info) (((info) >> 24) & 0x1f) /* bits 24-28: kind (5 bits) */
#define BTF_INFO_VLEN(info) ((info) & 0xffff) /* bits 0-15: vlen (16 bits) */
#define BTF_INFO_KFLAG(info) ((info) >> 31) /* bit 31: kind_flag */- kind(5
bits):类型种类(
BTF_KIND_INT,BTF_KIND_STRUCT, 等 20 种),定义在include/uapi/linux/btf.h的enum btf_kind中; - vlen(16 bits):变长成员数量——如结构体的字段数、函数的参数数;
- kind_flag(1 bit,bit
31):扩展标志,用在
BTF_KIND_STRUCT/BTF_KIND_UNION中,指示成员偏移量编码方式(0为 32-bit 偏移,1使用BTF_MEMBER_BITFIELD_SIZE记录高 8 位作为位域大小); - kflag(= kind_flag):存在是因为 BTF 的 vlen 只需要 16 位而 type kind 只有 5 位,剩下 11 位中的最高位用来承载 extra flag。
size 字段的含义随 kind
变化:
| kind | size 含义 |
|---|---|
BTF_KIND_INT |
整数类型的总字节大小 |
BTF_KIND_STRUCT /
BTF_KIND_UNION |
结构体/联合体的总字节大小 |
BTF_KIND_ENUM /
BTF_KIND_ENUM64 |
枚举的底层类型大小 |
BTF_KIND_PTR / BTF_KIND_FWD /
BTF_KIND_TYPEDEF |
0(不适用) |
BTF_KIND_ARRAY |
0,数组的 element type / nelems 存在 vlen data 中 |
BTF_KIND_FUNC |
<linkage>(符号链接属性) |
BTF_KIND_FUNC_PROTO |
无关紧要 |
BTF_KIND_VAR |
变量大小 |
BTF_KIND_DATASEC |
节的字节大小 |
2.3 字符串表
字符串表是一个以 \0
结尾的连续字节区域,存放在 BTF
数据块的尾部。name_off
是类型名称在字符串表中的起始偏移——与 ELF 的字符串表不同,BTF
字符串没有全局去重的要求(虽然 btf_dedup
会合并相同字符串),解析时按 name_off
直接读取,读到下一个 \0 为止。
例如,name_off = 42
意味着类型名称的起始地址为
btf_data + str_off + 42。
三、20 种 BTF Type Kind 编码
3.1 基本类型:BTF_KIND_INT
BTF_KIND_INT 编码 C
语言的整数类型(char, short,
int, long,
unsigned long, bool
等)。size 字段给出字节大小(1/2/4/8),vlen
data 包含一个 32-bit 的 encoding 字段:
/* include/uapi/linux/btf.h - BTF_INT_* 编码位 */
#define BTF_INT_ENCODING(VAL) (((VAL) & 0x0f000000) >> 24)
#define BTF_INT_OFFSET(VAL) (((VAL) & 0x00ff0000) >> 16)
#define BTF_INT_BITS(VAL) ((VAL) & 0x000000ff)
/* 编码标志 */
#define BTF_INT_SIGNED (1 << 0) /* 有符号 */
#define BTF_INT_CHAR (1 << 1) /* char 或类似 */
#define BTF_INT_BOOL (1 << 2) /* bool */例子——unsigned int(32-bit 无符号整数): -
name_off 指向字符串 "unsigned int"
- info 中
kind=BTF_KIND_INT, vlen=1(1 个 encoding 值) -
size=4 - vlen data 中的 encoding
值:bits(32) | offset(0) | (BTF_INT_SIGNED 未置位)
3.2 指针/引用:BTF_KIND_PTR
最简单的一种——size 为
0(真正的类型信息在被指向的类型中),vlen 为 0,vlen data
为空。指向目标类型的 type_id 存放在
btf_type->size 后面的隐含 4 字节中:
struct btf_type // 12 字节
__u32 type; // 隐含的 4 字节:被指向类型的 type_id解析器需要根据 btf_type
的长度判断是否有这个尾部字段——BTF_KIND_PTR 的
vlen=0 且 total size = 16(12 + 4)。
3.3 数组:BTF_KIND_ARRAY
编码 C
语言的数组(int arr[10])。size
字段为 0。vlen data 包含一个
struct btf_array:
/* include/uapi/linux/btf.h (kernel 6.6) */
struct btf_array {
__u32 type; /* 元素类型 ID */
__u32 index_type; /* 索引类型 ID(通常是 'unsigned int' 的 BTF type_id) */
__u32 nelems; /* 元素个数 */
};对于 int[5]:type 指向
int 类型的 BTF type_id,index_type
指向 __u32 对应的 BTF_KIND_INT
entry,nelems=5。
3.4 结构体:BTF_KIND_STRUCT
编码 C 语言的结构体。size
字段给出总字节大小。vlen 表示成员数量,vlen data 包含
vlen 个 struct btf_member:
/* include/uapi/linux/btf.h (kernel 6.6) */
struct btf_member {
__u32 name_off; /* 成员名称在字符串表中的偏移 */
__u32 type; /* 成员类型 ID */
__u32 offset; /* 从结构体起始到该成员的位偏移 */
};offset
的单位是位(bit),不是字节。对于正常的非位域成员,offset
是字节偏移量乘以 8。对于位域成员,offset 的低
24 位表示位偏移,高 8 位表示位域大小(当
kind_flag 设置时):
offset 编码(kind_flag=1 时):
bits [31:24]: 位域大小(0 表示非位域)
bits [23:0]: 位偏移(从结构体起始)
3.5 联合体:BTF_KIND_UNION
与 BTF_KIND_STRUCT
几乎相同,唯一的区别是所有成员的 offset 都是
0(因为联合体成员共享起始地址),size
是最大成员的大小。
3.6 枚举:BTF_KIND_ENUM 与 BTF_KIND_ENUM64
编码 C 语言的枚举。vlen data 包含 vlen 个
struct btf_enum 或
struct btf_enum64:
/* include/uapi/linux/btf.h */
struct btf_enum {
__u32 name_off; /* 枚举值名称 */
__s32 val; /* 枚举值(32-bit 有符号) */
};
struct btf_enum64 {
__u32 name_off;
__u32 val_lo32; /* 低 32-bit */
__u32 val_hi32; /* 高 32-bit */
};BTF_KIND_ENUM64(6.0+)解决了传统
BTF_KIND_ENUM 无法表示 64-bit
枚举值的问题——在内核中带有 U64_MAX
等值的枚举需要 64 位宽度。
3.7 前向声明:BTF_KIND_FWD
编码 C
语言的前向声明(struct task_struct;)。name_off
指向类型名称,size=0(前向声明没有大小信息),kind_flag
指示是 struct(0)还是
union(1)。
3.8 typedef:BTF_KIND_TYPEDEF
编码 C 语言的
typedef(typedef int pid_t)。size=0,隐含的尾部
__u32 type 指向被定义的类型。
3.9 类型修饰符:CONST / VOLATILE / RESTRICT / TYPE_TAG
这四种修饰符都是对另一个类型的包裹:
BTF_KIND_CONST:const int→CONST(INT)BTF_KIND_VOLATILE:volatile int→VOLATILE(INT)BTF_KIND_RESTRICT:int *restrict中的 restrictBTF_KIND_TYPE_TAG:__user、__rcu等 attribute 标签
编码方式与 BTF_KIND_PTR 相同——隐含的尾部
__u32 type
指向被修饰的类型,name_off
指向类型标签的名称(如 "__user")。
3.10 函数相关:BTF_KIND_FUNC 与 BTF_KIND_FUNC_PROTO
BTF_KIND_FUNC 编码内核中的函数符号(如
tcp_connect)。name_off
指向函数名,隐含的尾部 __u32 type 指向对应的
BTF_KIND_FUNC_PROTO。
BTF_KIND_FUNC_PROTO
编码函数签名。size 表示参数数量。vlen data 包含
vlen 个
struct btf_param,每个参数为一个
(name_off, type_id) 对:
/* include/uapi/linux/btf.h */
struct btf_param {
__u32 name_off; /* 参数名称(可以是 0 表示未命名) */
__u32 type; /* 参数类型 ID */
};隐含的尾部 __u32 type 指向返回类型。
int tcp_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
在 BTF 中编码为:
FUNC_PROTO(返回类型=INT, 参数数=3)
param[0]: name="sk", type=PTR→STRUCT("sock")
param[1]: name="uaddr", type=PTR→STRUCT("sockaddr")
param[2]: name="addr_len", type=INT
FUNC(name="tcp_connect", type=FUNC_PROTO)3.11 全局变量与节:BTF_KIND_VAR 与 BTF_KIND_DATASEC
BTF_KIND_VAR 编码全局变量(如 BPF 程序的全局
data/rodata 变量)。name_off
指向变量名,隐含尾部 __u32 type
指向变量类型,额外的尾部 __u32 linkage(1 =
static,2 = global,3 = extern)。
BTF_KIND_DATASEC 编码 ELF Section
中的变量布局。size 是节的字节大小,vlen data
包含 vlen 个
struct btf_var_secinfo:
/* include/uapi/linux/btf.h */
struct btf_var_secinfo {
__u32 type; /* BTF_KIND_VAR 的 type_id */
__u32 offset; /* 变量在节中的字节偏移 */
__u32 size; /* 变量占用的字节大小 */
};skeleton 中的
skel->rodata、skel->data
等指针就是通过 DATASEC
解析得到的——它告诉用户态代码每个全局变量在 BPF
程序的数据区中的精确偏移和大小。
3.12 浮点与声明标签
BTF_KIND_FLOAT(5.1+):编码float(size=4)和double(size=8)。虽然 BPF 指令集不支持浮点操作,但 BTF 需要能够描述内核数据结构中的float/double字段。BTF_KIND_DECL_TAG(5.16+):声明级别的注解(如__attribute__((btf_decl_tag("...")))),用于给 map、程序、变量附加 key-value 元数据。内核通过bpf_core_decl_tag宏在编译时转换为BTF_KIND_DECL_TAG条目。
3.13 完整的 type kind 表
| kind 枚举 | 数值 | 引入版本 | vlen data 结构体 | 用途 |
|---|---|---|---|---|
BTF_KIND_INT |
1 | 本源 | u32 encoding |
整数、char、bool |
BTF_KIND_PTR |
2 | 本源 | u32 type |
指针 |
BTF_KIND_ARRAY |
3 | 本源 | struct btf_array |
数组 |
BTF_KIND_STRUCT |
4 | 本源 | struct btf_member[] |
结构体 |
BTF_KIND_UNION |
5 | 本源 | struct btf_member[] |
联合体 |
BTF_KIND_ENUM |
6 | 本源 | struct btf_enum[] |
枚举(32-bit) |
BTF_KIND_FWD |
7 | 本源 | 无 | 前向声明 |
BTF_KIND_TYPEDEF |
8 | 本源 | u32 type |
typedef |
BTF_KIND_VOLATILE |
9 | 本源 | u32 type |
volatile 修饰 |
BTF_KIND_CONST |
10 | 本源 | u32 type |
const 修饰 |
BTF_KIND_RESTRICT |
11 | 本源 | u32 type |
restrict 修饰 |
BTF_KIND_FUNC |
12 | 5.0 | u32 type |
函数符号 |
BTF_KIND_FUNC_PROTO |
13 | 5.0 | struct btf_param[] |
函数签名 |
BTF_KIND_VAR |
14 | 5.1 | u32 type + u32 linkage |
全局变量 |
BTF_KIND_DATASEC |
15 | 5.1 | struct btf_var_secinfo[] |
节内变量布局 |
BTF_KIND_FLOAT |
16 | 5.1 | 无 | 浮点类型 |
BTF_KIND_DECL_TAG |
17 | 5.16 | u32 type + s32 idx |
声明注解 |
BTF_KIND_TYPE_TAG |
18 | 5.17 | u32 type |
类型标签 |
BTF_KIND_ENUM64 |
19 | 6.0 | struct btf_enum64[] |
枚举(64-bit) |
四、BTF.ext 节:连接指令与类型
.BTF.ext 是 ELF 文件中独立于
.BTF 的节,包含三类扩展信息:
/* include/uapi/linux/btf.h */
struct btf_ext_header {
__u16 magic; /* 0xeb9f */
__u8 version;
__u8 flags;
__u32 hdr_len;
__u32 func_info_off; /* func_info 子节的偏移 */
__u32 func_info_len;
__u32 line_info_off; /* line_info 子节的偏移 */
__u32 line_info_len;
__u32 core_relo_off; /* CO-RE 重定位子节的偏移(第 12 篇详述) */
__u32 core_relo_len;
};4.1 func_info
func_info 将 BPF 程序中的函数指令偏移映射到
BTF 中的函数类型 ID。内核 verifier 在加载时使用
func_info 来确定 BPF_PSEUDO_CALL
目标函数的签名——这使得 verifier 能够像检查 helper
调用一样检查程序内调用:
/* 对每个 BPF 子程序 */
struct bpf_func_info {
__u32 insn_off; /* 函数起始指令的偏移(从程序第一条指令算起) */
__u32 type_id; /* 此函数在 BTF 中的 BTF_KIND_FUNC type_id */
};4.2 line_info
line_info 将 BPF 程序的每条指令映射回源码的
"file:line:column\“。它的作用是让 verifier
日志输出人类可读的源码位置,而不是只打印指令编号:
/* 对每条 BPF 指令 */
struct bpf_line_info {
__u32 insn_off; /* 指令偏移 */
__u32 file_name_off; /* 文件名在 BTF 字符串表中的偏移 */
__u32 line_off; /* 行号在 BTF 字符串表中的偏移(存为字符串) */
__u32 line_col; /* 低 10 位: 列号, 高 22 位: 行号 */
};没有 line_info 时,verifier 日志长这样:
0: (b7) r1 = 0
1: (73) *(u8 *)(r10 -1) = r1
2: (bf) r2 = r10
...
有 line_info 后:
; u8 key = 0;
0: (b7) r1 = 0
; map_val = bpf_map_lookup_elem(&my_map, &key);
1: (73) *(u8 *)(r10 -1) = r1
2: (bf) r2 = r10
这在调试大型 BPF 程序时是不可或缺的。
4.3 CO-RE 重定位条目
core_relo 子节存储面向 CO-RE
的重定位条目(struct bpf_core_relo),包含指令偏移、访问路径、重定位种类等信息。libbpf
在加载时使用这些条目修补 BPF 指令。详细机制见第 12 篇。
五、内核 BTF 的生成管线
5.1 构建流程
内核 BTF 的生成是构建流程的一部分,需要在
CONFIG_DEBUG_INFO_BTF=y 下进行:
kernel compile (gcc/clang + -g)
→ vmlinux(含 DWARF .debug_info 节, ~120 MB)
→ pahole --btf_encode_detached
→ vmlinux.btf(BTF 编码, ~5 MB 原始)
→ libbpf btf_dedup()
→ vmlinux.btf(去重后, ~1–2 MB,典型 x86_64 + CONFIG_DEBUG_INFO_BTF)
→ 嵌入 vmlinux image
→ 挂载到 /sys/kernel/btf/vmlinux关键步骤:
- 内核编译:
gcc -g或clang -g生成 DWARF 调试信息,嵌入 vmlinux ELF; - pahole
转换:构建系统(
scripts/link-vmlinux.sh)调用pahole --btf_encode_detached,从 vmlinux 的 DWARF 中提取类型信息,编码为 BTF 格式; - btf_dedup 去重:
pahole内部链接 libbpf,调用btf_dedup()将原始 BTF(包含因头文件重复包含产生的大量重复类型)压缩到最终大小; - 嵌入 vmlinux:去重后的 BTF 通过
objcopy --add-section .BTF=vmlinux.btf嵌入 vmlinux ELF; - 运行时访问:内核启动后将 vmlinux BTF
暴露到
/sys/kernel/btf/vmlinux。
5.2 模块 BTF
每个内核模块也会生成自己的 BTF(如果
CONFIG_DEBUG_INFO_BTF_MODULES=y),存储在模块的
ko 文件中,加载时通过
/sys/kernel/btf/<模块名> 暴露。模块 BTF
的 type_id 会进行偏移调整——模块的第一个类型紧接 vmlinux BTF
的最后一个类型之后,形成统一的类型命名空间。
六、btf_dedup:BTF 去重算法
6.1 为什么需要去重
内核源码中的同一个类型可能出现在多个地方:
// include/linux/types.h
typedef unsigned int __u32; // type_id=100
// include/linux/counter.h (includes types.h)
typedef unsigned int __u32; // type_id=1500
// arch/x86/include/asm/page.h (includes types.h)
typedef unsigned int __u32; // type_id=3000在 DWARF 中,这三个 __u32
分别对应不同的编译单元(CU),pahole 在转换时会把每个 DWARF
DIE 生成一个独立的 BTF type
entry。如果不去重,三个完全相同的 __u32 typedef
会浪费类型空间和字符串表空间。
6.2 算法原理
btf_dedup()(tools/lib/bpf/btf.c)基于结构等价性(structural
equivalence)进行去重:
哈希阶段:为每个类型计算一个哈希值——对
INT类型,哈希输入为(name_off, size, encoding);对STRUCT类型,哈希输入为(name_off, size, member_count, 每个成员的 (name_off, type_id, offset))。递归依赖的 type_id 在哈希时使用当前的临时映射。等价性映射阶段:对每个哈希桶中的类型进行等价性比较。两个
STRUCT类型被认为等价当且仅当:- 名称相同(或两者都为匿名)
- 大小相同
- 成员数量相同
- 每个对应成员的名称、类型(经过映射后)、偏移量相同
重映射阶段:创建从旧 type_id 到新 type_id 的映射表,所有引用旧 type_id 的地方替换为新 type_id。
压缩阶段:移除重复的 type entry,重新编号,生成紧凑的 BTF。
6.3 去重的边界
去重不会合并名称不同但结构相同的类型。struct {int x; int y;}
如果分别以 point 和 vector
命名,去重后仍是两个独立类型——名称是结构等价性判断的输入之一。这是刻意的设计选择:合并不同名称的类型会导致调试工具(bpftool btf dump format c)输出混乱(类型名丢失)。
去重的一个副作用是消除 "forward declaration"
冲突——如果一个 FWD 类型指向一个后续定义的
STRUCT,去重会将 FWD 直接映射到
STRUCT 的 type_id,消除间接引用。
七、常用操作:通过 BTF 访问内核类型
7.1 从 /sys/kernel/btf/vmlinux 获取 BTF
# 查看 vmlinux BTF 的大小
ls -lh /sys/kernel/btf/vmlinux
# 导出为原始二进制
cp /sys/kernel/btf/vmlinux /tmp/vmlinux.btf
# 查看 BTF 中的所有类型
bpftool btf dump file /sys/kernel/btf/vmlinux | head -100
# 生成 vmlinux.h
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h7.2 查找特定类型
# 在 BTF 中查找 struct task_struct
bpftool btf dump file /sys/kernel/btf/vmlinux | grep -A 50 'STRUCT "task_struct"'
# 查找特定函数签名
bpftool btf dump file /sys/kernel/btf/vmlinux | grep -A 20 'FUNC_PROTO.*tcp_connect'
# 查看所有枚举
bpftool btf dump file /sys/kernel/btf/vmlinux | grep '^\[.*\] ENUM '7.3 检查模块 BTF
# 列出所有可用的 BTF
ls /sys/kernel/btf/
# 查看特定模块的类型
bpftool btf dump file /sys/kernel/btf/nf_tables | head -50八、小结
BTF 是 eBPF 基础设施的类型语言。从
struct btf_header 的固定 24 字节头,到 20 种
type kind 的变长编码,再到 BTF.ext 的
func_info/line_info 指令级映射——这条格式管线支撑了 verifier
的类型检查、bpftool 的人性化输出、CO-RE
的跨版本字段定位。内核构建流程中 pahole 的 DWARF-to-BTF
转换将 120 MB 的调试信息压缩到 1-2
MB,btf_dedup()
的结构等价性去重让这个压缩成为可能。下一篇文章进入 CO-RE
重定位引擎——BTF 的下游消费者,讲清 libbpf 如何使用 BTF
在加载时修补 BPF 指令来实现在不同内核版本上的可移植性。
参考
- Linux 内核源码
include/uapi/linux/btf.h(kernel 6.6):struct btf_header、struct btf_type、BTF_KIND_* 枚举及其成员结构体 - Linux 内核源码
tools/lib/bpf/btf.c:btf_dedup()去重算法实现 - Linux 内核源码
tools/lib/bpf/btf.h:BTF 解析宏(BTF_INFO_KIND()、BTF_INFO_VLEN()) - Linux 内核文档
Documentation/bpf/btf.rst:BTF 格式规范 - Linux 内核文档
Documentation/bpf/bpf_devel_QA.rst:BTF 生成的编译要求 - pahole 源码(
dwarves项目):DWARF 到 BTF 的编码器实现
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【eBPF 内核实现深度拆解】CO-RE 重定位引擎:libbpf 的运行时指令修补
从 clang 内置函数 __builtin_preserve_access_index 出发,追踪 BPF_CORE_READ 等宏如何生成 BTF.ext CO-RE 重定位记录,再到 libbpf 加载时 bpf_core_apply_relo() 根据目标内核 BTF 计算正确字段偏移量并修补 BPF 指令——可移植 BPF 的核心引擎。
【eBPF 内核实现深度拆解】从验证器到 JIT,从 BTF 到调度器
eBPF 内核虚拟机内部实现系统讲解:BPF 指令集与寄存器机器、验证器的抽象解释与状态裁剪、JIT 编译器后端、Map 各类型的并发与内存模型、helper 函数注册与类型检查、BTF 格式规范与 CO-RE 重定位引擎、libbpf 加载器工程、fentry/fexit 蹦床机制、sched_ext 调度器内核接口。面向想读懂 eBPF 内核源码、写生产级 BPF 程序的系统工程师。
【eBPF 内核实现深度拆解】实战:构建微型 eBPF 可观测 Agent
把 01--17 的知识串成一条实践线——从 libbpf skeleton 写第一个 BPF 程序、加载到内核、用 ring buffer 回传事件、用 CO-RE 实现跨内核版本兼容、map pinning 实现热升级、配上半自动化的 verifier 错误排障流程——构建一个麻雀虽小五脏俱全的 eBPF 可观测 Agent。
【eBPF 内核实现深度拆解】BPF 指令集解码:寄存器机器、调用约定与指令编码
从 eBPF 虚拟机的 11 个 64-bit 寄存器和 struct bpf_insn 出发,逐条拆解 ALU64/ALU32、跳转、加载存储、call 四类指令的字段语义与编码格式,建立后续 verifier 和 JIT 讨论的精确基础。