筆者在很早之前就看eBPF這類似的文章,那時(shí)候看這個(gè)技術(shù)一臉懵逼,不知道它是用來做什么,可以解決什么問題。所以也沒有太關(guān)注這個(gè)技術(shù)。很慶幸最近剛好有機(jī)會研究這個(gè)技術(shù)。
什么是BPF
BPF的全稱是Berkaley Packet Filter,即伯克利報(bào)文過法器,它的設(shè)計(jì)思想來源于 1992 年Steven McCanne和Van Jacobson寫的一篇論文“The BSD packet filter. A New architecture for user-level packet apture' (《BSD數(shù)據(jù)包過濾器:一種用于用戶級數(shù)據(jù)包捕獲的新休系結(jié)構(gòu)》)。最初,BPF是在 BSD 內(nèi)核實(shí)現(xiàn)的,后來,由于其出色的設(shè)計(jì)思想,其他操作系統(tǒng)也將其引入包括 Linux。
在這篇論文中,作者描述了他們?nèi)绾卧赨nix內(nèi)核實(shí)現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)包過濾,這種新的技術(shù)比當(dāng)時(shí)最先進(jìn)的數(shù)據(jù)包過濾技術(shù)快20倍。如下圖來源于論文:
通俗易懂的理解上圖,BPF是作為網(wǎng)絡(luò)報(bào)文傳輸?shù)呐月锋溌?,?dāng)接收到的網(wǎng)絡(luò)報(bào)文到達(dá)內(nèi)閣驅(qū)動程序后,網(wǎng)絡(luò)報(bào)文在傳輸給網(wǎng)絡(luò)協(xié)議棧的同時(shí),會額外將網(wǎng)絡(luò)報(bào)文的副本傳輸給BPF。之后網(wǎng)絡(luò)報(bào)文會經(jīng)過BPF程序的內(nèi)部邏輯進(jìn)行過濾,最終再送到用戶程序。
BPF 在數(shù)據(jù)包過濾上引入了兩大革新:
一個(gè)新的虛擬機(jī)(VM)設(shè)計(jì),可以有效地工作在基于寄存器結(jié)構(gòu)的CPU之上;
應(yīng)用程序使用緩存只復(fù)制與過濾數(shù)據(jù)包相關(guān)的數(shù)據(jù),不會復(fù)制數(shù)據(jù)包的所有信息,最大程度地減少BPF處理的數(shù)據(jù),提高處理效率。
我們熟悉的tcpdump就是基于BPF技術(shù),好比一個(gè)神器站另外一個(gè)神器的絕作。
什么是eBPF
BPF發(fā)展到現(xiàn)在名稱升級為eBPF:「extended Berkeley Packet Filter」。它演進(jìn)成為了一套通用執(zhí)行引擎,提供可基于系統(tǒng)或程序事件高效安全執(zhí)行特定代碼的通用能力,通用能力的使用者不再局限于內(nèi)核開發(fā)者。其使用場景不再僅僅是網(wǎng)絡(luò)分析,可以基于eBPF開發(fā)性能分析、系統(tǒng)追蹤、網(wǎng)絡(luò)優(yōu)化等多種類型的工具和平臺。
eBPF原理
** eBPF技術(shù)架構(gòu)圖:**
eBPF主要分為用戶空間程序與內(nèi)核程序兩大部分:
在用戶空間,程序通過LLVM/Clang被編譯成eBPF可接受的字節(jié)碼并提交到內(nèi)核,以及負(fù)責(zé)讀取內(nèi)核回傳的消息事件或統(tǒng)計(jì)信息。eBPF提供了兩種內(nèi)核態(tài)與用戶態(tài)傳遞數(shù)據(jù)的方式,內(nèi)核態(tài)可以將自定義perf_event消息事件發(fā)往用戶態(tài),或用戶態(tài)通過文件描述符讀寫存儲在內(nèi)核中的k/v Map數(shù)據(jù)。
在內(nèi)核空間,為了穩(wěn)定與安全,eBPF接收的字節(jié)碼首先會交給Verifier進(jìn)行安全驗(yàn)證,如驗(yàn)證程序循環(huán)次數(shù),數(shù)組越界問題,無法訪問的指令等等。只有校驗(yàn)通過的字節(jié)碼才會提交到內(nèi)核自帶編譯器或JIT編譯器編譯成可直接執(zhí)行的機(jī)器指令。同時(shí),eBPF對提交程序提出限制,如程序大小限制,最大可使用堆棧大小限制,可調(diào)用函數(shù)限制,循環(huán)次數(shù)限制等。
從上面的架構(gòu)圖可以看出,eBPF在內(nèi)核態(tài)會依賴內(nèi)核探針進(jìn)行工作,其中kprobes實(shí)現(xiàn)內(nèi)核函數(shù)動態(tài)跟蹤;uprobes實(shí)現(xiàn)用戶函數(shù)動態(tài)跟蹤;tracepoints是內(nèi)核中的靜態(tài)跟蹤點(diǎn);perf_events支持定時(shí)采樣和PMC。
eBPF環(huán)境搭建
為了有一個(gè)eBPF程序編寫驗(yàn)證的平臺,我在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的內(nèi)核版本,安裝對應(yīng)的內(nèi)核源碼,并解壓源碼:
#?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
編譯內(nèi)核源碼的bpf模塊,如果沒有報(bào)錯(cuò),說明已經(jīng)完成環(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樣例編寫
在內(nèi)核源碼的samples/bpf目錄下提供了很多實(shí)例供我們學(xué)習(xí),通過目錄下的makefile就可以構(gòu)建里面的bpf程序,如果我們用 C 語言編寫的 BPF 程序編譯可以直接在該目錄提供的環(huán)境中進(jìn)行編譯。
samples/bpf 下的程序一般組成方式是 xxx_user.c 和 xxx_kern.c:
xxx_user.c:為用戶空間的程序用于設(shè)置 BPF 程序的相關(guān)配置、加載 BPF 程序至內(nèi)核、設(shè)置 BPF 程序中的 map 值和讀取 BPF 程序運(yùn)行過程中發(fā)送至用戶空間的消息等。目前 xxx_user.c 與 xxx_kern.c 程序在交互實(shí)現(xiàn)都是基于 bpf() 系統(tǒng)調(diào)用完成的。直接使用 bpf() 系統(tǒng)調(diào)用涉及的參數(shù)和細(xì)節(jié)比較多,使用門檻較高,因此為了方便用戶空間程序更加易用,內(nèi)核提供了 libbpf 庫封裝了對于 bpf() 系統(tǒng)調(diào)用的細(xì)節(jié)。
xxx_kern.c:為 BPF 程序代碼,通過 clang 編譯成字節(jié)碼加載至內(nèi)核中,在對應(yīng)事件觸發(fā)的時(shí)候運(yùn)行,可以接受用戶空間程序發(fā)送的各種數(shù)據(jù),并將運(yùn)行時(shí)產(chǎn)生的數(shù)據(jù)發(fā)送至用戶空間程序。
編寫一個(gè)樣例流程,在目錄samples/bpf中新建兩個(gè)文件:youyeetoo_user.c和youyeetoo_kern.c,并且在makefile中加入構(gòu)建:
youyeetoo_user.c的內(nèi)容:
#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的內(nèi)容:
#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樣例驗(yàn)證
編譯樣例:
#?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下查看編譯結(jié)果,可以看到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中運(yùn)行兩個(gè)終端,用來測試youyeetoo:
在終端以運(yùn)行youyeetoo可執(zhí)行文件,在終端2中執(zhí)行任意命令,在終端1查看程序是否能夠監(jiān)測到,如果成功監(jiān)測到新進(jìn)程運(yùn)行便會輸出一條“bpf_trace_printk: Hello”
編輯:黃飛
評論
查看更多