本文地址: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
: 0info.kind
: BTF_KIND_INTinfo.vlen
: 0size
: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()= 2
和BTF_INT_BITS() = 4
那么在結構體的內存布局中,這個成員將占用 4
位,從 100+2
位開始。
另外,比特域結構成員可以采用以下方式來訪問與上述相同的比特位:
- btf 成員位偏移量 102
- btf 成員指向一個 int 類型
- int 類型有
BTF_INT_OFFSET()= 0
和BTF_INT_BITS() = 4
BTF_INT_OFFSET()
的初衷是為了提供靈活的位域編碼的靈活性。目前, 對於所有的 int 類型,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
可以是任何常規的 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 或 1info.kind
: BTF_KIND_STRUCT 或 BTF_KIND_UNIONinfo.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
: 0info.kind
: BTF_KIND_ENUMinfo.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 代表 unioninfo.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
: 0type
: 一個 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
: 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.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
, 其數據如下:
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
: 0info.kind
: BTF_KIND_DATASECinfo.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_id
、func_info
、翻譯后字節碼的 line_info
,以及 jited_line_info
。
3.7 BPF_BTF_GET_FD_BY_ID
有了從 bpf_map_info
和 bpf_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_info
和 bpf_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.h
和 tools/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_info
和 line_info
部分,而不是 type/string
部分。關於 func_info
和 line_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.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
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
是結構單元中的指令偏移量 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_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 轉換器。pahole 目前還不支持 .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 # 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 相關的測試。