筆者在很早之前就看eBPF這類似的文章,那時候看這個技術一臉懵逼,不知道它是用來做什么,可以解決什么問題。所以也沒有太關注這個技術。很慶幸最近剛好有機會研究這個技術。
什么是BPF
BPF的全稱是Berkaley Packet Filter,即伯克利報文過法器,它的設計思想來源于 1992 年Steven McCanne和Van Jacobson寫的一篇論文“The BSD packet filter. A New architecture for user-level packet apture' (《BSD數據包過濾器:一種用于用戶級數據包捕獲的新休系結構》)。最初,BPF是在 BSD 內核實現的,后來,由于其出色的設計思想,其他操作系統(tǒng)也將其引入包括 Linux。
在這篇論文中,作者描述了他們如何在Unix內核實現網絡數據包過濾,這種新的技術比當時最先進的數據包過濾技術快20倍。如下圖來源于論文:
通俗易懂的理解上圖,BPF是作為網絡報文傳輸的旁路鏈路,當接收到的網絡報文到達內閣驅動程序后,網絡報文在傳輸給網絡協(xié)議棧的同時,會額外將網絡報文的副本傳輸給BPF。之后網絡報文會經過BPF程序的內部邏輯進行過濾,最終再送到用戶程序。
BPF 在數據包過濾上引入了兩大革新:
一個新的虛擬機(VM)設計,可以有效地工作在基于寄存器結構的CPU之上;
應用程序使用緩存只復制與過濾數據包相關的數據,不會復制數據包的所有信息,最大程度地減少BPF處理的數據,提高處理效率。
我們熟悉的tcpdump就是基于BPF技術,好比一個神器站另外一個神器的絕作。
什么是eBPF
BPF發(fā)展到現在名稱升級為eBPF:「extended Berkeley Packet Filter」。它演進成為了一套通用執(zhí)行引擎,提供可基于系統(tǒng)或程序事件高效安全執(zhí)行特定代碼的通用能力,通用能力的使用者不再局限于內核開發(fā)者。其使用場景不再僅僅是網絡分析,可以基于eBPF開發(fā)性能分析、系統(tǒng)追蹤、網絡優(yōu)化等多種類型的工具和平臺。
eBPF原理
** eBPF技術架構圖:**
eBPF主要分為用戶空間程序與內核程序兩大部分:
在用戶空間,程序通過LLVM/Clang被編譯成eBPF可接受的字節(jié)碼并提交到內核,以及負責讀取內核回傳的消息事件或統(tǒng)計信息。eBPF提供了兩種內核態(tài)與用戶態(tài)傳遞數據的方式,內核態(tài)可以將自定義perf_event消息事件發(fā)往用戶態(tài),或用戶態(tài)通過文件描述符讀寫存儲在內核中的k/v Map數據。
在內核空間,為了穩(wěn)定與安全,eBPF接收的字節(jié)碼首先會交給Verifier進行安全驗證,如驗證程序循環(huán)次數,數組越界問題,無法訪問的指令等等。只有校驗通過的字節(jié)碼才會提交到內核自帶編譯器或JIT編譯器編譯成可直接執(zhí)行的機器指令。同時,eBPF對提交程序提出限制,如程序大小限制,最大可使用堆棧大小限制,可調用函數限制,循環(huán)次數限制等。
從上面的架構圖可以看出,eBPF在內核態(tài)會依賴內核探針進行工作,其中kprobes實現內核函數動態(tài)跟蹤;uprobes實現用戶函數動態(tài)跟蹤;tracepoints是內核中的靜態(tài)跟蹤點;perf_events支持定時采樣和PMC。
eBPF環(huán)境搭建
為了有一個eBPF程序編寫驗證的平臺,我在ubuntu22.04中搭建了eBPF環(huán)境,ubuntu22.04安裝流程在這里不在過多的介紹。以下的操作都在root用戶下執(zhí)行
更新系統(tǒng)的包索引和包列表:
#?apt?update
編譯 BPF 程序需要系統(tǒng)安裝必備的 linux-headers 包:
#?sudo?apt?install?linux-headers-$(uname?-r)
安裝eBPF依賴工具:
#?apt?install?-y?bison?flex?build-essential?git?cmake?make?libelf-dev?strace?tar?libfl-dev?libssl-dev?libedit-dev?zlib1g-dev??python??python3-distutils
安裝LLVM,并檢查一下版本:
#?apt?install?llvm
#?llc?-version Ubuntu?LLVM?version?14.0.0 ?? ..... ????wasm32?????-?WebAssembly?32-bit ????wasm64?????-?WebAssembly?64-bit ????x86????????-?32-bit?X86:?Pentium-Pro?and?above ????x86-64?????-?64-bit?X86:?EM64T?and?AMD64 ????xcore??????-?XCore #?
安裝Clang,并檢查一下版本:
#?apt?install?clang #?clang?-version Ubuntu?clang?version?14.0.0-1ubuntu1 Target:?x86_64-pc-linux-gnu Thread?model:?posix InstalledDir:?/usr/bin #?
查看但錢ubuntu的內核版本,安裝對應的內核源碼,并解壓源碼:
#?apt-cache?search?linux-source linux-source?-?Linux?kernel?source?with?Ubuntu?patches linux-source-5.19.0?-?Linux?kernel?source?for?version?5.19.0?with?Ubuntu?patches #?apt?install?linux-source-5.19.0 #?cd?/usr/src #?tar?-jxvf?linux-source-5.19.0.tar.bz2 #?cd?linux-source-5.19.0
編譯內核源碼的bpf模塊,如果沒有報錯,說明已經完成環(huán)境搭建:
#?cp?-v?/boot/config-$(uname?-r)?.config #?make?oldconfig?&&?make?prepare #?make?headers_install #?apt-get?install?libcap-dev? #?make?M=samples/bpf ??CC??samples/bpf/../../tools/testing/selftests/bpf/cgroup_helpers.o ??CC??samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o ??CC??samples/bpf/cookie_uid_helper_example.o ??CC??samples/bpf/cpustat_user.o ??CC??samples/bpf/fds_example.o .... WARNING:?Symbol?version?dump?"Module.symvers"?is?missing. ?????????Modules?may?not?have?dependencies?or?modversions. ?????????You?may?get?many?unresolved?symbol?warnings.
eBPF樣例編寫
在內核源碼的samples/bpf目錄下提供了很多實例供我們學習,通過目錄下的makefile就可以構建里面的bpf程序,如果我們用 C 語言編寫的 BPF 程序編譯可以直接在該目錄提供的環(huán)境中進行編譯。
samples/bpf 下的程序一般組成方式是 xxx_user.c 和 xxx_kern.c:
xxx_user.c:為用戶空間的程序用于設置 BPF 程序的相關配置、加載 BPF 程序至內核、設置 BPF 程序中的 map 值和讀取 BPF 程序運行過程中發(fā)送至用戶空間的消息等。目前 xxx_user.c 與 xxx_kern.c 程序在交互實現都是基于 bpf() 系統(tǒng)調用完成的。直接使用 bpf() 系統(tǒng)調用涉及的參數和細節(jié)比較多,使用門檻較高,因此為了方便用戶空間程序更加易用,內核提供了 libbpf 庫封裝了對于 bpf() 系統(tǒng)調用的細節(jié)。
xxx_kern.c:為 BPF 程序代碼,通過 clang 編譯成字節(jié)碼加載至內核中,在對應事件觸發(fā)的時候運行,可以接受用戶空間程序發(fā)送的各種數據,并將運行時產生的數據發(fā)送至用戶空間程序。
編寫一個樣例流程,在目錄samples/bpf中新建兩個文件:youyeetoo_user.c和youyeetoo_kern.c,并且在makefile中加入構建:
youyeetoo_user.c的內容:
#include?#include? #include? #include?"trace_helpers.h" int?main(int?ac,?char?**argv) { ?struct?bpf_link?*link?=?NULL; ?struct?bpf_program?*prog; ?struct?bpf_object?*obj; ?char?filename[256]; ?snprintf(filename,?sizeof(filename),?"%s_kern.o",?argv[0]); ?obj?=?bpf_object__open_file(filename,?NULL); ?if?(libbpf_get_error(obj))?{ ??fprintf(stderr,?"ERROR:?opening?BPF?object?file?failed "); ??return?0; ?} ?prog?=?bpf_object__find_program_by_name(obj,?"bpf_prog"); ?if?(!prog)?{ ??fprintf(stderr,?"ERROR:?finding?a?prog?in?obj?file?failed "); ??goto?cleanup; ?} ?/*?load?BPF?program?*/ ?if?(bpf_object__load(obj))?{ ??fprintf(stderr,?"ERROR:?loading?BPF?object?file?failed "); ??goto?cleanup; ?} ?link?=?bpf_program__attach(prog); ?if?(libbpf_get_error(link))?{ ??fprintf(stderr,?"ERROR:?bpf_program__attach?failed "); ??link?=?NULL; ??goto?cleanup; ?} ?read_trace_pipe(); cleanup: ?bpf_link__destroy(link); ?bpf_object__close(obj); ?return?0; }
youyeetoo_kern.c的內容:
#include?#include? #include? #include? SEC("tracepoint/syscalls/sys_enter_execve") int?bpf_prog1(struct?pt_regs?*ctx) { ??char?fmt[]?=?"youyeetoo?%s?! "; ?char?comm[16]; ?bpf_get_current_comm(&comm,?sizeof(comm));?? ??bpf_trace_printk(fmt,?sizeof(fmt),?comm); ?? ?return?0; } char?_license[]?SEC("license")?=?"GPL"; u32?_version?SEC("version")?=?LINUX_VERSION_CODE;
Makefile 文件修改:
#?diff?-u?Makefile.old?Makefile ---?Makefile.old?2021-09-26?03:16:16.883348130?+0000 +++?Makefile?2021-09-26?03:20:46.732277872?+0000 @@?-55,6?+55,7?@@ ?tprogs-y?+=?xdp_sample_pkts ?tprogs-y?+=?ibumad ?tprogs-y?+=?hbm +tprogs-y?+=?youyeetoo ?#?Libbpf?dependencies ?LIBBPF?=?$(TOOLS_PATH)/lib/bpf/libbpf.a @@?-113,6?+114,7?@@ ?xdp_sample_pkts-objs?:=?xdp_sample_pkts_user.o ?ibumad-objs?:=?ibumad_user.o ?hbm-objs?:=?hbm.o?$(CGROUP_HELPERS) +youyeetoo-objs?:=?youyeetoo_user.o?$(TRACE_HELPERS) ?#?Tell?kbuild?to?always?build?the?programs ?always-y?:=?$(tprogs-y) @@?-174,6?+176,7?@@ ?always-y?+=?hbm_out_kern.o ?always-y?+=?hbm_edt_kern.o ?always-y?+=?xdpsock_kern.o +always-y?+=?youyeetoo_kern.o ?ifeq?($(ARCH),?arm) ?#?Strip?all?except?-D__LINUX_ARM_ARCH__?option?needed?to?handle?linux
eBPF樣例驗證
編譯樣例:
#?make?M=samples/bpf ??CC??samples/bpf/../../tools/testing/selftests/bpf/cgroup_helpers.o ??CC??samples/bpf/../../tools/testing/selftests/bpf/trace_helpers.o ??CC??samples/bpf/cookie_uid_helper_example.o ??CC??samples/bpf/cpustat_user.o ??CC??samples/bpf/fds_example.o .... ??LD??samples/bpf/youyeetoo ??CLANG-bpf??samples/bpf/youyeetoo_kern.o WARNING:?Symbol?version?dump?"Module.symvers"?is?missing. ?????????Modules?may?not?have?dependencies?or?modversions. ?????????You?may?get?many?unresolved?symbol?warnings.
在samples/bpf下查看編譯結果,可以看到y(tǒng)ouyeetoo可執(zhí)行文件:
#?ls?-al?youyeetoo* -rwxr-xr-x?1?root?root?407976??6月??9?19:08?youyeetoo -rw-r--r--?1?root?root????451??6月??9?10:44?youyeetoo_kern.c -rw-r--r--?1?root?root???5216??6月??9?19:08?youyeetoo_kern.o -rw-r--r--?1?root?root????997??6月??9?10:40?youyeetoo_user.c -rw-r--r--?1?root?root???3360??6月??9?19:08?youyeetoo_user.o
在ubuntu中運行兩個終端,用來測試youyeetoo:
在終端以運行youyeetoo可執(zhí)行文件,在終端2中執(zhí)行任意命令,在終端1查看程序是否能夠監(jiān)測到,如果成功監(jiān)測到新進程運行便會輸出一條“bpf_trace_printk: Hello”
編輯:黃飛
評論
查看更多