BPF BTF 詳解

yooooooo發表於2024-03-07

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