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: 0

  • info.kind: BTF_KIND_INT

  • info.vlen: 0

  • size: 整型型別的大小(以位元組為單位)。

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

那麼在結構體記憶體佈局中,此成員將從位 100 + 2 = 102 開始佔用 4 位。

或者,位欄位結構成員可以透過以下方式訪問與上述相同的位:

  • btf 成員位偏移量 102,

  • btf 成員指向一個整型,

  • 整型具有 BTF_INT_OFFSET() = 0BTF_INT_BITS() = 4

BTF_INT_OFFSET() 的最初目的是提供位欄位編碼的靈活性。目前,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 可以是任何常規整型(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 或 1

  • info.kind: BTF_KIND_STRUCT 或 BTF_KIND_UNION

  • info.vlen: 結構體/聯合體成員的數量

  • info.size: 結構體/聯合體的大小(以位元組為單位)

btf_type 後面跟著 info.vlenstruct 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_ENUM

  • info.vlen: 列舉值數量

  • size: 1/2/4/8

btf_type 後面跟著 info.vlenstruct 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_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: 連結資訊 (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: 0

  • info.kind_flag: 0

  • info.kind: BTF_KIND_FUNC_PROTO

  • info.vlen: 引數數量

  • type: 返回型別

btf_type 後面跟著 info.vlenstruct 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.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,包含以下資料:

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: 0

  • info.kind: BTF_KIND_DATASEC

  • info.vlen: 變數數量

  • size: 段總大小(以位元組為單位)(編譯時為 0,由 BPF 載入器(如 libbpf)

    修補為實際大小)

btf_type 後面跟著 info.vlenstruct 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: 0

  • info.kind: BTF_KIND_FLOAT

  • info.vlen: 0

  • size: 浮點型的大小(以位元組為單位):2、4、8、12 或 16。

btf_type 之後沒有額外的型別資料。

2.2.17 BTF_KIND_DECL_TAG

struct btf_type 編碼要求
  • name_off: 指向非空字串的偏移量

  • info.kind_flag: 0 或 1

  • info.kind: BTF_KIND_DECL_TAG

  • info.vlen: 0

  • type: struct, union, func, vartypedef

btf_type 後面跟著 struct btf_decl_tag

struct btf_decl_tag {
    __u32   component_idx;
};

type 應該是 structunionfuncvartypedef。對於 vartypedef 型別,btf_decl_tag.component_idx 必須為 -1。對於其他三種類型,如果 btf_decl_tag 屬性應用於 structunionfunc 本身,則 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 或 1

  • info.kind: BTF_KIND_TYPE_TAG

  • info.vlen: 0

  • type: 具有 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_ENUM64

  • info.vlen: 列舉值數量

  • size: 1/2/4/8

btf_type 後面跟著 info.vlenstruct 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 函式連結常量值

函式連結值及含義

種類

描述

BTF_FUNC_STATIC

0x0

子程式定義在包含編譯單元外部不可見

BTF_FUNC_GLOBAL

0x1

子程式定義在包含編譯單元外部可見

BTF_FUNC_EXTERN

0x2

子程式宣告,其定義在包含編譯單元外部

2.3.2 變數連結常量值

變數連結值及含義

種類

描述

BTF_VAR_STATIC

0x0

全域性變數定義在包含編譯單元外部不可見

BTF_VAR_GLOBAL_ALLOCATED

0x1

全域性變數定義在包含編譯單元外部可見

BTF_VAR_GLOBAL_EXTERN

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_infobpf_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_infobpf_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.htools/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_offbpf_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_LISTBTF_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 相關測試。