BPF BTF 詳細介紹


本文地址:https://www.ebpf.top/post/kernel_btf/
英文文檔:https://www.kernel.org/doc/html/latest/bpf/btf.html

1. 介紹

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()= 2BTF_INT_BITS() = 4

那么在結構體的內存布局中,這個成員將占用 4 位,從 100+2 位開始。

另外,比特域結構成員可以采用以下方式來訪問與上述相同的比特位:

  • btf 成員位偏移量 102
  • btf 成員指向一個 int 類型
  • int 類型有 BTF_INT_OFFSET()= 0BTF_INT_BITS() = 4

BTF_INT_OFFSET() 的初衷是為了提供靈活的位域編碼的靈活性。目前, 對於所有的 int 類型,llvm 和 pahole 都生成了 BTF_INT_OFFSET() = 0

btf-int

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 = 0type = 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_idfunc_info、翻譯后字節碼的 line_info,以及 jited_line_info

3.7 BPF_BTF_GET_FD_BY_ID

有了從 bpf_map_infobpf_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_infobpf_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.htools/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_infoline_info 部分,而不是 type/string 部分。關於 func_infoline_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.extbpf_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.extbpf_line_info 結構的大小。

bpf_func_info->insn_offbpf_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_infoline_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.extbtf 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 相關的測試。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM