BPF 类型格式 (BTF)¶
1. 简介¶
BTF (BPF 类型格式) 是一种元数据格式,用于编码与 BPF 程序/映射相关的调试信息。BTF 最初用于描述数据类型。后来,BTF 扩展到包含已定义子例程的函数信息,以及用于源代码/行信息的行信息。
调试信息用于映射美观打印、函数签名等。函数签名可以生成更好的 BPF 程序/函数内核符号。行信息有助于生成带有源注释的翻译字节码、JIT 编译代码和验证器日志。
- BTF 规范包含两部分:
BTF 内核 API
BTF ELF 文件格式
内核 API 是用户空间和内核之间的约定。内核在使用 BTF 信息之前会对其进行验证。ELF 文件格式是 ELF 文件和 libbpf 加载器之间的用户空间约定。
类型和字符串段是 BTF 内核 API 的一部分,描述了 BPF 程序引用的调试信息(主要与类型相关)。这两个段将在 2. BTF 类型和字符串编码 中详细讨论。
2. BTF 类型和字符串编码¶
文件 include/uapi/linux/btf.h
提供了关于类型/字符串如何编码的高级定义。
数据块的起始部分必须是
struct btf_header {
__u16 magic;
__u8 version;
__u8 flags;
__u32 hdr_len;
/* All offsets are in bytes relative to the end of this header */
__u32 type_off; /* offset of type section */
__u32 type_len; /* length of type section */
__u32 str_off; /* offset of string section */
__u32 str_len; /* length of string section */
};
魔数是 0xeB9F
,它在大端和小端系统中具有不同的编码,可用于测试 BTF 是为大端还是小端目标生成的。btf_header
被设计为可扩展的,当生成数据块时,hdr_len
等于 sizeof(struct btf_header)
。
2.1 字符串编码¶
字符串段中的第一个字符串必须是空字符串。字符串表的其余部分是其他以 null 结尾的字符串的连接。
2.2 类型编码¶
类型 ID 0
保留给 void
类型。类型段按顺序解析,并从 ID 1
开始为每个识别的类型分配类型 ID。目前支持以下类型:
#define BTF_KIND_INT 1 /* Integer */
#define BTF_KIND_PTR 2 /* Pointer */
#define BTF_KIND_ARRAY 3 /* Array */
#define BTF_KIND_STRUCT 4 /* Struct */
#define BTF_KIND_UNION 5 /* Union */
#define BTF_KIND_ENUM 6 /* Enumeration up to 32-bit values */
#define BTF_KIND_FWD 7 /* Forward */
#define BTF_KIND_TYPEDEF 8 /* Typedef */
#define BTF_KIND_VOLATILE 9 /* Volatile */
#define BTF_KIND_CONST 10 /* Const */
#define BTF_KIND_RESTRICT 11 /* Restrict */
#define BTF_KIND_FUNC 12 /* Function */
#define BTF_KIND_FUNC_PROTO 13 /* Function Proto */
#define BTF_KIND_VAR 14 /* Variable */
#define BTF_KIND_DATASEC 15 /* Section */
#define BTF_KIND_FLOAT 16 /* Floating point */
#define BTF_KIND_DECL_TAG 17 /* Decl Tag */
#define BTF_KIND_TYPE_TAG 18 /* Type Tag */
#define BTF_KIND_ENUM64 19 /* Enumeration up to 64-bit values */
请注意,类型段编码的是调试信息,而不仅仅是纯粹的类型。BTF_KIND_FUNC
不是一个类型,它表示一个已定义的子程序。
每种类型都包含以下通用数据:
struct btf_type {
__u32 name_off;
/* "info" bits arrangement
* bits 0-15: vlen (e.g. # of struct's members)
* bits 16-23: unused
* bits 24-28: kind (e.g. int, ptr, array...etc)
* bits 29-30: unused
* bit 31: kind_flag, currently used by
* struct, union, enum, fwd, enum64,
* decl_tag and type_tag
*/
__u32 info;
/* "size" is used by INT, ENUM, STRUCT, UNION and ENUM64.
* "size" tells the size of the type it is describing.
*
* "type" is used by PTR, TYPEDEF, VOLATILE, CONST, RESTRICT,
* FUNC, FUNC_PROTO, DECL_TAG and TYPE_TAG.
* "type" is a type_id referring to another type.
*/
union {
__u32 size;
__u32 type;
};
};
对于某些种类,通用数据后面跟着特定种类的数据。struct btf_type
中的 name_off
指定了字符串表中的偏移量。以下各节详细介绍了每种类的编码。
2.2.1 BTF_KIND_INT¶
struct btf_type
编码要求name_off
: 任何有效偏移量info.kind_flag
: 0info.kind
: BTF_KIND_INTinfo.vlen
: 0size
: 整型类型的大小(以字节为单位)。
btf_type
后面跟着一个 u32
,其位排列如下:
#define BTF_INT_ENCODING(VAL) (((VAL) & 0x0f000000) >> 24)
#define BTF_INT_OFFSET(VAL) (((VAL) & 0x00ff0000) >> 16)
#define BTF_INT_BITS(VAL) ((VAL) & 0x000000ff)
The BTF_INT_ENCODING
具有以下属性:
#define BTF_INT_SIGNED (1 << 0)
#define BTF_INT_CHAR (1 << 1)
#define BTF_INT_BOOL (1 << 2)
BTF_INT_ENCODING()
为整型提供了额外信息:有符号性、char 或 bool。char 和 bool 编码主要用于美观打印。对于整型,最多只能指定一种编码。
BTF_INT_BITS()
指定此整型实际持有的位数。例如,一个 4 位位字段编码的 BTF_INT_BITS()
等于 4。对于该类型,btf_type.size * 8
必须等于或大于 BTF_INT_BITS()
。BTF_INT_BITS()
的最大值为 128。
BTF_INT_OFFSET()
指定用于计算此整型值的起始位偏移量。例如,一个位字段结构成员具有:
btf 成员位偏移量为结构体起始位置的 100,
btf 成员指向一个整型,
整型具有
BTF_INT_OFFSET() = 2
和BTF_INT_BITS() = 4
那么在结构体内存布局中,此成员将从位 100 + 2 = 102
开始占用 4
位。
或者,位字段结构成员可以通过以下方式访问与上述相同的位:
btf 成员位偏移量 102,
btf 成员指向一个整型,
整型具有
BTF_INT_OFFSET() = 0
和BTF_INT_BITS() = 4
BTF_INT_OFFSET()
的最初目的是提供位字段编码的灵活性。目前,llvm 和 pahole 都为所有整型生成 BTF_INT_OFFSET() = 0
。
2.2.2 BTF_KIND_PTR¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_PTRinfo.vlen
: 0type
: 指针指向的类型
btf_type
之后没有额外的类型数据。
2.2.3 BTF_KIND_ARRAY¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_ARRAYinfo.vlen
: 0size/type
: 0,未使用
btf_type
后面跟着一个 struct btf_array
struct btf_array {
__u32 type;
__u32 index_type;
__u32 nelems;
};
struct btf_array
编码type
: 元素类型index_type
: 索引类型nelems
: 此数组的元素数量(也允许为0
)。
index_type
可以是任何常规整型(u8
, u16
, u32
, u64
, unsigned __int128
)。包含 index_type
的最初设计遵循 DWARF,其数组类型也包含 index_type
。目前在 BTF 中,除了类型验证之外,index_type
并未使用。
struct btf_array
允许通过元素类型链接以表示多维数组。例如,对于 int a[5][6]
,以下类型信息说明了这种链接:
[1]: int
[2]: 数组,
btf_array.type = [1]
,btf_array.nelems = 6
[3]: 数组,
btf_array.type = [2]
,btf_array.nelems = 5
目前,pahole 和 llvm 都将多维数组折叠成一维数组,例如,对于 a[5][6]
,btf_array.nelems
等于 30
。这是因为最初的使用场景是映射美观打印,整个数组被转储出来,因此一维数组就足够了。随着 BTF 更多用法的探索,pahole 和 llvm 可以进行修改,以生成多维数组的正确链式表示。
2.2.4 BTF_KIND_STRUCT¶
2.2.5 BTF_KIND_UNION¶
struct btf_type
编码要求name_off
: 0 或指向有效 C 标识符的偏移量info.kind_flag
: 0 或 1info.kind
: BTF_KIND_STRUCT 或 BTF_KIND_UNIONinfo.vlen
: 结构体/联合体成员的数量info.size
: 结构体/联合体的大小(以字节为单位)
btf_type
后面跟着 info.vlen
个 struct btf_member
。
struct btf_member {
__u32 name_off;
__u32 type;
__u32 offset;
};
struct btf_member
编码name_off
: 指向有效 C 标识符的偏移量type
: 成员类型offset
: <见下文>
如果类型信息 kind_flag
未设置,则偏移量仅包含成员的位偏移量。请注意,位字段的基本类型只能是 int 或 enum 类型。如果位字段大小为 32,则基本类型可以是 int 或 enum 类型。如果位字段大小不是 32,则基本类型必须是 int,并且 int 类型 BTF_INT_BITS()
编码位字段大小。
如果设置了 kind_flag
,则 btf_member.offset
包含成员位字段大小和位偏移量。位字段大小和位偏移量计算如下。
#define BTF_MEMBER_BITFIELD_SIZE(val) ((val) >> 24)
#define BTF_MEMBER_BIT_OFFSET(val) ((val) & 0xffffff)
在这种情况下,如果基本类型是 int 类型,它必须是常规 int 类型:
BTF_INT_OFFSET()
必须为 0。
BTF_INT_BITS()
必须等于{1,2,4,8,16} * 8
。
提交 9d5f9f701b18 引入了 kind_flag
并解释了为什么存在这两种模式。
2.2.6 BTF_KIND_ENUM¶
struct btf_type
编码要求name_off
: 0 或指向有效 C 标识符的偏移量info.kind_flag
: 0 表示无符号,1 表示有符号info.kind
: BTF_KIND_ENUMinfo.vlen
: 枚举值数量size
: 1/2/4/8
btf_type
后面跟着 info.vlen
个 struct btf_enum
。
struct btf_enum {
__u32 name_off;
__s32 val;
};
btf_enum
编码name_off
: 指向有效 C 标识符的偏移量val
: 任何值
如果原始枚举值为有符号且大小小于 4,则该值将进行符号扩展到 4 字节。如果大小为 8,则该值将被截断为 4 字节。
2.2.7 BTF_KIND_FWD¶
struct btf_type
编码要求name_off
: 指向有效 C 标识符的偏移量info.kind_flag
: 0 表示结构体,1 表示联合体info.kind
: BTF_KIND_FWDinfo.vlen
: 0type
: 0
btf_type
之后没有额外的类型数据。
2.2.8 BTF_KIND_TYPEDEF¶
struct btf_type
编码要求name_off
: 指向有效 C 标识符的偏移量info.kind_flag
: 0info.kind
: BTF_KIND_TYPEDEFinfo.vlen
: 0type
: 可以通过name_off
处的名称引用的类型
btf_type
之后没有额外的类型数据。
2.2.9 BTF_KIND_VOLATILE¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_VOLATILEinfo.vlen
: 0type
: 具有volatile
限定符的类型
btf_type
之后没有额外的类型数据。
2.2.10 BTF_KIND_CONST¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_CONSTinfo.vlen
: 0type
: 具有const
限定符的类型
btf_type
之后没有额外的类型数据。
2.2.11 BTF_KIND_RESTRICT¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_RESTRICTinfo.vlen
: 0type
: 具有restrict
限定符的类型
btf_type
之后没有额外的类型数据。
2.2.12 BTF_KIND_FUNC¶
struct btf_type
编码要求name_off
: 指向有效 C 标识符的偏移量info.kind_flag
: 0info.kind
: BTF_KIND_FUNCinfo.vlen
: 链接信息 (BTF_FUNC_STATIC, BTF_FUNC_GLOBAL或 BTF_FUNC_EXTERN - 参见 2.3.1 函数链接常量值)
type
: 一个 BTF_KIND_FUNC_PROTO 类型
btf_type
之后没有额外的类型数据。
BTF_KIND_FUNC 定义的不是一个类型,而是一个子程序(函数),其签名由 type
定义。因此,该子程序是该类型的一个实例。BTF_KIND_FUNC 又可以在 4.2 .BTF.ext 段 (ELF) 的 func_info 中或在 3.3 BPF_PROG_LOAD (ABI) 的参数中被引用。
目前,内核只支持 BTF_FUNC_STATIC 和 BTF_FUNC_GLOBAL 的链接值。
2.2.13 BTF_KIND_FUNC_PROTO¶
struct btf_type
编码要求name_off
: 0info.kind_flag
: 0info.kind
: BTF_KIND_FUNC_PROTOinfo.vlen
: 参数数量type
: 返回类型
btf_type
后面跟着 info.vlen
个 struct btf_param
。
struct btf_param {
__u32 name_off;
__u32 type;
};
如果 BTF_KIND_FUNC_PROTO 类型被 BTF_KIND_FUNC 类型引用,则 btf_param.name_off
必须指向一个有效的 C 标识符,除了可能表示可变参数的最后一个参数。btf_param.type 指的是参数类型。
如果函数有可变参数,则最后一个参数使用 name_off = 0
和 type = 0
进行编码。
2.2.14 BTF_KIND_VAR¶
struct btf_type
编码要求name_off
: 指向有效 C 标识符的偏移量info.kind_flag
: 0info.kind
: BTF_KIND_VARinfo.vlen
: 0type
: 变量的类型
btf_type
后面跟着一个 struct btf_variable
,包含以下数据:
struct btf_var {
__u32 linkage;
};
btf_var.linkage
可以取值:BTF_VAR_STATIC, BTF_VAR_GLOBAL_ALLOCATED 或 BTF_VAR_GLOBAL_EXTERN - 参见 2.3.2 变量链接常量值。
目前 LLVM 并非支持所有类型的全局变量。目前支持以下类型:
带或不带段属性的静态变量
带段属性的全局变量
后者用于将来从映射定义中提取映射键/值类型 ID。
2.2.15 BTF_KIND_DATASEC¶
struct btf_type
编码要求name_off
: 指向与变量或.data/.bss/.rodata 之一关联的有效名称的偏移量
info.kind_flag
: 0info.kind
: BTF_KIND_DATASECinfo.vlen
: 变量数量size
: 段总大小(以字节为单位)(编译时为 0,由 BPF 加载器(如 libbpf)修补为实际大小)
btf_type
后面跟着 info.vlen
个 struct btf_var_secinfo
。
struct btf_var_secinfo {
__u32 type;
__u32 offset;
__u32 size;
};
struct btf_var_secinfo
编码type
: BTF_KIND_VAR 变量的类型offset
: 变量在段内的偏移量size
: 变量的大小(以字节为单位)
2.2.16 BTF_KIND_FLOAT¶
struct btf_type
编码要求name_off
: 任何有效偏移量info.kind_flag
: 0info.kind
: BTF_KIND_FLOATinfo.vlen
: 0size
: 浮点型的大小(以字节为单位):2、4、8、12 或 16。
btf_type
之后没有额外的类型数据。
2.2.17 BTF_KIND_DECL_TAG¶
struct btf_type
编码要求name_off
: 指向非空字符串的偏移量info.kind_flag
: 0 或 1info.kind
: BTF_KIND_DECL_TAGinfo.vlen
: 0type
:struct
,union
,func
,var
或typedef
btf_type
后面跟着 struct btf_decl_tag
。
struct btf_decl_tag {
__u32 component_idx;
};
type
应该是 struct
、union
、func
、var
或 typedef
。对于 var
或 typedef
类型,btf_decl_tag.component_idx
必须为 -1
。对于其他三种类型,如果 btf_decl_tag 属性应用于 struct
、union
或 func
本身,则 btf_decl_tag.component_idx
必须为 -1
。否则,如果属性应用于 struct
/union
成员或 func
参数,则 btf_decl_tag.component_idx
应该是一个有效的索引(从 0 开始),指向一个成员或一个参数。
如果 info.kind_flag
为 0,则这是一个普通声明标签,name_off
编码 btf_decl_tag 属性字符串。
如果 info.kind_flag
为 1,则声明标签表示一个任意的 __attribute__。在这种情况下,name_off
编码一个字符串,该字符串表示属性说明符的属性列表。例如,对于 __attribute__((aligned(4)))
,字符串内容是 aligned(4)
。
2.2.18 BTF_KIND_TYPE_TAG¶
struct btf_type
编码要求name_off
: 指向非空字符串的偏移量info.kind_flag
: 0 或 1info.kind
: BTF_KIND_TYPE_TAGinfo.vlen
: 0type
: 具有btf_type_tag
属性的类型
目前,BTF_KIND_TYPE_TAG
仅针对指针类型发出。它具有以下 BTF 类型链:
ptr -> [type_tag]*
-> [const | volatile | restrict | typedef]*
-> base_type
基本上,一个指针类型指向零个或多个 type_tag,然后是零个或多个 const/volatile/restrict/typedef,最后是基本类型。基本类型是 int、ptr、array、struct、union、enum、func_proto 和 float 类型之一。
与声明标签类似,如果 info.kind_flag
为 0,则这是一个普通类型标签,name_off
编码 btf_type_tag 属性字符串。
如果 info.kind_flag
为 1,则类型标签表示一个任意的 __attribute__,name_off
编码一个字符串,该字符串表示属性说明符的属性列表。
2.2.19 BTF_KIND_ENUM64¶
struct btf_type
编码要求name_off
: 0 或指向有效 C 标识符的偏移量info.kind_flag
: 0 表示无符号,1 表示有符号info.kind
: BTF_KIND_ENUM64info.vlen
: 枚举值数量size
: 1/2/4/8
btf_type
后面跟着 info.vlen
个 struct btf_enum64
。
struct btf_enum64 {
__u32 name_off;
__u32 val_lo32;
__u32 val_hi32;
};
btf_enum64
编码name_off
: 指向有效 C 标识符的偏移量val_lo32
: 64 位值的低 32 位值val_hi32
: 64 位值的高 32 位值
如果原始枚举值为有符号且大小小于 8,则该值将进行符号扩展到 8 字节。
2.3 常量值¶
2.3.1 函数链接常量值¶
种类 |
值 |
描述 |
---|---|---|
|
0x0 |
子程序定义在包含编译单元外部不可见 |
|
0x1 |
子程序定义在包含编译单元外部可见 |
|
0x2 |
子程序声明,其定义在包含编译单元外部 |
2.3.2 变量链接常量值¶
种类 |
值 |
描述 |
---|---|---|
|
0x0 |
全局变量定义在包含编译单元外部不可见 |
|
0x1 |
全局变量定义在包含编译单元外部可见 |
|
0x2 |
全局变量声明,其定义在包含编译单元外部 |
3. BTF 内核 API¶
- 以下 BPF 系统调用命令涉及 BTF:
BPF_BTF_LOAD: 将 BTF 数据块加载到内核中
BPF_MAP_CREATE: 使用 BTF 键和值类型信息创建映射。
BPF_PROG_LOAD: 使用 BTF 函数和行信息加载程序。
BPF_BTF_GET_FD_BY_ID: 获取一个 BTF 文件描述符
BPF_OBJ_GET_INFO_BY_FD: 返回 BTF、func_info、line_info 和其他 BTF 相关信息。
工作流程通常如下所示:
Application:
BPF_BTF_LOAD
|
v
BPF_MAP_CREATE and BPF_PROG_LOAD
|
V
......
Introspection tool:
......
BPF_{PROG,MAP}_GET_NEXT_ID (get prog/map id's)
|
V
BPF_{PROG,MAP}_GET_FD_BY_ID (get a prog/map fd)
|
V
BPF_OBJ_GET_INFO_BY_FD (get bpf_prog_info/bpf_map_info with btf_id)
| |
V |
BPF_BTF_GET_FD_BY_ID (get btf_fd) |
| |
V |
BPF_OBJ_GET_INFO_BY_FD (get btf) |
| |
V V
pretty print types, dump func signatures and line info, etc.
3.1 BPF_BTF_LOAD¶
将 BTF 数据块加载到内核中。在 2. BTF 类型和字符串编码 中描述的数据块可以直接加载到内核中。一个 btf_fd
会返回给用户空间。
3.2 BPF_MAP_CREATE¶
可以使用 btf_fd
和指定的键/值类型 ID 创建映射。
__u32 btf_fd; /* fd pointing to a BTF type data */
__u32 btf_key_type_id; /* BTF type_id of the key */
__u32 btf_value_type_id; /* BTF type_id of the value */
在 libbpf 中,映射可以像下面这样定义额外的注解:
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct ipv_counts);
__uint(max_entries, 4);
} btf_map SEC(".maps");
在 ELF 解析过程中,libbpf 能够自动提取键/值 type_id 并将其分配给 BPF_MAP_CREATE 属性。
3.3 BPF_PROG_LOAD¶
在 prog_load 期间,func_info 和 line_info 可以通过以下属性的正确值传递给内核:
__u32 insn_cnt;
__aligned_u64 insns;
......
__u32 prog_btf_fd; /* fd pointing to BTF type data */
__u32 func_info_rec_size; /* userspace bpf_func_info size */
__aligned_u64 func_info; /* func info */
__u32 func_info_cnt; /* number of bpf_func_info records */
__u32 line_info_rec_size; /* userspace bpf_line_info size */
__aligned_u64 line_info; /* line info */
__u32 line_info_cnt; /* number of bpf_line_info records */
func_info 和 line_info 分别是以下结构体的数组。
struct bpf_func_info {
__u32 insn_off; /* [0, insn_cnt - 1] */
__u32 type_id; /* pointing to a BTF_KIND_FUNC type */
};
struct bpf_line_info {
__u32 insn_off; /* [0, insn_cnt - 1] */
__u32 file_name_off; /* offset to string table for the filename */
__u32 line_off; /* offset to string table for the source line */
__u32 line_col; /* line number and column number */
};
func_info_rec_size 是每个 func_info 记录的大小,line_info_rec_size 是每个 line_info 记录的大小。将记录大小传递给内核使得将来可以扩展记录本身。
- 以下是 func_info 的要求:
func_info[0].insn_off 必须为 0。
func_info insn_off 严格递增,并与 BPF 函数边界匹配。
- 以下是 line_info 的要求:
每个函数中的第一个指令必须有一个指向它的 line_info 记录。
line_info insn_off 严格递增。
对于 line_info,行号和列号定义如下:
#define BPF_LINE_INFO_LINE_NUM(line_col) ((line_col) >> 10)
#define BPF_LINE_INFO_LINE_COL(line_col) ((line_col) & 0x3ff)
3.4 BPF_{PROG,MAP}_GET_NEXT_ID¶
在内核中,每个加载的程序、映射或 BTF 都有一个唯一的 ID。该 ID 在程序、映射或 BTF 的生命周期内不会改变。
BPF 系统调用命令 BPF_{PROG,MAP}_GET_NEXT_ID 将所有 ID(每个命令一个)分别返回给用户空间,用于 BPF 程序或映射,以便检查工具可以检查所有程序和映射。
3.5 BPF_{PROG,MAP}_GET_FD_BY_ID¶
自省工具不能直接使用 ID 获取程序或映射的详细信息。出于引用计数目的,需要首先获取文件描述符。
3.6 BPF_OBJ_GET_INFO_BY_FD¶
一旦获取了程序/映射文件描述符,自省工具就可以从内核获取关于此文件描述符的详细信息,其中一些与 BTF 相关。例如,bpf_map_info
返回 btf_id
和键/值类型 ID。bpf_prog_info
返回 btf_id
、func_info 以及翻译后的 BPF 字节码的行信息,以及 jited_line_info。
3.7 BPF_BTF_GET_FD_BY_ID¶
通过在 bpf_map_info
和 bpf_prog_info
中获取的 btf_id
,BPF 系统调用命令 BPF_BTF_GET_FD_BY_ID 可以检索 BTF 文件描述符。然后,通过命令 BPF_OBJ_GET_INFO_BY_FD,可以检索到最初通过 BPF_BTF_LOAD 加载到内核中的 BTF 数据块。
借助 BTF 数据块、bpf_map_info
和 bpf_prog_info
,自省工具可以完全了解 BTF,并能够美观地打印映射键/值,转储函数签名和行信息,以及字节码/JIT 代码。
4. ELF 文件格式接口¶
4.1 .BTF 段¶
.BTF 段包含类型和字符串数据。此段的格式与 2. BTF 类型和字符串编码 中描述的相同。
4.2 .BTF.ext 段¶
.BTF.ext 段编码 func_info、line_info 和 CO-RE 重定位,这些在加载到内核之前需要加载器进行操作。
.BTF.ext 段的规范在 tools/lib/bpf/btf.h
和 tools/lib/bpf/btf.c
中定义。
.BTF.ext 段的当前头部
struct btf_ext_header {
__u16 magic;
__u8 version;
__u8 flags;
__u32 hdr_len;
/* All offsets are in bytes relative to the end of this header */
__u32 func_info_off;
__u32 func_info_len;
__u32 line_info_off;
__u32 line_info_len;
/* optional part of .BTF.ext header */
__u32 core_relo_off;
__u32 core_relo_len;
};
它与 .BTF 段非常相似。它不包含类型/字符串段,而是包含 func_info、line_info 和 core_relo 子段。有关 func_info 和 line_info 记录格式的详细信息,请参见 3.3 BPF_PROG_LOAD。
func_info 组织如下。
func_info_rec_size /* __u32 value */
btf_ext_info_sec for section #1 /* func_info for section #1 */
btf_ext_info_sec for section #2 /* func_info for section #2 */
...
func_info_rec_size
指定在生成 .BTF.ext 时 bpf_func_info
结构体的大小。下面定义的 btf_ext_info_sec
是每个特定 ELF 段的 func_info 集合。
struct btf_ext_info_sec {
__u32 sec_name_off; /* offset to section name */
__u32 num_info;
/* Followed by num_info * record_size number of bytes */
__u8 data[0];
};
在这里,num_info 必须大于 0。
line_info 组织如下。
line_info_rec_size /* __u32 value */
btf_ext_info_sec for section #1 /* line_info for section #1 */
btf_ext_info_sec for section #2 /* line_info for section #2 */
...
line_info_rec_size
指定在生成 .BTF.ext 时 bpf_line_info
结构体的大小。
bpf_func_info->insn_off
和 bpf_line_info->insn_off
在内核 API 和 ELF API 之间的解释不同。对于内核 API,insn_off
是以 struct bpf_insn
为单位的指令偏移量。对于 ELF API,insn_off
是从段起始处(btf_ext_info_sec->sec_name_off
)的字节偏移量。
core_relo 组织如下。
core_relo_rec_size /* __u32 value */
btf_ext_info_sec for section #1 /* core_relo for section #1 */
btf_ext_info_sec for section #2 /* core_relo for section #2 */
core_relo_rec_size
指定在生成 .BTF.ext 时 bpf_core_relo
结构体的大小。单个 btf_ext_info_sec
中的所有 bpf_core_relo
结构体描述应用于由 btf_ext_info_sec->sec_name_off
命名的段的重定位。
有关 CO-RE 重定位的更多信息,请参见 Documentation/bpf/llvm_reloc.rst。
4.3 .BTF_ids 段¶
.BTF_ids 段编码在内核中使用的 BTF ID 值。
该段在内核编译期间通过 include/linux/btf_ids.h
头文件中定义的宏创建。内核代码可以使用它们来创建 BTF ID 值的列表和集合(排序列表)。
BTF_ID_LIST
和 BTF_ID
宏定义了未排序的 BTF ID 值列表,语法如下:
BTF_ID_LIST(list)
BTF_ID(type1, name1)
BTF_ID(type2, name2)
在 .BTF_ids 段中产生以下布局:
__BTF_ID__type1__name1__1:
.zero 4
__BTF_ID__type2__name2__2:
.zero 4
u32 list[];
变量用于访问该列表。
BTF_ID_UNUSED
宏定义 4 个零字节。它用于在 BTF_ID_LIST 中定义未使用条目,例如:
BTF_ID_LIST(bpf_skb_output_btf_ids)
BTF_ID(struct, sk_buff)
BTF_ID_UNUSED
BTF_ID(struct, task_struct)
BTF_SET_START/END
宏对定义了排序的 BTF ID 值列表及其计数,语法如下:
BTF_SET_START(set)
BTF_ID(type1, name1)
BTF_ID(type2, name2)
BTF_SET_END(set)
在 .BTF_ids 段中产生以下布局:
__BTF_ID__set__set:
.zero 4
__BTF_ID__type1__name1__3:
.zero 4
__BTF_ID__type2__name2__4:
.zero 4
struct btf_id_set set;
变量用于访问该列表。
typeX
名称可以是以下之一:
struct, union, typedef, func
并用作解析 BTF ID 值时的过滤器。
所有 BTF ID 列表和集合都在 .BTF_ids 段中编译,并在内核构建的链接阶段由 resolve_btfids
工具解析。
4.4 .BTF.base 段¶
分割 BTF(其中 .BTF 段仅包含关联的基础 .BTF 段中不存在的类型)是一种为内核模块编码类型信息的极其有效的方法,因为它们通常由一些模块特定类型和大量共享内核类型组成。前者编码在分割 BTF 中,而后者编码在基础 BTF 中,从而产生更紧凑的表示。分割 BTF 中引用基础 BTF 中类型的类型使用其基础 BTF ID 进行引用,并且分割 BTF ID 从 last_base_BTF_ID + 1 开始。
然而,这种方法的缺点是它使得分割 BTF 变得有些脆弱——当基础 BTF 发生变化时,基础 BTF ID 引用将不再有效,分割 BTF 本身也变得无用。.BTF.base 段的作用是使分割 BTF 在基础 BTF 可能发生变化的情况下更具弹性,例如内核模块并非每次内核构建时都重新构建的情况。.BTF.base 包含命名基础类型;INTs、FLOATs、STRUCTs、UNIONs、ENUM[64]s 和 FWDs。INTs 和 FLOATs 在 .BTF.base 段中完全描述,而像结构体和联合体这样的复合类型则没有完全定义——.BTF.base 类型仅用作分割 BTF 引用的类型的描述,因此结构体/联合体在 .BTF.base 段中拥有 0 个成员。ENUM[64]s 也以 0 个成员记录。任何其他类型都添加到分割 BTF 中。这种提炼过程使我们得到一个包含基础类型最小描述的 .BTF.base 段和一个引用这些基础类型的 .BTF 分割段。稍后,我们可以使用 .BTF.base 段中存储的信息和新的 .BTF 基础来重定位分割 BTF;.BTF.base 段中的类型信息允许我们更新分割 BTF 引用以指向相应的新基础 BTF ID。
BTF 重定位发生在内核模块加载时,当内核模块包含 .BTF.base 段时,libbpf 也提供了 btf__relocate() API 来实现此功能。
举例来说,考虑以下基础 BTF:
[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
[2] STRUCT 'foo' size=8 vlen=2
'f1' type_id=1 bits_offset=0
'f2' type_id=1 bits_offset=32
...以及关联的分割 BTF:
[3] PTR '(anon)' type_id=2
即,分割 BTF 描述了一个指向 struct foo { int f1; int f2 }; 的指针。
.BTF.base 将包含:
[1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
[2] STRUCT 'foo' size=8 vlen=0
如果我们稍后使用以下新的基础 BTF 重定位分割 BTF:
[1] INT 'long unsigned int' size=8 bits_offset=0 nr_bits=64 encoding=(none)
[2] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
[3] STRUCT 'foo' size=8 vlen=2
'f1' type_id=2 bits_offset=0
'f2' type_id=2 bits_offset=32
...我们可以使用我们的 .BTF.base 描述来知道分割 BTF 引用指向 struct foo,重定位结果生成新的分割 BTF:
[4] PTR '(anon)' type_id=3
请注意,我们必须更新分割 BTF 的 BTF ID 和起始 BTF ID。
因此,我们看到 .BTF.base 如何促进后续重定位,从而实现更具弹性的分割 BTF。
对于非内核源码树的内核模块构建(即设置了 KBUILD_EXTMOD 的情况,如“make M=path/2/mod”),.BTF.base 段将自动生成。.BTF.base 的生成需要 pahole 支持“distilled_base”BTF 功能;此功能在 pahole v1.28 及更高版本中可用。
5. 使用 BTF¶
5.1 bpftool 映射美观打印¶
借助 BTF,可以根据字段而不是简单的原始字节打印映射的键/值。这对于大型结构或数据结构包含位字段的情况特别有价值。例如,对于以下映射:
enum A { A1, A2, A3, A4, A5 };
typedef enum A ___A;
struct tmp_t {
char a1:4;
int a2:4;
int :4;
__u32 a3:4;
int b;
___A b1:4;
enum A b2:4;
};
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__type(key, int);
__type(value, struct tmp_t);
__uint(max_entries, 1);
} tmpmap SEC(".maps");
bpftool 能够像下面这样进行美观打印:
[{
"key": 0,
"value": {
"a1": 0x2,
"a2": 0x4,
"a3": 0x6,
"b": 7,
"b1": 0x8,
"b2": 0xa
}
}
]
5.2 bpftool 程序转储¶
以下示例展示了 func_info 和 line_info 如何帮助程序转储提供更好的内核符号名称、函数原型和行信息。
$ bpftool prog dump jited pinned /sys/fs/bpf/test_btf_haskv
[...]
int test_long_fname_2(struct dummy_tracepoint_args * arg):
bpf_prog_44a040bf25481309_test_long_fname_2:
; static int test_long_fname_2(struct dummy_tracepoint_args *arg)
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x30,%rsp
b: sub $0x28,%rbp
f: mov %rbx,0x0(%rbp)
13: mov %r13,0x8(%rbp)
17: mov %r14,0x10(%rbp)
1b: mov %r15,0x18(%rbp)
1f: xor %eax,%eax
21: mov %rax,0x20(%rbp)
25: xor %esi,%esi
; int key = 0;
27: mov %esi,-0x4(%rbp)
; if (!arg->sock)
2a: mov 0x8(%rdi),%rdi
; if (!arg->sock)
2e: cmp $0x0,%rdi
32: je 0x0000000000000070
34: mov %rbp,%rsi
; counts = bpf_map_lookup_elem(&btf_map, &key);
[...]
5.3 验证器日志¶
以下是 line_info 如何帮助调试验证失败的示例。
/* The code at tools/testing/selftests/bpf/test_xdp_noinline.c
* is modified as below.
*/
data = (void *)(long)xdp->data;
data_end = (void *)(long)xdp->data_end;
/*
if (data + 4 > data_end)
return XDP_DROP;
*/
*(u32 *)data = dst->dst;
$ bpftool prog load ./test_xdp_noinline.o /sys/fs/bpf/test_xdp_noinline type xdp
; data = (void *)(long)xdp->data;
224: (79) r2 = *(u64 *)(r10 -112)
225: (61) r2 = *(u32 *)(r2 +0)
; *(u32 *)data = dst->dst;
226: (63) *(u32 *)(r2 +0) = r1
invalid access to packet, off=0 size=4, R2(id=0,off=0,r=0)
R2 offset is outside of the packet
6. BTF 生成¶
您需要最新的 pahole:
或 llvm (8.0 或更高版本)。pahole 充当 dwarf2btf 转换器。它尚不支持 .BTF.ext 和 btf BTF_KIND_FUNC 类型。例如:
-bash-4.4$ cat t.c
struct t {
int a:2;
int b:3;
int c:2;
} g;
-bash-4.4$ gcc -c -O2 -g t.c
-bash-4.4$ pahole -JV t.o
File t.o:
[1] STRUCT t kind_flag=1 size=4 vlen=3
a type_id=2 bitfield_size=2 bits_offset=0
b type_id=2 bitfield_size=3 bits_offset=2
c type_id=2 bitfield_size=2 bits_offset=5
[2] INT int size=4 bit_offset=0 nr_bits=32 encoding=SIGNED
llvm 能够直接使用 -g 为 BPF 目标生成 .BTF 和 .BTF.ext。汇编代码 (-S) 能够以汇编格式显示 BTF 编码。
-bash-4.4$ cat t2.c
typedef int __int32;
struct t2 {
int a2;
int (*f2)(char q1, __int32 q2, ...);
int (*f3)();
} g2;
int main() { return 0; }
int test() { return 0; }
-bash-4.4$ clang -c -g -O2 --target=bpf t2.c
-bash-4.4$ readelf -S t2.o
......
[ 8] .BTF PROGBITS 0000000000000000 00000247
000000000000016e 0000000000000000 0 0 1
[ 9] .BTF.ext PROGBITS 0000000000000000 000003b5
0000000000000060 0000000000000000 0 0 1
[10] .rel.BTF.ext REL 0000000000000000 000007e0
0000000000000040 0000000000000010 16 9 8
......
-bash-4.4$ clang -S -g -O2 --target=bpf t2.c
-bash-4.4$ cat t2.s
......
.section .BTF,"",@progbits
.short 60319 # 0xeb9f
.byte 1
.byte 0
.long 24
.long 0
.long 220
.long 220
.long 122
.long 0 # BTF_KIND_FUNC_PROTO(id = 1)
.long 218103808 # 0xd000000
.long 2
.long 83 # BTF_KIND_INT(id = 2)
.long 16777216 # 0x1000000
.long 4
.long 16777248 # 0x1000020
......
.byte 0 # string offset=0
.ascii ".text" # string offset=1
.byte 0
.ascii "/home/yhs/tmp-pahole/t2.c" # string offset=7
.byte 0
.ascii "int main() { return 0; }" # string offset=33
.byte 0
.ascii "int test() { return 0; }" # string offset=58
.byte 0
.ascii "int" # string offset=83
......
.section .BTF.ext,"",@progbits
.short 60319 # 0xeb9f
.byte 1
.byte 0
.long 24
.long 0
.long 28
.long 28
.long 44
.long 8 # FuncInfo
.long 1 # FuncInfo section string offset=1
.long 2
.long .Lfunc_begin0
.long 3
.long .Lfunc_begin1
.long 5
.long 16 # LineInfo
.long 1 # LineInfo section string offset=1
.long 2
.long .Ltmp0
.long 7
.long 33
.long 7182 # Line 7 Col 14
.long .Ltmp3
.long 7
.long 58
.long 8206 # Line 8 Col 14
7. 测试¶
内核 BPF 自测试 tools/testing/selftests/bpf/prog_tests/btf.c 提供了一套广泛的 BTF 相关测试。