1. 介绍
BTF(BPF Type Format)是内嵌在BPF(Berkeley Packet Filter)程序中的数据结构描述信息。BPF原本是用于数据包过滤的编程语言,但随着eBPF(extended BPF)的发展,它的用途已经扩展到多种内核子系统中,包括性能监测、网络安全和配置管理等。 BTF是为了实现更复杂的eBPF程序而设计的。其提供了一种机制,通过它可以将编程时使用的数据结构(如C语言中的结构体、联合体、枚举等)的信息嵌入到eBPF程序中。这样做的主要目的是为了让eBPF程序在运行时能够具有类型安全(Type Safety),同时也便于内核和用户空间的程序理解和操作这些数据结构。 在eBPF程序开发过程中,用户通常会在用户空间编写C代码,然后使用特定的编译器(如clang)编译这些代码为eBPF字节码。由于C程序中定义的复杂数据结构信息在编译为eBPF字节码过程中会丢失,因此BTF被设计来保留这些信息。当eBPF程序加载到内核时,BTF信息可以被内核使用,以确保程序操作的数据结构与内核预期的一致,从而保证程序的正确运行。 举个例子,如果eBPF程序需要访问内核数据结构,BTF就能够提供这些内核数据结构的确切布局,让eBPF程序能够安全而准确地读取或修改这些数据。 总之,BTF使得eBPF程序能更安全且方便地与复杂的数据类型互动,并有助于提高eBPF程序与内核间的兼容性和稳定性。
BTF(BPF 类型格式)是一种元数据格式,对与 BPF 程序 /map 有关的调试信息进行编码。BTF 这个名字最初是用来描述数据类型。后来,BTF 被扩展到包括已定义的子程序的函数信息和行信息。
调试信息可用于 map 的更好打印、函数签名等。函数签名能够更好地实现 bpf 程序/函数的内核符号。行信息有助于生成源注释的翻译字节码、JIT 代码和验证器的日志。
BTF 规范包含两个部分:
- BTF 内核 API
- BTF ELF 文件格式 内核 API 是用户空间和内核之间的约定。内核在使用之前使用 BTF 信息对其进行验证。ELF 文件格式是一个用户空间 ELF 文件和 libbpf 加载器之间的约定。
类型和字符串部分(section)是 BTF 内核 API 的一部分,描述了 bpf 程序所引用的调试信息(主要是与类型有关的)。这两个部分将在 BTF_Type_String 章节中详细讨论。
2. BTF 类型和字符串编码
文件 include/uapi/linux/btf.h 提供了关于类型/字符串如何编码的更高层次的定义。
数据块(blob)的开头必须是:
代码语言:javascript复制struct btf_header {
__u16 magic;
__u8 version;
__u8 flags;
__u32 hdr_len;
/* 所有的偏移量都是相对于这个头的末尾的字节 */
__u32 type_off; /* 类型部分的偏移量 */
__u32 type_len; /* 类型部分的长度 */
__u32 str_off; /* 字符串部分的偏移量 */
__u32 str_len; /* 字符串部分的长度 */
};
magic 数值是 0xeB9F,其在对大、小端系统上的编码有所不同,这可以用来测试 BTF 所在系统是否为大、小端系统。btf_header 被设计为可扩展的,当数据 blob 生成时, hdr_len 等于 sizeof(struct btf_header)。
2.1 字符串编码
字符串部分的第一个字符串必须以 null 结尾字符串。字符串表的其他部分有其他非 null 结尾的字符串连接而成。
2.2 类型编码
类型标识 0 是为 void 类型保留的。类型部分(section)是按顺序解析,每个类型以 ID 从 1 开始的进行编码。目前,支持以下类型:
代码语言:javascript复制#define BTF_KIND_INT 1 /* 整数 */
#define BTF_KIND_PTR 2 /* 指针 */
#define BTF_KIND_ARRAY 3 /* 数组 */
#define BTF_KIND_STRUCT 4 /* 结构体 */
#define BTF_KIND_UNION 5 /* 联合体 */
#define BTF_KIND_ENUM 6 /* 枚举 */
#define BTF_KIND_FWD 7 /* 前向引用 */
#define BTF_KIND_TYPEDEF 8 /* 类型定义 */
#define BTF_KIND_VOLATILE 9 /* VOLATILE 变量 */
#define BTF_KIND_CONST 10 /* 常量 */
#define BTF_KIND_RESTRICT 11 /* 限制性 */
#define BTF_KIND_FUNC 12 /* 函数 */
#define BTF_KIND_FUNC_PROTO 13 /* 函数原型 */
#define BTF_KIND_VAR 14 /* 变量 */
#define BTF_KIND_DATASEC 15 /* 数据部分 */
注意,类型部分是对调试信息进行编码的,而不是类型自身。BTF_KIND_FUNC 不是一个类型, 它代表一个已定义的子程序。
每个类型都包含以下常见的数据:
代码语言:javascript复制struct btf_type {
__u32 name_off;
/* "info" 位值设置如下:
* 第 0-15 位:vlen(例如结构的成员)
* bits 16-23: unused
* bits 24-27: kind (e.g. int, ptr, array...etc)
* bits 28-30 位:未使用
* bits 31: kind_flag, 目前被 struct, union 和 fwd 使用
*/
__u32 info;
/* "size" 被 INT、ENUM、STRUCT 和 UNION 使用
* "size" 用于描述类型的大小
*
* "type“ 被 PTR, TYPEDEF, VOLATILE, CONST, RESTRICT, FUNC 和 FUNC_PROTO 使用。
* "type" 是指另一个类型的 type_id
*/
union {
__u32 size;
__u32 type;
};
};
libbpf 库底层使用的结构:
代码语言:javascript复制struct btf {
void *data;
struct btf_type **types;
u32 *resolved_ids;
u32 *resolved_sizes;
const char *strings;
void *nohdr_data;
struct btf_header hdr;
u32 nr_types;
u32 types_size;
u32 data_size;
refcount_t refcnt;
u32 id;
struct rcu_head rcu;
};
对于某些类别来讲,通用数据之后是特定类型的数据。在 struct btf_type 中的 name_off 字段指定了字符串表中的偏移。