eBPF學習筆記(一)概述
eBPF技術簡述
eBPF 是起源於核心的革命性技術,從 2011 年開發至今,eBPF 社群依然非常活躍 。eBPF 可以通過熱載入的sandbox程式到核心中而不需要insmod module的方式,避免核心模組的方式可能會引入宕機風險,並具備堪比原生程式碼的執行效率。eBPF程式不會引起系統宕機,Just-In-Time (JIT) compiler編譯器假設程式碼是安全但有 verification engine從組合語言級別檢查程式的有效性,程式中規定了記憶體範圍、有限的迴圈、bpf_spin_lock()不會死鎖、不可以在free之後使用、不可以有記憶體洩露等。
使用eBPF技術可以應用在很多場景中:
- 在現代資料中心和雲原生環境中提供高效能的網路和負載平衡
- 以低開銷提取細粒度的安全可觀察資料,幫助應用程式開發人員跟蹤應用程式,提供效能故障排除、應用程式和容器執行時安全實施的見解
等等,可能性是無限的。
eBPF架構圖
圖1 簡易的eBPF程式架構圖
圖1是一個簡易的eBPF程式架構圖,它描述了一個eBPF程式的主要構成部分和程式編譯、執行順序。先來看一個eBPF程式的主要構成部分:
1. 使用者空間的BPF位元組碼:圖1中使用者空間綠色的部分,使用者空間的BPF程式,檔名一般是xxx.bpf.c,由該檔案生成BPF位元組碼
2. 使用者空間的程式程式碼:圖1中使用者空間紫色的部分,表述從核心獲取資料後的處理邏輯。當程式掛載的系統呼叫被觸發,就會把相應的事件資料傳輸到perf_event data中,由使用者空間的程式碼來分析行為。
3. 核心空間的verifier: 驗證器確保了eBPF程式可以安全執行。它驗證程式是否滿足幾個條件,例如:
- 載入eBPF程式的流程擁有所需的功能(特權)
- 程式不會崩潰或以其他方式損害系統
- 程式不能使用任何未初始化的變數或越界訪問記憶體
- 程式必須符合系統的尺寸要求,不可能載入任意大的eBPF程式
- 程式必須具有有限的複雜性。驗證者將評估所有可能的執行路徑,並且必須能夠在配置的複雜度上限範圍內完成分析
- 程式總是執行到結束,即沒有死迴圈
4. 核心空間的JIT Compiler:即時(JIT)編譯步驟將程式的通用位元組碼轉換為特定於機器的指令集,以優化程式的執行速度。這使得eBPF程式可以像本地編譯的核心程式碼或作為核心模組載入的程式碼一樣高效地執行。
5. 核心空間的BPF沙箱程式:這是整個程式的核心部分,BPF程式可以被掛載到多種系統點:kprobe、uprobe、tracepoint和perf_events. 至於你關注的系統呼叫屬於那種系統hook點,可以用 bpftrace -l 命令來查詢。當程式掛載的系統呼叫被觸發,就會把相應的事件資料傳輸到perf_event data中.
6. 核心空間的MAPS:eBPF非常重要的能力是能分享收集到的資訊並存儲狀態。為了達到這個目的,eBPF使用maps來儲存資料。這些資料可以是各種各樣的struct. Maps中的資料可以被使用者空間的程式通過系統呼叫讀取。
然後再來看一個eBPF程式的主要流程:
1. 編寫BPF程式碼,生成位元組碼
2. 載入BPF位元組碼到核心上
3. 非同步讀取maps資料
4. 系統事件觸發,使用者空間接收到事件資訊開始處理分析邏輯
eBPF開發工具鏈
eBPF程式開發常使用的方法分為bcc、bpftrace、eBPF Go Library和libbpf.
- BCC:BCC是一個框架,該框架使得使用者可以寫python程式來和eBPF核心函式互動,這些python程式內嵌了eBPF程式。執行python程式就可以生成eBPF位元組碼並把它們載入到核心。
- bpftrace:bpftrace是一種用於Linux eBPF的高階跟蹤語音,在4.x及以上的核心中可用。bpftrace使用LLVM作為後端來編譯指令碼成eBPF位元組碼並使用BCC來和Linux eBPF子系統進行互動。bpftrace語言的靈感來自awk、C以及DTrace和SystemTap等早期跟蹤程式。
- eBPF Go Library:eBPF Go庫提供了一個通用的eBPF庫,它將獲取eBPF位元組碼的過程與eBPF程式的載入和管理分離開來。eBPF程式通常是通過編寫高階語言建立的,然後使用clang/LLVM編譯器編譯成eBPF位元組碼。
- libbpf Library:libbpf 庫是基於 C/C++ 的通用 eBPF 庫,可以幫助把從clang/LLVM編譯器生成的eBPF物件檔案的載入解耦到核心,它通過為應用程式提供易於使用的API庫來抽象化和BPF系統呼叫的互動操作。例如開源專案tracee就是使用的libbpf進行開發的,它使用的libbpfgo其實是對libbpf用go語言進行的封裝。
以上四種方法只是書寫eBPF程式和生成eBPF位元組碼的方式不同,也就是圖1的第一個步驟不同,其他的都是一致的。