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
提供了类型/字符串编码方式的高级定义。
数据 blob 的开头必须是
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 */
};
magic 是 0xeB9F
,它对于大端和小端系统具有不同的编码,可用于测试 BTF 是为大端还是小端目标生成的。btf_header
被设计为可扩展的,当生成数据 blob 时,hdr_len
等于 sizeof(struct btf_header)
。
2.1 字符串编码¶
字符串部分中的第一个字符串必须是空字符串。其余的字符串表是其他以 null 结尾的字符串的串联。
2.2 类型编码¶
类型 id 0
保留给 void
类型。类型部分按顺序解析,类型 id 从 id 1
开始分配给每个识别的类型。目前,支持以下类型
#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, fwd, enum and enum64.
*/
__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
:int 类型的字节大小。
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)
BTF_INT_ENCODING
具有以下属性
#define BTF_INT_SIGNED (1 << 0)
#define BTF_INT_CHAR (1 << 1)
#define BTF_INT_BOOL (1 << 2)
BTF_INT_ENCODING()
提供额外信息:int 类型的有符号性、char 或 bool。char 和 bool 编码主要用于美化打印。对于 int 类型,最多可以指定一种编码。
BTF_INT_BITS()
指定此 int 类型实际保留的位数。例如,一个 4 位位域编码 BTF_INT_BITS()
等于 4。对于该类型,btf_type.size * 8
必须等于或大于 BTF_INT_BITS()
。BTF_INT_BITS()
的最大值为 128。
BTF_INT_OFFSET()
指定起始位偏移量,用于计算此 int 的值。例如,位域结构成员具有
btf 成员从结构开始的位偏移量为 100,
btf 成员指向一个 int 类型,
int 类型具有
BTF_INT_OFFSET() = 2
和BTF_INT_BITS() = 4
那么在结构内存布局中,此成员将占用从位 100 + 2 = 102
开始的 4
位。
或者,位域结构成员可以是以下内容,以访问与上述相同的位
btf 成员位偏移量为 102,
btf 成员指向一个 int 类型,
int 类型具有
BTF_INT_OFFSET() = 0
和BTF_INT_BITS() = 4
BTF_INT_OFFSET()
的最初目的是提供位域编码的灵活性。目前,llvm 和 pahole 都为所有 int 类型生成 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
可以是任何常规的 int 类型(u8
、u16
、u32
、u64
、unsigned __int128
)。包含 index_type
的原始设计遵循 DWARF,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 或枚举类型。如果位域大小为 32,则基本类型可以是 int 或枚举类型。如果位域大小不是 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
。
Commit 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
: 枚举值的数量大小
: 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
: 0类型
: 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) 或 3.3 BPF_PROG_LOAD (ABI) 的参数中的 func_info 引用。
目前,内核中仅支持 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,由 libbpf 等 BPF 加载器修补为实际大小)实际大小由 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
: float 类型的大小(以字节为单位):2、4、8、12 或 16。
btf_type
之后没有其他类型数据。
2.2.17 BTF_KIND_DECL_TAG¶
struct btf_type
编码要求name_off
: 指向非空字符串的偏移量info.kind_flag
: 0info.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;
};
name_off
编码 btf_decl_tag 属性字符串。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 开始),指向成员或参数。
2.2.18 BTF_KIND_TYPE_TAG¶
struct btf_type
编码要求name_off
: 指向非空字符串的偏移量info.kind_flag
: 0info.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 类型之一。
2.2.19 BTF_KIND_ENUM64¶
struct btf_type
编码要求name_off
: 0 或指向有效 C 标识符的偏移量info.kind_flag
: 0 表示无符号,1 表示有符号info.kind
: BTF_KIND_ENUM64info.vlen
: 枚举值的数量大小
: 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 数据 blob 加载到内核中
BPF_MAP_CREATE:使用 btf 键和值类型信息创建映射。
BPF_PROG_LOAD:使用 btf 函数和行信息加载程序。
BPF_BTF_GET_FD_BY_ID:获取 btf fd
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 数据 blob 加载到内核中。可以直接将2. BTF 类型和字符串编码中描述的数据 blob 加载到内核中。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 的要求
每个函数中的第一个 insn 必须有一个指向它的 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,每个命令一个 ID,分别用于 bpf 程序或映射,因此检查工具可以检查所有程序和映射。
3.5 BPF_{PROG,MAP}_GET_FD_BY_ID¶
内省工具不能使用 ID 来获取有关程序或映射的详细信息。需要首先获取文件描述符,以便进行引用计数。
3.6 BPF_OBJ_GET_INFO_BY_FD¶
获取程序/映射 fd 后,内省工具可以从内核获取有关此 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 fd。然后,使用命令 BPF_OBJ_GET_INFO_BY_FD,可以检索最初使用 BPF_BTF_LOAD 加载到内核中的 btf blob。
有了 btf blob、bpf_map_info
和 bpf_prog_info
,内省工具就可以获得完整的 btf 知识,并能够漂亮地打印映射键/值,转储函数签名和行信息以及字节/jit 代码。
4. ELF 文件格式接口¶
4.1 .BTF section¶
.BTF section 包含类型和字符串数据。此 section 的格式与 2. BTF 类型和字符串编码 中描述的格式相同。
4.2 .BTF.ext section¶
.BTF.ext section 编码 func_info、line_info 和 CO-RE 重定位,在加载到内核之前需要加载器操作。
.BTF.ext section 的规范在 tools/lib/bpf/btf.h
和 tools/lib/bpf/btf.c
中定义。
.BTF.ext section 的当前标头
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 section 非常相似。它不包含类型/字符串 section,而是包含 func_info、line_info 和 core_relo 子 section。有关 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 section 的 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
结构的大小。
内核 API 和 ELF API 之间对 bpf_func_info->insn_off
和 bpf_line_info->insn_off
的解释不同。对于内核 API,insn_off
是以 struct bpf_insn
为单位的指令偏移量。对于 ELF API,insn_off
是从 section 开头(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 节仅包含关联的 base .BTF 节中没有的类型 - 是一种极其高效的方式来编码内核模块的类型信息,因为它们通常由一些特定于模块的类型和一大组共享的内核类型组成。前者在拆分 BTF 中编码,而后者在 base BTF 中编码,从而产生更紧凑的表示。拆分 BTF 中引用 base BTF 中类型的类型使用其 base BTF ID 引用它,并且拆分 BTF ID 从 last_base_BTF_ID + 1 开始。
然而,这种方法的缺点是,这使得拆分 BTF 有点脆弱 - 当 base BTF 更改时,base BTF ID 引用不再有效,并且拆分 BTF 本身变得无用。.BTF.base 节的作用是使拆分 BTF 在 base BTF 可能更改的情况下(例如,对于并非每次都构建内核的内核模块),更具弹性。.BTF.base 包含命名的 base 类型:INT、FLOAT、STRUCT、UNION、ENUM[64] 和 FWD。INT 和 FLOAT 在 .BTF.base 节中完全描述,而像结构体和联合体这样的复合类型则没有完全定义 - .BTF.base 类型仅用作拆分 BTF 引用的类型的描述,因此结构体/联合体在 .BTF.base 节中具有 0 个成员。ENUM[64] 也以 0 个成员记录。任何其他类型都添加到拆分 BTF 中。然后,此提炼过程为我们留下了一个具有这些基本类型的最少描述的 .BTF.base 节,以及一个引用这些基本类型的 .BTF 拆分节。稍后,我们可以使用存储在 .BTF.base 节和新的 .BTF base 中的信息来重定位拆分 BTF;.BTF.base 节中的类型信息允许我们更新拆分 BTF 引用,以指向相应的新 base BTF ID。
当内核模块具有 .BTF.base 节时,BTF 重定位发生在内核模块加载时,libbpf 还提供了一个 btf__relocate() API 来完成此操作。
例如,考虑以下 base 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
如果我们稍后使用以下新的 base 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。
.BTF.base 节将为树外内核模块构建自动生成 - 即,设置了 KBUILD_EXTMOD 的情况(例如“make M=path/2/mod”的情况)。.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 prog dump¶
以下示例显示 func_info 和 line_info 如何帮助 prog dump 提供更好的内核符号名称、函数原型和行信息。
$ 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 能够直接为 bpf 目标生成 .BTF 和 .BTF.ext,带有 -g 选项。汇编代码 (-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 相关测试。