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: 0info.kind: BTF_KIND_INTinfo.vlen: 0size: 整型型別的大小(以位元組為單位)。
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() = 2和BTF_INT_BITS() = 4
那麼在結構體記憶體佈局中,此成員將從位 100 + 2 = 102 開始佔用 4 位。
或者,位欄位結構成員可以透過以下方式訪問與上述相同的位:
btf 成員位偏移量 102,
btf 成員指向一個整型,
整型具有
BTF_INT_OFFSET() = 0和BTF_INT_BITS() = 4
BTF_INT_OFFSET() 的最初目的是提供位欄位編碼的靈活性。目前,llvm 和 pahole 都為所有整型生成 BTF_INT_OFFSET() = 0。
2.2.2 BTF_KIND_PTR¶
struct btf_type編碼要求name_off: 0info.kind_flag: 0info.kind: BTF_KIND_PTRinfo.vlen: 0type: 指標指向的型別
btf_type 之後沒有額外的型別資料。
2.2.3 BTF_KIND_ARRAY¶
struct btf_type編碼要求name_off: 0info.kind_flag: 0info.kind: BTF_KIND_ARRAYinfo.vlen: 0size/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 或 1info.kind: BTF_KIND_STRUCT 或 BTF_KIND_UNIONinfo.vlen: 結構體/聯合體成員的數量info.size: 結構體/聯合體的大小(以位元組為單位)
btf_type 後面跟著 info.vlen 個 struct 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_ENUMinfo.vlen: 列舉值數量size: 1/2/4/8
btf_type 後面跟著 info.vlen 個 struct 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_FWDinfo.vlen: 0type: 0
btf_type 之後沒有額外的型別資料。
2.2.8 BTF_KIND_TYPEDEF¶
struct btf_type編碼要求name_off: 指向有效 C 識別符號的偏移量info.kind_flag: 0info.kind: BTF_KIND_TYPEDEFinfo.vlen: 0type: 可以透過name_off處的名稱引用的型別
btf_type 之後沒有額外的型別資料。
2.2.9 BTF_KIND_VOLATILE¶
struct btf_type編碼要求name_off: 0info.kind_flag: 0info.kind: BTF_KIND_VOLATILEinfo.vlen: 0type: 具有volatile限定符的型別
btf_type 之後沒有額外的型別資料。
2.2.10 BTF_KIND_CONST¶
struct btf_type編碼要求name_off: 0info.kind_flag: 0info.kind: BTF_KIND_CONSTinfo.vlen: 0type: 具有const限定符的型別
btf_type 之後沒有額外的型別資料。
2.2.11 BTF_KIND_RESTRICT¶
struct btf_type編碼要求name_off: 0info.kind_flag: 0info.kind: BTF_KIND_RESTRICTinfo.vlen: 0type: 具有restrict限定符的型別
btf_type 之後沒有額外的型別資料。
2.2.12 BTF_KIND_FUNC¶
struct btf_type編碼要求name_off: 指向有效 C 識別符號的偏移量info.kind_flag: 0info.kind: BTF_KIND_FUNCinfo.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: 0info.kind_flag: 0info.kind: BTF_KIND_FUNC_PROTOinfo.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.2.14 BTF_KIND_VAR¶
struct btf_type編碼要求name_off: 指向有效 C 識別符號的偏移量info.kind_flag: 0info.kind: BTF_KIND_VARinfo.vlen: 0type: 變數的型別
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: 0info.kind: BTF_KIND_DATASECinfo.vlen: 變數數量size: 段總大小(以位元組為單位)(編譯時為 0,由 BPF 載入器(如 libbpf)修補為實際大小)
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: 變數的大小(以位元組為單位)
2.2.16 BTF_KIND_FLOAT¶
struct btf_type編碼要求name_off: 任何有效偏移量info.kind_flag: 0info.kind: BTF_KIND_FLOATinfo.vlen: 0size: 浮點型的大小(以位元組為單位):2、4、8、12 或 16。
btf_type 之後沒有額外的型別資料。
2.2.17 BTF_KIND_DECL_TAG¶
struct btf_type編碼要求name_off: 指向非空字串的偏移量info.kind_flag: 0 或 1info.kind: BTF_KIND_DECL_TAGinfo.vlen: 0type:struct,union,func,var或typedef
btf_type 後面跟著 struct btf_decl_tag。
struct btf_decl_tag {
__u32 component_idx;
};
type 應該是 struct、union、func、var 或 typedef。對於 var 或 typedef 型別,btf_decl_tag.component_idx 必須為 -1。對於其他三種類型,如果 btf_decl_tag 屬性應用於 struct、union 或 func 本身,則 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 或 1info.kind: BTF_KIND_TYPE_TAGinfo.vlen: 0type: 具有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_ENUM64info.vlen: 列舉值數量size: 1/2/4/8
btf_type 後面跟著 info.vlen 個 struct 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 函式連結常量值¶
種類 |
值 |
描述 |
|---|---|---|
|
0x0 |
子程式定義在包含編譯單元外部不可見 |
|
0x1 |
子程式定義在包含編譯單元外部可見 |
|
0x2 |
子程式宣告,其定義在包含編譯單元外部 |
2.3.2 變數連結常量值¶
種類 |
值 |
描述 |
|---|---|---|
|
0x0 |
全域性變數定義在包含編譯單元外部不可見 |
|
0x1 |
全域性變數定義在包含編譯單元外部可見 |
|
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_info 和 bpf_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_info 和 bpf_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.h 和 tools/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_off 和 bpf_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_LIST 和 BTF_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 相關測試。