【原創】Kernel調試追蹤技術之 eBPF on ARM64
本文目標:
- 理解eBPF的核心概念和實現方法
- 探索ARM64 Linux上eBPF的使用
1. eBPF是什么?
eBPF是一種不需要修改kernel代碼,不需要加載內核模塊,就可以擴展內核功能的技術。可用於網絡包過濾,kernel行為監控和觀測,安全檢查等。
1.1 BPF and tcpdump
BPF (Berkeley Packet Filter) 是1992年就有的技術,最初用在unix上,用於網絡包過濾。
BPF的實現原理是把包過濾命令翻譯成字節碼(bytecode),在kernel中轉譯並執行這些字節碼。通過在Kernel各處加入鈎子函數,實現網絡數據包的捕獲的過濾,例如tcpdump就是基於bpf實現的。
classic BPF Overview http://www.tcpdump.org/papers/bpf-usenix93.pdf
-
tcpdump 的-d參數可以dump出執行的字節碼
$ sudo tcpdump -i enp0s10 port 22 -d [sudo] password for yu: (000) ldh [12] (001) jeq #0x86dd jt 2 jf 10 (002) ldb [20] (003) jeq #0x84 jt 6 jf 4 (004) jeq #0x6 jt 6 jf 5 (005) jeq #0x11 jt 6 jf 23 (006) ldh [54] (007) jeq #0x16 jt 22 jf 8 (008) ldh [56] (009) jeq #0x16 jt 22 jf 23 (010) jeq #0x800 jt 11 jf 23 (011) ldb [23] (012) jeq #0x84 jt 15 jf 13 (013) jeq #0x6 jt 15 jf 14 (014) jeq #0x11 jt 15 jf 23 (015) ldh [20] (016) jset #0x1fff jt 23 jf 17 (017) ldxb 4*([14]&0xf) (018) ldh [x + 14] (019) jeq #0x16 jt 22 jf 20 (020) ldh [x + 16] (021) jeq #0x16 jt 22 jf 23 (022) ret #262144 (023) ret #0
1.2 eBPF
2011年左右, Plumgrid公司的Alexei Starovoitov 等人覺得經典BPF(也叫cBPF)有些過時,比如其字節碼指令只使用2個32位寄存器,功能和效率都受限,開始對BPF做出大幅修改,新版命名為extended BPF,並且使其可以用於網絡(Networking)以外的領域,比如跟蹤(Tracing)和安全等,使得BPF向通用化技術演進。
eBPF對經典BPF(cBPF)做了兼容,所以現在說的BPF通指eBPF。
eBPF對cBPF做了如下優化:
- 字節碼寄存器從32位變為64位
- 寄存器數從2個增加到10個,可以實現主流處理器的ABI的直接映射
- JIT支持,預先翻譯成匯編指令,執行速度大增
- 實現了bpf()系統調用
下圖為eBPF的框架結構圖:
下面對BPF的核心概念做簡要介紹。
Verifier
BPF字節碼程序傳入Kernel后,首先要進行嚴格的檢驗,由Verifier(檢驗器)模塊完成。
- 不能包含循環(Direct Acyclic),如果是小循環會被展開
- 檢查越界指令
- 檢查所有分支指令,並且仿真運行
- 寄存器必須先初始化再使用,
- 不允許指針計算
- 禁止隨機內存訪問
只有通過verifier驗證后的程序才能被運行。
Map
BPF中定義各種map數據結構存放數據,用於kernel和用戶層的數據交換,比如傳遞數據包、Trace 數據等。
include/uapi/linux/bpf.h
98 enum bpf_map_type {
99 BPF_MAP_TYPE_UNSPEC,
100 BPF_MAP_TYPE_HASH, // 哈希
101 BPF_MAP_TYPE_ARRAY, // 數組
102 BPF_MAP_TYPE_PROG_ARRAY, // 程序數組
103 BPF_MAP_TYPE_PERF_EVENT_ARRAY, // PERF EVENT數組
104 BPF_MAP_TYPE_PERCPU_HASH, // PERCPU 哈希
105 BPF_MAP_TYPE_PERCPU_ARRAY, // PERCPU 數組
106 BPF_MAP_TYPE_STACK_TRACE, // 棧
107 BPF_MAP_TYPE_CGROUP_ARRAY, // Cgroup數組
108 BPF_MAP_TYPE_LRU_HASH, // LRU 哈希
109 BPF_MAP_TYPE_LRU_PERCPU_HASH,
110 BPF_MAP_TYPE_LPM_TRIE, // Longest-pre match
111 BPF_MAP_TYPE_ARRAY_OF_MAPS, // map的數組
112 BPF_MAP_TYPE_HASH_OF_MAPS, // map的哈希
113 BPF_MAP_TYPE_DEVMAP,
114 BPF_MAP_TYPE_SOCKMAP,
115 };
這些map在編寫BPF kernel程序時定義,在BPF user程序中通過bpf() 系統調用讀到用戶空間。
bpf() syscall
eBPF應用程序通過唯一的系統調用接口與kernel通信。
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
-
cmd 參數指定執行的操作
enum bpf_cmd { BPF_MAP_CREATE, // 創建一個map BPF_MAP_LOOKUP_ELEM, // 查詢一個map 元素 BPF_MAP_UPDATE_ELEM, // 更新一個map 元素 BPF_MAP_DELETE_ELEM, // 刪除一個map 元素 BPF_MAP_GET_NEXT_KEY, // 獲取下一個map的key值 BPF_PROG_LOAD, // 加載bpf程序 BPF_OBJ_PIN, // pin bpf program和map 組成的對象,防止進程退出后刪除 BPF_OBJ_GET, // 獲取object BPF_PROG_ATTACH, // attach bpf 程序到cgroup BPF_PROG_DETACH, BPF_PROG_TEST_RUN, // 測試性執行一次 BPF_PROG_GET_NEXT_ID, // 獲取下一個prog ID BPF_MAP_GET_NEXT_ID, // 獲取下一個map ID BPF_PROG_GET_FD_BY_ID, // 通過ID獲取prog的fd BPF_MAP_GET_FD_BY_ID, // 通過ID獲取map的fd BPF_OBJ_GET_INFO_BY_FD, // 通過fd獲取obj的信息 };
-
uattr 是針對各個CMD傳入的參數,比如map信息,program信息等,size是uattr的大小。
JIT (Just-in-time Compilation)
JIT是在運行時把Bytecode翻譯成Machine Code(機器碼)的技術,JAVA就是通過JIT技術使得性能大幅提升。因為Bytecode是一種虛擬的指令集,不能直接運行在CPU上,如果不支持JIT,bytecode要被通過C語言仿真執行,效率很低。目前Kernel支持的主流處理器架構都支持JIT,比如X86,ARM64, Powerpc。
BPF程序通過verifier后會被通過JIT翻譯成ASM,構造成一個函數,注冊到struct bpf_prog
的bpf_func
接口上,這樣bytecode的執行效率和原生代碼就幾乎沒差別了。
Clang 和LLVM
因為BPF使用自身特定的Bytecode,需要把C代碼編譯成bytecode,LLVM實現了BPF后端支持,所以目前主要使用Clang+LLVM的方法。GCC 需要GCC10版本才支持。
Clang是LLVM的C語言前端,負責把C編譯成LLVM的中間語言IR,再由LLVM的bpf后端編譯成bytecode。
詳細比較可參看 https://stackoverflow.com/questions/24836183/understanding-g-vs-clang-vs-llvm
eBPF工具和項目
2014年Alexei去Netflix拜訪性能分析的大神Brendan Gregg,介紹了eBPF,Brendan大贊,認為eBPF可以更方便地實現很多性能分析工具,因為類似虛擬機運行,可以對執行代碼進行驗證(Verify),安全性高,可以方便用於生產系統。Brendan把他之前開發的很多工具用eBPF改寫,且擴充了許多工具,促進了eBPF的大發展和普及。
並且寫了一本書《BPF Performance Tool - Linux System and Application Observation》。
http://www.brendangregg.com/blog/2019-07-15/bpf-performance-tools-book.html。
為了方便eBPF應用開發,eBPF開發者開發了BCC (BPF Compiler Collection),包含BPF的輔助庫和對Python、Lua等前端程序的支持,降低了編程難度。 BCC 放在ioviso (https://github.com/iovisor)項目中,除此之外還有bpftrace、ply等項目。
eBPF被稱為目前Linux上新的supper power,因為可以在產品系統中使用,可以基於eBPF在應用層實現許多網絡應用程序,例如:
- Cilium & Hubble 基於eBPF的網絡管理、監控軟件
- Cilium可以算是eBPF的官方項目,由eBPF主要設計者開發,是cilium eBPF的詳細設計和使用文檔。
- Sysdig 系統行為可視化工具
- 最初由Kernel module實現,后基於eBPF實現
eBPF對於Kernel,就像Javascript對於HTML一樣 [BPF-Rethink the Linux kernel]。
1.3 eBPF和Ftrace的區別
eBPF和tracing是不同的技術。
tracing往往會產生大量數據,需要做篩選,甚至編寫解析程序實現可視化和統計。
eBPF對內核事件、數據的獲取更有針對性,方便編成實現精細控制。
ftrace的優勢在於大多kernel原生支持,方便使用;
eBPF優勢在於擴展性極強。
2. eBPF的實現
2.1 代碼框圖
eBPF的相關代碼可以分成三個部分
- Kernel代碼
- 包括bpf核心實現
- 處理器相關的JIT支持
- 各個子系統對bpf的支持
- Kernel代碼樹的用戶層代碼
- 在tools/lib/bpf中的輔助函數庫libbpf.c
- 在samples/bpf/中的例子程序和測試程序
- iovisor項目中的BCC、bpftrace、ply等BPF開發框架和工具的代碼
- 包括bpf的C庫和python、Lua、CC等前端支持,以及應用程序
下圖為eBPF源碼結構圖:
2.2 bpf程序加載
用戶通過Clang+LLVM編譯好的bpf bytecode 通過bpf() syscall的BPF_PROG_LOAD
命令傳入kernel。
進入kernel后進行license檢查、verify、JIT、創建文件描述符,最后得到一個有符號名的bpf 函數,
# echo 1 > /proc/sys/net/core/bpf_jit_kallsyms
# cat /proc/kallsyms | grep -E "bpf_prog_.+_sys_[enter|exit]"
ffff000000086a84 t bpf_prog_f173133dc38ccf87_sys_enter [bpf]
ffff000000088618 t bpf_prog_c1bd85c092d6e4aa_sys_exit [bpf]
2.3 eBPF core, ARM64 JIT
Bytecode到ARM執行的轉換,需要先定義eBPF和ARM寄存器和指令的映射關系,代碼如下:
//寄存器映射
41 /* Map BPF registers to A64 registers */
42 static const int bpf2a64[] = {
43 /* return value from in-kernel function, and exit value from eBPF */
44 [BPF_REG_0] = A64_R(7),
45 /* arguments from eBPF program to in-kernel function */
46 [BPF_REG_1] = A64_R(0),
47 [BPF_REG_2] = A64_R(1),
48 [BPF_REG_3] = A64_R(2),
49 [BPF_REG_4] = A64_R(3),
50 [BPF_REG_5] = A64_R(4),
51 /* callee saved registers that in-kernel function will preserve */
52 [BPF_REG_6] = A64_R(19),
53 [BPF_REG_7] = A64_R(20),
54 [BPF_REG_8] = A64_R(21),
55 [BPF_REG_9] = A64_R(22),
56 /* read-only frame pointer to access stack */
57 [BPF_REG_FP] = A64_R(25),
58 /* temporary registers for internal BPF JIT */
59 [TMP_REG_1] = A64_R(10),
60 [TMP_REG_2] = A64_R(11),
61 [TMP_REG_3] = A64_R(12),
62 /* tail_call_cnt */
63 [TCALL_CNT] = A64_R(26),
64 /* temporary register for blinding constants */
65 [BPF_REG_AX] = A64_R(9),
66 };
// 指令映射
320 static int build_insn(const struct bpf_insn *insn, struct jit_ctx *ctx)
321 {
...
346
347 switch (code) {
348 /* dst = src */
349 case BPF_ALU | BPF_MOV | BPF_X:
350 case BPF_ALU64 | BPF_MOV | BPF_X:
351 emit(A64_MOV(is64, dst, src), ctx);
352 break;
353 /* dst = dst OP src */
354 case BPF_ALU | BPF_ADD | BPF_X:
355 case BPF_ALU64 | BPF_ADD | BPF_X:
356 emit(A64_ADD(is64, dst, dst, src), ctx);
357 break;
..
539 case BPF_JMP | BPF_JSGE | BPF_X:
540 case BPF_JMP | BPF_JSLE | BPF_X:
541 emit(A64_CMP(1, dst, src), ctx);
542 emit_cond_jmp:
543 jmp_offset = bpf2a64_offset(i + off, i, ctx);
544 check_imm19(jmp_offset);
545 switch (BPF_OP(code)) {
546 case BPF_JEQ:
547 jmp_cond = A64_COND_EQ;
548 break;
....
下圖為Bytecode轉換為ARM64指令的函數調用過程:
2.4 Verifier
Verifier是一個靜態代碼分析器,用來分析傳入Kernel 的BPF程序。
eBPF程序是從在用戶空間傳入的,必須進行嚴格限制,防止其導致kernel 崩潰或性能下降,所以Verifier定義了嚴格的審查規則。
Verifier對用戶是透明的,但是理解Verifier,對於理解eBPF非常有幫助,ksrc/kernel/bpf/verifier.c 開頭的一段注釋解釋了verifier的核心驗證規則,要點如下圖:
驗證主要分成兩個階段。
第一個階段檢查函數的深度和指令數,使用深度優先遍歷,並且確保沒有循環(DAG:有向無環圖),用壓棧出棧的方式實現。
第二個階段檢查每條bytecode指令,根據指令的分類(class),檢查其操作寄存器的讀寫屬性、內存訪問是否越界、BPF_CALL是否符合接口協議等。
下圖為指令檢查過程:
BCC中runqlen工具的轉譯實例:
src/iovisor/bcc-master/libbpf-tools/bin $ ./bpftool prog show
...
59: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2020-09-02T08:23:41+0800 uid 0
xlated 296B jited 200B memlock 4096B map_ids 58,59
73: perf_event name do_perf_event tag 168815884b13b0c4 gpl
loaded_at 2020-09-02T09:33:25+0800 uid 0
xlated 360B jited 210B memlock 4096B map_ids 67
$ sudo bcc.runqlen
C code
int do_perf_event()
{
unsigned int len = 0;
pid_t pid = 0;
struct task_struct *task = NULL;
struct cfs_rq_partial *my_q = NULL;
task = (struct task_struct *)bpf_get_current_task();
my_q = (struct cfs_rq_partial *)task->se.cfs_rq;
len = my_q->nr_running;
if (len > 0)
len--;
STORE
return 0;
}
Tranlated Bytecode
$ sudo ./bpftool prog dump xlated tag 168815884b13b0c4
0: (85) call bpf_get_current_task#-59136
1: (b7) r6 = 0
2: (7b) *(u64 *)(r10 -8) = r6
3: (07) r0 += 472
4: (bf) r1 = r10
5: (07) r1 += -8
6: (b7) r2 = 8
7: (bf) r3 = r0
8: (85) call bpf_probe_read#-56224
9: (79) r3 = *(u64 *)(r10 -8)
10: (63) *(u32 *)(r10 -8) = r6
...
40: (07) r3 += -8
41: (b7) r4 = 1
42: (85) call htab_map_update_elem#109984
43: (b7) r0 = 0
**** 44: (95) exit
2.5 BPF_CALL
BPF程序只能調用通過BPF_CALL
聲明的kernel接口,這樣可以保障安全和性能。
這些接口主要是在網絡、trace和bpf核心代碼中實現的的一些幫助函數。
bpf程序通過這些接口和kernel各個模塊進行交互。
Kerne4.14.74中有78個,kernel5.8.0中有170個
# Helper
./kernel/bpf/helpers.c:31: BPF_CALL_2(bpf_map_lookup_elem, struct bpf_map *, map, void *, key)
./kernel/bpf/helpers.c:46: BPF_CALL_4(bpf_map_update_elem, struct bpf_map *, map, void *, key,
./kernel/bpf/helpers.c:64: BPF_CALL_2(bpf_map_delete_elem, struct bpf_map *, map, void *, key)
./kernel/bpf/helpers.c:85: BPF_CALL_0(bpf_get_smp_processor_id)
./kernel/bpf/helpers.c:96: BPF_CALL_0(bpf_get_numa_node_id)
./kernel/bpf/helpers.c:107: BPF_CALL_0(bpf_ktime_get_ns)
./kernel/bpf/helpers.c:119: BPF_CALL_0(bpf_get_current_pid_tgid)
./kernel/bpf/helpers.c:135: BPF_CALL_0(bpf_get_current_uid_gid)
./kernel/bpf/helpers.c:155: BPF_CALL_2(bpf_get_current_comm, char *, buf, u32, size)
./kernel/bpf/core.c:1423: BPF_CALL_0(bpf_user_rnd_u32)
./kernel/bpf/stackmap.c:118:BPF_CALL_3(bpf_get_stackid, struct pt_regs *, regs, struct bpf_map *, map,
./kernel/bpf/sockmap.c:901: BPF_CALL_4(bpf_sock_map_update, struct bpf_sock_ops_kern *, bpf_sock,
# Trace
./kernel/trace/bpf_trace.c:64: BPF_CALL_3(bpf_probe_read, void *, dst, u32, size, const void *, unsafe_ptr)
./kernel/trace/bpf_trace.c:84: BPF_CALL_3(bpf_probe_write_user, void *, unsafe_ptr, const void *, src,
./kernel/trace/bpf_trace.c:128: BPF_CALL_5(bpf_trace_printk, char *, fmt, u32, fmt_size, u64, arg1,
./kernel/trace/bpf_trace.c:258: BPF_CALL_2(bpf_perf_event_read, struct bpf_map *, map, u64, flags)
./kernel/trace/bpf_trace.c:329: BPF_CALL_5(bpf_perf_event_output, struct pt_regs *, regs, struct bpf_map *, map,
./kernel/trace/bpf_trace.c:390: BPF_CALL_0(bpf_get_current_task)
./kernel/trace/bpf_trace.c:401: BPF_CALL_2(bpf_current_task_under_cgroup, struct bpf_map *, map, u32, idx)
./kernel/trace/bpf_trace.c:426: BPF_CALL_3(bpf_probe_read_str, void *, dst, u32, size,
./kernel/trace/bpf_trace.c:537: BPF_CALL_5(bpf_perf_event_output_tp, void *, tp_buff, struct bpf_map *, map,
./kernel/trace/bpf_trace.c:561: BPF_CALL_3(bpf_get_stackid_tp, void *, tp_buff, struct bpf_map *, map,
# Network
./net/core/filter.c:161: BPF_CALL_0(__get_raw_cpu_id)
./net/core/filter.c:112: BPF_CALL_1(__skb_get_pay_offset, struct sk_buff *, skb)
./net/core/filter.c:117: BPF_CALL_3(__skb_get_nlattr, struct sk_buff *, skb, u32, a, u32, x)
./net/core/filter.c:137: BPF_CALL_3(__skb_get_nlattr_nest, struct sk_buff *, skb, u32, a, u32, x)
./net/core/filter.c:1521: BPF_CALL_5(bpf_l3_csum_replace, struct sk_buff *, skb, u32, offset,
./net/core/filter.c:1565: BPF_CALL_5(bpf_l4_csum_replace, struct sk_buff *, skb, u32, offset,
./net/core/filter.c:1618: BPF_CALL_5(bpf_csum_diff, __be32 *, from, u32, from_size,
./net/core/filter.c:1657: BPF_CALL_2(bpf_csum_update, struct sk_buff *, skb, __wsum, csum)
./net/core/filter.c:1758: BPF_CALL_3(bpf_clone_redirect, struct sk_buff *, skb, u32, ifindex, u64, flags)
./net/core/filter.c:1808: BPF_CALL_2(bpf_redirect, u32, ifindex, u64, flags)
./net/core/filter.c:1885: BPF_CALL_1(bpf_get_cgroup_classid, const struct sk_buff *, skb)
./net/core/filter.c:1897: BPF_CALL_1(bpf_get_route_realm, const struct sk_buff *, skb)
./net/core/filter.c:1909: BPF_CALL_1(bpf_get_hash_recalc, struct sk_buff *, skb)
./net/core/filter.c:1926: BPF_CALL_1(bpf_set_hash_invalid, struct sk_buff *, skb)
./net/core/filter.c:3035: BPF_CALL_1(bpf_get_socket_cookie, struct sk_buff *, skb)
./net/core/filter.c:3047: BPF_CALL_1(bpf_get_socket_uid, struct sk_buff *, skb)
./net/core/filter.c:1942: BPF_CALL_2(bpf_set_hash, struct sk_buff *, skb, u32, hash)
./net/core/filter.c:1844: BPF_CALL_4(bpf_sk_redirect_map, struct sk_buff *, skb,
./net/core/filter.c:1499: BPF_CALL_2(bpf_skb_pull_data, struct sk_buff *, skb, u32, len)
./net/core/filter.c:1432: BPF_CALL_5(bpf_skb_store_bytes, struct sk_buff *, skb, u32, offset,
./net/core/filter.c:1469: BPF_CALL_4(bpf_skb_load_bytes, const struct sk_buff *, skb, u32, offset,
./net/core/filter.c:3065: BPF_CALL_5(bpf_setsockopt, struct bpf_sock_ops_kern *, bpf_sock,
./net/core/filter.c:1960: BPF_CALL_3(bpf_skb_vlan_push, struct sk_buff *, skb, __be16, vlan_proto,
./net/core/filter.c:1987: BPF_CALL_1(bpf_skb_vlan_pop, struct sk_buff *, skb)
./net/core/filter.c:2163: BPF_CALL_3(bpf_skb_change_proto, struct sk_buff *, skb, __be16, proto,
./net/core/filter.c:2202: BPF_CALL_2(bpf_skb_change_type, struct sk_buff *, skb, u32, pkt_type)
./net/core/filter.c:2318: BPF_CALL_4(bpf_skb_adjust_room, struct sk_buff *, skb, s32, len_diff,
./net/core/filter.c:2367: BPF_CALL_3(bpf_skb_change_tail, struct sk_buff *, skb, u32, new_len,
./net/core/filter.c:2418: BPF_CALL_3(bpf_skb_change_head, struct sk_buff *, skb, u32, head_room,
./net/core/filter.c:2722: BPF_CALL_5(bpf_skb_event_output, struct sk_buff *, skb, struct bpf_map *, map,
./net/core/filter.c:2752: BPF_CALL_4(bpf_skb_get_tunnel_key, struct sk_buff *, skb, struct bpf_tunnel_key *, to,
./net/core/filter.c:2819: BPF_CALL_3(bpf_skb_get_tunnel_opt, struct sk_buff *, skb, u8 *, to, u32, size)
./net/core/filter.c:2855: BPF_CALL_4(bpf_skb_set_tunnel_key, struct sk_buff *, skb,
./net/core/filter.c:2925: BPF_CALL_3(bpf_skb_set_tunnel_opt, struct sk_buff *, skb,
./net/core/filter.c:2974: BPF_CALL_3(bpf_skb_under_cgroup, struct sk_buff *, skb, struct bpf_map *, map,
./net/core/filter.c:2458: BPF_CALL_2(bpf_xdp_adjust_head, struct xdp_buff *, xdp, int, offset)
./net/core/filter.c:2639: BPF_CALL_2(bpf_xdp_redirect, u32, ifindex, u64, flags)
./net/core/filter.c:2662: BPF_CALL_4(bpf_xdp_redirect_map, struct bpf_map *, map, u32, ifindex, u64, flags,
./net/core/filter.c:3010: BPF_CALL_5(bpf_xdp_event_output, struct xdp_buff *, xdp, struct bpf_map *, map,
每個BPF_CALL都要定義一個對應的proto接口,用於verifier做參數檢檢查
const struct bpf_func_proto bpf_map_lookup_elem_proto = {
.func = bpf_map_lookup_elem,
.gpl_only = false,
.pkt_access = true,
.ret_type = RET_PTR_TO_MAP_VALUE_OR_NULL,
.arg1_type = ARG_CONST_MAP_PTR,
.arg2_type = ARG_PTR_TO_MAP_KEY,
};
2.6 BPF xxx_kern.c , xxx_user.c
BPF的編程包括兩個部分
- bpf 的kernel程序
- 定義map數據結構
- 使用有限的C語言特性,比如不能有循環
- 可以定義多個bpf prog函數,被調用的函數要用inline
- 只能調用BPF_CALL的kernel的接口
- bpf的用戶程序
- 一個可執行程序
- 調用bpf()系統調用傳入bpf kernel程序編譯后的的bytecode
- 使用循環獲取map值
示例代碼:
BPF kernel 代碼
samples/bpf/tracex4_kern.c
#include <linux/ptrace.h>
#include <linux/version.h>
#include <uapi/linux/bpf.h>
#include "bpf_helpers.h"
struct pair { //Hash map的value
u64 val;
u64 ip;
};
struct bpf_map_def SEC("maps") my_map = {
.type = BPF_MAP_TYPE_HASH, // 使用Hash類型的map
.key_size = sizeof(long), // key是一個long,實際是分配的內存的指針
.value_size = sizeof(struct pair),
.max_entries = 1000000, // 存放1M個記錄
};
/* kprobe is NOT a stable ABI. If kernel internals change t
* example will no longer be meaningful
*/
// void kmem_cache_free(struct kmem_cache *s, void *x)
SEC("kprobe/kmem_cache_free") // 使用kprobe event,探測kmem_cache_free()接口
int bpf_prog1(struct pt_regs *ctx)
{
long ptr = PT_REGS_PARM2(ctx); // 得到kmem_cache_free第二個參數, 即要釋放的地址
bpf_map_delete_elem(&my_map, &ptr); // 刪除hash map entry
return 0;
}
// void *kmem_cache_alloc_node(struct kmem_cache *s, gfp_t gfpflags, int node)
SEC("kretprobe/kmem_cache_alloc_node") //使用kretprobe event,記錄分配內存的指針
int bpf_prog2(struct pt_regs *ctx)
{
long ptr = PT_REGS_RC(ctx); // 存放返回指針的寄存器是R0
long ip = 0;
/* get ip address of kmem_cache_alloc_node() caller */
BPF_KRETPROBE_READ_RET_IP(ip, ctx); // 記錄caller 到ip,即內存分配者
struct pair v = {
.val = bpf_ktime_get_ns(),
.ip = ip,
};
bpf_map_update_elem(&my_map, &ptr, &v, BPF_ANY); // 記錄pair到hash map
return 0;
}
char _license[] SEC("license") = "GPL";
u32 _version SEC("version") = LINUX_VERSION_CODE;
BPF user 代碼
samples/bpf/tracex4_user.c
#include "libbpf.h"
#include "bpf_load.h"
struct pair {
long long val;
__u64 ip;
};
static __u64 time_get_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000ull + ts.tv_nsec;
}
static void print_old_objects(int fd)
{
long long val = time_get_ns();
__u64 key, next_key;
struct pair v;
key = write(1, "\e[1;1H\e[2J", 12); /* clear screen */
key = -1;
while (bpf_map_get_next_key(map_fd[0], &key, &next_key) == 0) { //遍歷map
bpf_map_lookup_elem(map_fd[0], &next_key, &v); // 得到map entry的值
key = next_key;
if (val - v.val < 1000000000ll)
/* object was allocated more then 1 sec ago */
continue;
printf("obj 0x%llx is %2lldsec old was allocated at ip %llx\n",
next_key, (val - v.val) / 1000000000ll, v.ip);
}
}
int main(int ac, char **argv)
{
struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
char filename[256];
int i;
snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
if (setrlimit(RLIMIT_MEMLOCK, &r)) {
perror("setrlimit(RLIMIT_MEMLOCK, RLIM_INFINITY)");
return 1;
}
// load_bpf_file -> do_load_bpf_file -> load_and_attach -> bpf_load_program -> sys_bpf(BPF_PROG_LOAD, &attr, sizeof(attr))
| | | |
samples/bpf/bpf_load.c samples/bpf/bpf_load.c tools/lib/bpf/bpf.c tools/lib/bpf/bpf.c
if (load_bpf_file(filename)) {
printf("%s", bpf_log_buf);
return 1;
}
for (i = 0; ; i++) {
print_old_objects(map_fd[1]);
sleep(1);
}
return 0;
}
3. Arm64 Linux上使用eBPF工具集BCC
BCC是eBPF最主要的支持庫和工具集,可以基於BCC寫自己的bpf程序,也可以使用已有的100多個bcc 工具進行跟蹤或性能分析。
但是因為BCC依賴於Clang+LLVM,導致大量的庫依賴,交叉編譯難度較大。
目前Android等Arm Linux系統比較方便的方案是使用adeb(Android Debian)項目的adeb。 [https://github.com/joelagnel/adeb] 。
adeb相當於預先構建一個ARM64的Debian rootfs,傳到板子上(或通過nfs掛載)后,用chroot切換到debian文件系統,就可以直接使用里面編譯好的Clang+LLVM+GCC工具鏈了。
這個ARM64 Debian文件系統是從Debian源里直接下載安裝的,可以根據需要定制安裝包,可以自動解決依賴關系。
Debian源里的BCC版本可能比較老,也可以在下載最新BCC源碼,直接在板子上編譯。
kernel需要打開的BPF相關配置:
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_HAVE_BPF_JIT=y
CONFIG_BPF_JIT=y
CONFIG_BPF_EVENTS=y
3.1 adeb安裝
adeb為Android系統設計,默認使用adb和Android的/data目錄,也支持ssh,做少許修改就可以使用
diff --git a/androdeb b/androdeb
index a2ee65b..f215508 10075
--- a/androdeb
+++ b/androdeb
@@ -14,11 +14,11 @@ source $spath/utils/remote
# Set default vars
DISTRO=buster; ARCH=arm64
ADB="adb"
-REMOTE="adb"
-FULL=0 # Default to a minimal install
+REMOTE="ssh"
+FULL=1 # Default to a minimal install
DOWNLOAD=1 # Default to downloading from web
-SKIP_DEVICE=0 # Skip device preparation
-INSTALL_BCC=0 # Decide if BCC is to be installed
+SKIP_DEVICE=1 # Skip device preparation
+INSTALL_BCC=1 # Decide if BCC is to be installed
完全安裝包需要2GB空間,可以放在/userdata分區,sd卡里,或者nfs映射。
nfs相對比較快捷,因為每次解壓安裝可能需要十幾分鍾時間。
-
方法一: 安裝到板板子上,
# 板子確保/userdata有2G空間,執行: mkdir /userdata/data mount -o remount,rw / ln -s /data /userdata/data # Linux上執行: 解壓adeb.tgz cd adeb ./adeb --ssh root@192.168.2.12 prepare --archive tars/androdeb-fs.tgz # 等待安裝完成,大概十幾分鍾... ./adeb --ssh root@192.168.2.12 shell cd /usr/share/bcc/tools/ ls # 即可執行bcc命令。
-
方法二:nfs掛載
# Linux上執行: cd adeb/tars tar xvf androdeb-fs.tgz # 板子上掛載nfs mount -o remount,rw / mkdir /data/ mount -t nfs -o nolock 192.168.2.123:/home/yu/adeb/tars/debian /data ./adeb --ssh root@192.168.2.12 shell cd /usr/share/bcc/tools/ ls # 即可執行bcc命令。
2. bcc 工具使用。
bcc自帶了100多個工具,/usr/share/bcc/tools/doc/下有每個工具的說明文檔
$ pwd
/home/yu/src/bpf/adeb/tars/debian/usr/share/bcc/tools/doc
yu@yu doc (master) $ ls
argdist_example.txt fileslower_example.txt opensnoop_example.txt stackcount_example.txt
bashreadline_example.txt filetop_example.txt perlcalls_example.txt statsnoop_example.txt
biolatency_example.txt funccount_example.txt perlflow_example.txt syncsnoop_example.txt
biosnoop_example.txt funclatency_example.txt perlstat_example.txt syscount_example.txt
biotop_example.txt funcslower_example.txt phpcalls_example.txt tclcalls_example.txt
bitesize_example.txt gethostlatency_example.txt phpflow_example.txt tclflow_example.txt
bpflist_example.txt hardirqs_example.txt phpstat_example.txt tclobjnew_example.txt
btrfsdist_example.txt inject_example.txt pidpersec_example.txt tclstat_example.txt
btrfsslower_example.txt javacalls_example.txt profile_example.txt tcpaccept_example.txt
cachestat_example.txt javaflow_example.txt pythoncalls_example.txt tcpconnect_example.txt
cachetop_example.txt javagc_example.txt pythonflow_example.txt tcpconnlat_example.txt
capable_example.txt javaobjnew_example.txt pythongc_example.txt tcpdrop_example.txt
cobjnew_example.txt javastat_example.txt pythonstat_example.txt tcplife_example.txt
cpudist_example.txt javathreads_example.txt reset-trace_example.txt tcpretrans_example.txt
cpuunclaimed_example.txt killsnoop_example.txt rubycalls_example.txt tcpstates_example.txt
criticalstat_example.txt lib rubyflow_example.txt tcpsubnet_example.txt
cthreads_example.txt llcstat_example.txt rubygc_example.txt tcptop_example.txt
dbslower_example.txt mdflush_example.txt rubyobjnew_example.txt tcptracer_example.txt
dbstat_example.txt memleak_example.txt rubystat_example.txt tplist_example.txt
dcsnoop_example.txt mountsnoop_example.txt runqlat_example.txt trace_example.txt
dcstat_example.txt mysqld_qslower_example.txt runqlen_example.txt ttysnoop_example.txt
deadlock_example.txt nfsdist_example.txt runqslower_example.txt vfscount_example.txt
drsnoop_example.txt nfsslower_example.txt shmsnoop_example.txt vfsstat_example.txt
execsnoop_example.txt nodegc_example.txt slabratetop_example.txt wakeuptime_example.txt
exitsnoop_example.txt nodestat_example.txt sofdsnoop_example.txt xfsdist_example.txt
ext4dist_example.txt offcputime_example.txt softirqs_example.txt xfsslower_example.txt
ext4slower_example.txt offwaketime_example.txt solisten_example.txt zfsdist_example.txt
filelife_example.txt oomkill_example.txt sslsniff_example.txt zfsslower_example.txt
https://github.com/iovisor/bcc/tree/master/tools
下面腦圖是分類整理:
4. Reference
- 《BPF Performance Tool - Linux System and Application Observation》, Brendan Gregg
- LWN: A thorough introduction to eBPF
- LWN: An introduction to the BPF Compiler Collection
- https://events.static.linuxfound.org/sites/events/files/slides/ELC_2017_NA_dynamic_tracing_tools_on_arm_aarch64_platform.pdf
- eBPF In-kernel Virtual Machine & Cloud Computing
- https://elinux.org/images/d/dc/Kernel-Analysis-Using-eBPF-Daniel-Thompson-Linaro.pdf
- GCC,Clang, LLVM https://stackoverflow.com/questions/24836183/understanding-g-vs-clang-vs-llvm/24836566
- eBPF Trace from Kernel to Userspace PPT, eBPF基本概念介紹非常清楚
- Using Perf and its friend eBPF on Arm platform Leo Yan, Linaro
- LWN: BPFd: Running BCC tools remotely across systems and architectures, Joel Fernandes, Jan 2018
- Linux超能力BPF技術介紹及學習分享
— End—
文章標題:Ftrace的配置和使用
本文作者:hpyu
本文鏈接:https://www.cnblogs.com/hpyu/articles/14254250.html
歡迎轉載,請注明原文鏈接# 【原創】Kernel調試追蹤技術之 eBPF on ARM64