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)的开头必须是:

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 开始的进行编码。目前,支持以下类型:

#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 不是一个类型, 它代表一个已定义的子程序。

每个类型都包含以下常见的数据:

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 库底层使用的结构:

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 字段指定了字符串表中的偏移。

下面的章节将详细介绍每种类型的编码。

2.2.1 BTF_KIND_INT

struct btf_type 的编码如下:

  • name_off: 任何有效的偏移量
  • info.kind_flag: 0
  • info.kind: BTF_KIND_INT
  • info.vlen: 0
  • size: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)

对于 int 类型,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

那么在结构体的内存布局中,这个成员将占用 4 位,从 100+2 位开始。

另外,比特域结构成员可以采用以下方式来访问与上述相同的比特位:

  • btf 成员位偏移量 102
  • btf 成员指向一个 int 类型
  • int 类型有 BTF_INT_OFFSET()= 0 和 BTF_INT_BITS() = 4

BTF_INT_OFFSET() 的初衷是为了提供灵活的位域编码的灵活性。目前, 对于所有的 int 类型,llvm 和 pahole 都生成了 BTF_INT_OFFSET() = 0 。

2.2.2 btf_kind_ptr

struct btf_type 编码要求:

  • name_off: 0
  • info.kind_flag: 0
  • info.kind: BTF_KIND_PTR
  • info.vlen: 0
  • type: 指针的指向性类型

btf_type 后面没有其他类型数据。

2.2.3 BTF_KIND_ARRAY

struct btf_type 的编码要求:

  • name_off : 0
  • info.kind_flag: 0
  • info.kind: BTF_KIND_ARRAY
  • info.vlen: 0
  • size/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 规范,它的数组类型有一个 index_type。目前在 BTF 中,除了类型验证外,index_type 并没有使用。

struct btf_array 允许通过元素类型链来表示多维数组。例如,对于 “int a[5][6]",下面的类型信息说明了这种链式关系。

  * [1]: int
* [2]: array, `btf_array.type = [1]`, `btf_array.nelems = 6`
* [3]: array, `btf_array.type = [2]`, `btf_array.nelems = 5`

目前,pahole 和 llvm 都将多维数组折叠成一维数组,例如,对于 a[5][6],btf_array.nelems 等于 30。这是因为最初的用例是 map 良好打印,整个数组被打印出来,所以一维数组已经足够了。随着更多 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 或 1
  • info.kind: BTF_KIND_STRUCT 或 BTF_KIND_UNION
  • info.vlen: struct/union 成员的数量
  • info.size: struct/union 的大小,以字节为单位

btf_type 后面是 info.vlen 数量的 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.

下面的内核补丁引入了 kind_flag,并解释了两种模式存在的原因:

https://github.com/torvalds/linux/commit/9d5f9f701b1891466fb3dbb1806ad97716f95cc3#diff-fa650a64fdd3968396883d2fe8215ff3

2.2.6 BTF_KIND_ENUM

struct btf_type 编码要求:

  • name_off: 0 或偏移到一个有效的 C 标识符
  • info.kind_flag: 0
  • info.kind: BTF_KIND_ENUM
  • info.vlen: 枚举值的数量
  • size: 4

btf_type 后面是 info.vlen 的数量的 struct btf_enum 结构。

struct btf_enum {
__u32 name_off;
__s32 val;
};

btf_enum 的编码:

  • name_off: 偏移到一个有效的 C 标识符
  • val: 任何数值

2.2.7 BTF_KIND_FWD

struct btf_type 编码要求:

  • name_off: 一个有效的 C 语言标识符偏移
  • info.kind_flag: 0 代表 struct,1 代表 union
  • info.kind: BTF_KIND_FWD
  • info.vlen: 0
  • type: 0

在 btf_type 后面没有其他类型数据。

2.2.8 BTF_KIND_TYPEDEF

struct btf_type 编码要求:

  • name_off: 偏移到一个有效的 C 标识符。
  • info.kind_flag: 0
  • info.kind: BTF_KIND_TYPEDEF
  • info.vlen: 0
  • type: 在 name_off 处可以通过名字引用的类型。

btf_type 后面没有其他类型数据。

2.2.9 BTF_KIND_VOLATILE

struct btf_type 的编码要求:

  • name_off: 0
  • info.kind_flag: 0
  • info.kind: btf_kind_volatile
  • info.vlen: 0
  • type: 带有 volatile 限定符的类型

btf_type 后面没有其他类型数据。

2.2.10 BTF_KIND_CONST

struct btf_type 的编码要求:

  • name_off: 0
  • info.kind_flag: 0
  • info.kind: BTF_KIND_CONST
  • info.vlen: 0
  • type: 带有 const 限定词的类型

btf_type 后面没有其他类型数据。

2.2.11 BTF_KIND_RESTRICT

struct btf_type 的编码要求:

  • name_off: 0
  • info.kind_flag: 0
  • info.kind: btf_kind_restrict
  • info.vlen: 0
  • type: 带有 restrict 限定词的类型

btf_type 后面没有其他类型数据。

2.2.12 BTF_KIND_FUNC

struct btf_type 的编码要求:

  • name_off: 偏移到一个有效的 C 标识符
  • info.kind_flag: 0
  • info.kind: BTF_KIND_FUNC
  • info.vlen: 0
  • type: 一个 BTF_KIND_FUNC_PROTO 类型

btf_type 后面没有其他类型数据。

BTF_KIND_FUNC 定义的不是一个类型,而是一个子程序(函数),其签名由 type 定义。 因此,该子程序是该类型的一个实例。BTF_KIND_FUNC 可以反过被 BTF_Ext_Section(ELF) 中的 func_info 或 BPF_Prog_Load 的参数中引用 (ABI)。

2.2.13 BTF_KIND_FUNC_PROTO

struct btf_type 编码要求:

  • name_off: 0
  • info.kind_flag: 0
  • info.kind: BTF_KIND_FUNC_PROTO
  • info.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.14 BTF_KIND_VAR

struct btf_type 编码要求:

  • name_off: 偏移到一个有效的 C 标识符
  • info.kind_flag: 0
  • info.kind: BTF_KIND_VAR
  • info.vlen: 0
  • type: 变量的类型

btf_type 后面有一个 struct btf_variable, 其数据如下:

Sep 23, 2021struct btf_var {__u32   linkage;};

struct btf_var 的编码:

  • linkage: 目前只有静态变量 0,或全局分配的在 ELF 部分的变量 1

目前,LLVM 并不支持所有类型的全局变量。以下是目前可用的:

  • 带或不带区段属性的静态变量
  • 带有区段属性的全局变量

后者是为了将来从 map 定义中提取键/值类型的 ID。

2.2.15 BTF_KIND_DATASEC

struct btf_type 编码要求:

name_off: 与变量或.data/.bss 之一相关的有效名称的偏移量。 数据 .bss/.rodata 中的一个。

  • info.kind_flag: 0
  • info.kind: BTF_KIND_DATASEC
  • info.vlen: 变量的数量
  • size: 总的部分大小,以字节为单位(编译时为 0,由 BPF 加载器,如 libbp 修订为实际大小)

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: 变量的大小,以字节为单位

3. BTF 内核 API

以下 bpf 系统调用命令涉及 BTF:

  • BPF_BTF_LOAD:将 BTF 数据的 blob 数据加载到内核中
  • BPF_MAP_CREATE:用 btf 键和值类型信息创建 map
  • 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)工具:

     ......
BPF_{PROG,MAP}_GET_NEXT_ID (获取 prog/map ids)
|
V
BPF_{PROG,MAP}_GET_FD_BY_ID (获取 prog/map fd)
|
V
BPF_OBJ_GET_INFO_BY_FD (通过 fd 获取 bpf_prog_info/bpf_map_info)
| |
V |
BPF_BTF_GET_FD_BY_ID (获取 btf_fd) |
| |
V |
BPF_OBJ_GET_INFO_BY_FD (获取 btf 信息) |
| |
V V
更好打印类型、函数签名和代码行信息等\

3.1 BPF_BTF_LOAD

将一个 BTF 的 blob 数据加载到内核中。BTF_Type_String 中描述的 blob 数据,可以直接加载到内核中, 返回 btf_fd 至用户空间。

3.2 BPF_MAP_CREATE

可以用 btf_fd 和指定的键/值类型 id 创建一个 map。

__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 中,可以用额外的注解来定义 map,如下所示:

struct bpf_map_def SEC("maps") btf_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(int),
.value_size = sizeof(struct ipv_counts),
.max_entries = 4,
};
BPF_ANNOTATE_KV_PAIR(btf_map, int, struct ipv_counts);

这里,宏 BPF_ANNOTATE_KV_PAIR 的参数是 map 的名称、键和值类型。在 ELF 解析期间,libbpf 能够提取 key/value type_id,并把它们自动地分配给 BPF_MAP_CREATE 属性。

3.3 BPF_PROG_LOAD

在 prog_load 过程中,func_info 和 line_info 可以被传递给 kernel,并为以下属性提供适当的值。

__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 func 的边界。

以下是对 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

在内核中,每个加载的程序、map 或 btf 都有一个唯一的 id。这个 id 在一个程序、map 或 btf 的生命周期内不会改变。

bpf 系统调用命令 BPF__{PROG,MAP}__GET_NEXT_ID将 bpf 程序或 map 的所有 id,每个命令一个,分别返回到用户空间,因此,检查工具可以检查所有程序和 map。

3.5 BPF__{PROG,MAP}__GET_FD_BY_ID

检查工具不能使用 id 来获取程序或 map 的细节。需要首先获得一个文件描述符,以达到引用计数的目的。

3.6 BPF_OBJ_GET_INFO_BY_FD

一旦获得了一个程序/map fd,检查工具就可以从内核中获得这个 fd 的详细信息,其中一些是与 BTF 相关的。例如,bpf_map_info 返回 btf_id 和键/值类型 id。bpf_prog_info 返回 btf_id、func_info、翻译后字节码的 line_info,以及 jited_line_info。

3.7 BPF_BTF_GET_FD_BY_ID

有了从 bpf_map_info 和 bpf_prog_info 中获得的 btf_id,bpf 系统调用命令 BPF_OBJ_GET_INFO_BY_FD 就可以获取到 btf fd。然后,使用命令 BPF_OBJ_GET_INFO_BY_FD,就可以重新获取最初使用 BPF_BTF_LOAD 命令加载到内核的 btf blob。

有了 btf blob、bpf_map_info 和 bpf_prog_info,检查工具就拥有完整的 btf 信息,更好地打印出 map 键/值, func 签名和行信息,以及字节/jit 代码。

4.ELF 文件格式接口

4.1 .BTF 部分

.BTF 部分包含 type 和 string 数据。这个部分的格式是与 BTF_Type_String 中描述的相同。

4.2 .BTF.ext 部分

.BTF.ext 部分为 func_info 和 line_info 的编码,这些信息在加载到内核之前需要加载器处理。

.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; /* 偏移量基于本头部结构体的尾部 */
__u32 func_info_off;
__u32 func_info_len;
__u32 line_info_off;
__u32 line_info_len;
};

它与 .BTF 部分非常相似,它包含了 func_info 和 line_info 部分,而不是 type/string 部分。关于 func_info 和 line_info 记录格式的详细信息,请参见 BPF_Prog_Load 部分。

func_info 的组织方式如下:

func_info_rec_size
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
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 是结构单元中的指令偏移量 bpf_insn 为单位的。对于 ELF API,insn_off 是部分(btf_ext_info_sec->sec_name_off)开始的字节偏移。

5. 使用 BTF

5.1 bpftool map 更友好地打印

基于 BTF,map 的键/值可以根据字段来打印,而不只是打印简单的原始字节。这对于大的结构或如果数据结构有比特字段时,这一点特别有价值。例如,对于下面这个 map:

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 bpf_map_def SEC("maps") tmpmap = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(struct tmp_t),
.max_entries = 1,
};
BPF_ANNOTATE_KV_PAIR(tmpmap, int, struct tmp_t);

bpftool 可以像下面这样更好地打印:

[{
"key": 0,
"value": {
"a1": 0x2,
"a2": 0x4,
"a3": 0x6,
"b": 7,
"b1": 0x8,
"b2": 0xa
}
}
]

5.2 bpftool 程序打印(dump)

下面是一个例子,说明 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 转换器。pahole 目前还不支持 .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 # vlen 表示有 3 个成员变量
a type_id=2 bitfield_size=2 bits_offset=0 # type_id = 2 即为 [2] 中的代表
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

pahole 工具可以用于发现 C 语言中结构体的内存排列情况,用于内存优化,一般需要源码编译,底层依赖 libdw-dev 库。

$ ./pahole/build/pahole t2.o
struct t2 {
int a2; /* 0 4 */
/* XXX 4 bytes hole, try to pack */
int (*f2)(char, __int32, ...); /* 8 8 */
int (*f3)(void); /* 16 8 */ /* size: 24, cachelines: 1, members: 3 */
/* sum members: 20, holes: 1, sum holes: 4 */
/* last cacheline: 24 bytes */
};

对于编译对象为 bpf 目标的情况下,llvm 添加编译添加 -g 选项参数可直接生成 .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 # magic 0xeb9f btf_header
.byte 1 # version
.byte 0 # flags
.long 24 # hdr_len
.long 0 # type_off
.long 220 # type_len
.long 220 # str_off
.long 122 # str_len
.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 selftest test_btf.c 提供了大量与 BTF 相关的测试。

BPF BTF 详解的更多相关文章

  1. [Java入门笔记] 面向对象编程基础(二):方法详解

    什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...

  2. libpcap详解【转】

    libpcap详解 2010-12-01 22:07 libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库.它是一个独 ...

  3. 你对linux了解多少,Linux 系统结构详解!

    最近一直有人在请教老K关于Linux系统相关问题,这里我就该问题做个详解,Linux系统一般有4个主要部分:内核.shell.文件系统和应用程序. 内核.shell和文件系统一起形成了基本的操作系统结 ...

  4. Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)

    [Android布局学习系列]   1.Android 布局学习之——Layout(布局)详解一   2.Android 布局学习之——Layout(布局)详解二(常见布局和布局参数)   3.And ...

  5. freebsd网卡驱动程序详解

    freebsd网卡驱动程序详解 来源 https://blog.csdn.net/h_cszc/article/details/7776116 /* 注释:xie_minix */ /*此处为BSD申 ...

  6. suricata.yaml (一款高性能的网络IDS、IPS和网络安全监控引擎)默认配置文件(图文详解)

    不多说,直接上干货! 前期博客 基于CentOS6.5下Suricata(一款高性能的网络IDS.IPS和网络安全监控引擎)的搭建(图文详解)(博主推荐) 或者 基于Ubuntu14.04下Suric ...

  7. 基于Windows7下snort+apache+php 7 + acid(或者base) + adodb + jpgraph的入侵检测系统的搭建(图文详解)(博主推荐)

    为什么,要写这篇论文? 是因为,目前科研的我,正值研三,致力于网络安全.大数据.机器学习.人工智能.区域链研究领域! 论文方向的需要,同时不局限于真实物理环境机器实验室的攻防环境.也不局限于真实物理机 ...

  8. Linux 网络分析必备技能:tcpdump 实战详解

    大家好,我是肖邦,这是我的第 11 篇原创文章. 今天要分享的是 tcpdump,它是 Linux 系统中特别有用的网络工具,通常用于故障诊断.网络分析,功能非常的强大. 相对于其它 Linux 工具 ...

  9. Wireshark过滤器详解

    Wireshark过滤器详解 1.Wireshark主要提供两种主要的过滤器 捕获过滤器:当进行数据包捕获时,只有那些满足给定的包含/排除表达式的数据包会被捕获 显示过滤器:该过滤器根据指定的表达式用 ...

  10. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

随机推荐

  1. 微服务:gateway

    网关路由: 1.创建新模块 2.引入网关依赖 <!--网关--> <dependency> <groupId>org.springframework.cloud&l ...

  2. U盘安装Ubuntu18.04系统

    U盘安装Ubuntu18.04系统 一.安装盘制作 根据您当前使用系统的不同,需要不同的工具制作 U 盘启动安装盘.目前主要有 Linux 系统和 Windows 系统两类. 1.Linux 系统 ( ...

  3. Java--匿名类(学习笔记)

    匿名类的特点:(1) 匿名类是final类:(3) 在匿名类中可以定义实例变量和若干个实例初始化代码块和新的实例方法.Java虚拟机首先调用父类的构造方法,然后按照实例变量的和实例初始化代码块定义的先 ...

  4. 【Lodop】01 Lodop手册阅读上手

    官方网站: http://www.c-lodop.com/index.html 版本:6.2.2.6 一.概述 Lodop是一款用于WEB打印开发的专业WEB打印控件 控件发布包有3个系统文件组成,主 ...

  5. 【CDQ分治】【模板】三维偏序(陌上花开)

    P3810 [模板]三维偏序(陌上花开) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include <bits/stdc++.h> using namespace ...

  6. 基于surging的木舟IOT平台如何添加网络组件

    一 . 概述 为了弥补代码的遗失,木舟IOT平台正在加班加点进行研发,后面不只是针对于IOT设备接入上报,告警,视频管理,组态数据可视化大屏,后面还会有快速搭建微服务平台,利用surging.cli工 ...

  7. 【CentOS7】之执行yum命令报错

    备份文件: # CentOS-Base.repo # # The mirror system uses the connecting IP address of the client and the ...

  8. Go语言目前主要有哪些应用框架

    Go语言是一种高效.快速.简洁的编程语言,近年来越来越受到开发者的欢迎.由于Go语言的快速发展,出现了很多的优秀框架来支持Go应用程序的开发.以下是一些目前比较流行的Go语言框架: 1. Gin:Gi ...

  9. Android升 Androidx 语系切换失效

    背景: 一个很旧的Android项目,android升androidx 切换语系失败,debug的时候,传的语系值是对的,但是确实没有国际化效果 原因: 经过一番学习,原因是使用 implementa ...

  10. axis2添加拦截器

    项目背景: 2002年的某保险老项目,项目是部署了多个服务器,每个服务器有2到多个服务(每个服务的日志对应一个日志文件),外部对接是通过F5分发到随机服务器上来进行访问,正式出现问题或者看一些问题就需 ...