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