運(yùn)行第一個(gè) eBPF 程序

上手 ebpf
一直想學(xué)習(xí)一下 ebpf 這個(gè)東東,最近買了本《Linux 內(nèi)核觀測(cè)技術(shù) BPF》,準(zhǔn) 備系統(tǒng)的研究一下。
原以為有了書之后學(xué)起來(lái)就相當(dāng)輕松了,可以我發(fā)現(xiàn)書上的第一個(gè)例子就編譯不 過(guò)。
書上只給了部分的源碼,還需要去下載配套的 github 項(xiàng)目,這也沒(méi)啥關(guān)系,不過(guò)下 載后編譯也是編譯不過(guò)。
編譯不過(guò)的報(bào)錯(cuò)也不過(guò)是 types.h 頭文件找不到,解決了頭文件找不到的問(wèn)題 后發(fā)現(xiàn)又有新的問(wèn)題。
網(wǎng)上搜索了下發(fā)現(xiàn)基本上都是在介紹 ebpf xxx,沒(méi)有看到一篇講如何上手 ebpf, 只能自己搞搞嘍,在這里記錄下遇到的問(wèn)題。
安裝必要的程序
ebpf 程序的編譯依賴 llvm 與 clang,需要安裝這兩個(gè)程序。debian like 系 統(tǒng)可以執(zhí)行如下命令進(jìn)行安裝:
sudo?apt-get?install?clang?llvm
如果你要使用 python 作為 BCC 工具的前端來(lái)編寫代碼,你可能會(huì)遇到如下報(bào) 錯(cuò)信息:
ImportError:?No?module?named?bcc
可以執(zhí)行如下命令安裝之:
sudo?apt-get?install?python-bpfcc
克隆項(xiàng)目代碼
linux-observability-with-bpf?項(xiàng)目中能夠找到《Linux內(nèi)核觀測(cè)技術(shù) BPF》一 書中的配套代碼,它的 git 項(xiàng)目地址如下:
https://github.com/bpftools/linux-observability-with-bpf.git
直接使用 git 進(jìn)行克隆即可,克隆完成后進(jìn)入到源碼目錄中,發(fā)現(xiàn)它有如下的 目錄結(jié)構(gòu):
LICENSE README.md Vagrantfile code img
從 README.md 開始
在直接使用?code?目錄前,一定要先閱讀?README.md!一定要先閱讀?README.md!
README.md?文件中描述了使用這個(gè)項(xiàng)目需要預(yù)先執(zhí)行的步驟,具體過(guò)程如下:
安裝必要的工具
debian like 系統(tǒng)中可以執(zhí)行如下命令:
$?sudo?apt?update
$?sudo?apt?install?build-essential?git?make?libelf-dev?clang?strace?tar
$?bpfcc-tools?linux-headers-$(uname?-r)?gcc-multilib?llvm
獲取內(nèi)核源碼
首先執(zhí)行?uname -r?查看內(nèi)核版本信息,然后下載相應(yīng)的內(nèi)核源碼,這里需要注 意的是內(nèi)核版本不能過(guò)低,至少要是 5.0.0 的內(nèi)核。
進(jìn)入到內(nèi)核源碼的?tools/lib/bpf?目錄中,編譯并安裝
配置 libbpf.so
進(jìn)入到項(xiàng)目源碼的?code/chapter-x?中執(zhí)行?make
注意這一步可能需要根據(jù)實(shí)際情況修改?Makefile?中的路徑配置。
第一個(gè)問(wèn)題:升級(jí)內(nèi)核
我使用的是 debian 10 系統(tǒng),它使用的內(nèi)核版本是 4.19,要運(yùn)行一些 ebpf 的例子 至少需要 5.0.0 的內(nèi)核版本,這樣我需要升級(jí)個(gè)內(nèi)核先。
我已經(jīng)預(yù)先克隆了內(nèi)核的 git 倉(cāng)庫(kù),這樣我只需要檢出 v5.0 版本的內(nèi)核代碼 就行了。
執(zhí)行如下命令來(lái)完成:
$ git checkout -b v5.0 v5.0
這里的兩個(gè)?v5.0,第一個(gè)?v5.0?表示的是新創(chuàng)建的分支名,第二個(gè)?v5.0?表示 的是項(xiàng)目的?tag?名稱。
內(nèi)核?config?仍然使用我之前裁剪過(guò)的?config?文件,其下載地址如下:
https://download.csdn.net/download/Longyu_wlz/12900957
直接拷貝為?.config?后,然后執(zhí)行?make oldconfig,然后繼續(xù)執(zhí)行?make -j,發(fā)現(xiàn)需要重新設(shè)定其它的內(nèi)核選項(xiàng),一路 Enter 選擇使用默認(rèn)值。
編譯完成后執(zhí)行如下命令安裝內(nèi)核及內(nèi)核模塊:
$ sudo make modules_install
$ sudo make install
make install?的時(shí)候會(huì)自動(dòng)生成?initrd?并更新引導(dǎo)。
重啟系統(tǒng),在?grub?中選擇使用?5.0?版本的內(nèi)核引導(dǎo)系統(tǒng),進(jìn)入系統(tǒng)后,執(zhí) 行?uname -a?查看內(nèi)核信息,確定使用的是?5.0?版本的內(nèi)核。相關(guān)操作記錄 如下:
$?linux-git?$?uname?-a
Linux?debian-10?5.0.0+?#9?SMP?Sun?Nov?15?22:05:48?CST?2020?x86_64?GNU/Linux
第二個(gè)問(wèn)題:編譯 libbpf.so
libbpf.so?源碼位于內(nèi)核源碼樹的?tools/lib/bpf?目錄中,直接進(jìn)入到這個(gè)目 錄中編譯即可。
編譯過(guò)程記錄如下:
$ make
Auto-detecting system features:
... libelf: [ on ]
... bpf: [ on ]
CC libbpf.o
CC bpf.o
CC nlattr.o
CC btf.o
CC libbpf_errno.o
CC str_error.o
CC netlink.o
CC bpf_prog_linfo.o
LD libbpf-in.o
LINK libbpf.a
LINK libbpf.so
LINK test_libbpf
編譯完成后會(huì)生成?libbpf.so?文件,執(zhí)行?sudo make install?安裝此動(dòng)態(tài)庫(kù)。在我的系統(tǒng)中,它被安裝到了?/usr/local/lib64?目錄中,這個(gè)目錄并不會(huì)被動(dòng) 態(tài)庫(kù)鏈接器搜索,這樣直接運(yùn)行使用了這個(gè)動(dòng)態(tài)庫(kù)的?ebpf?程序就會(huì)有如下報(bào) 錯(cuò):
error while loading shared libraries: libbpf.so: cannot open shared object file: No such file or directory
解決方法如下:
在?/etc/ld.so.conf?中添加?/usr/local/lib64?這一行,運(yùn)行?sudo ldconfig?重新生成動(dòng)態(tài)庫(kù)配置信息 成功執(zhí)行的示例信息如下:
$ ld.so.conf.d $ sudo ldconfig -v 2>/dev/null | grep libbpf
libbpf.so -> libbpf.so
編譯運(yùn)行 hello world ebpf 程序
完成上面的配置過(guò)程后就可以編譯?hello world ebpf?程序了,它位于項(xiàng)目源碼 的?code/chapter-2/hello_world?中,其源碼如下:
#include?
#define?SEC(NAME)?__attribute__((section(NAME),?used))
static?int?(*bpf_trace_printk)(const?char?*fmt,?int?fmt_size,
???????????????????????????????...)?=?(void?*)BPF_FUNC_trace_printk;
SEC("tracepoint/syscalls/sys_enter_execve")
int?bpf_prog(void?*ctx)?{
??char?msg[]?=?"Hello,?BPF?World!";
??bpf_trace_printk(msg,?sizeof(msg));
??return?0;
}
char?_license[]?SEC("license")?=?"GPL";
這個(gè)程序,聲明監(jiān)控調(diào)用?execve?的事件,每監(jiān)控到一個(gè)事件就調(diào)用?bpf_trace_printk?來(lái)打印 Hello, BPF World! 字符串,其細(xì) 節(jié)我就不進(jìn)一步描述了,感興趣的讀者可以閱讀《Linux內(nèi)核觀測(cè)技術(shù)BPF》的第 二章。
在編譯之前還需要修改下 Makefile 中的內(nèi)核源碼路徑,它默認(rèn)是在?/kernel-src?目錄下的,需要根據(jù)實(shí)際情況修改為真實(shí)的路徑。
我執(zhí)行如下 sed 命令將 Makefile 中的內(nèi)核源碼路徑修改為我系統(tǒng)中的真實(shí)路 徑:
$ sed -i 's;/kernel-src;/home/longyu/linux-git;' ./Makefile
修改完 Makefile 后直接執(zhí)行 make 編譯即可,相關(guān)過(guò)程記錄如下:
$ make
clang -O2 -target bpf -c bpf_program.c -I/home/longyu/linux-git/tools/testing/selftests/bpf -o bpf_program.o
clang -o monitor-exec -lelf -I/home/longyu/linux-git/samples/bpf -I/home/longyu/linux-git/tools/lib -I/home/longyu/linux-git/tools/perf -I/home/longyu/linux-git/tools/include -L/usr/local/lib64 -lbpf \
/home/longyu/linux-git/samples/bpf/bpf_load.c loader.c
編譯完成后會(huì)生成一個(gè)?monitor-exec?程序,需要以?root?權(quán)限來(lái)運(yùn)行。使用普 通用戶執(zhí)行將會(huì)報(bào)如下錯(cuò)誤信息:
$ ./monitor-exec
bpf_load_program() err=1
The kernel didn't load the BPF program
使用 root 用戶執(zhí)行后過(guò)一會(huì)就會(huì)打印 Hello,BPF World!,示例信息如下;
$ sudo ./monitor-exec
sogou-qimpanel-31885 [004] .... 11022.245597: 0: Hello, BPF World!
sh-31886 [005] .... 11022.247254: 0: Hello, BPF World!
sogou-qimpanel-31887 [005] .... 11022.249711: 0: Hello, BPF World!
sh-31889 [004] .... 11022.251231: 0: Hello, BPF World!
sh-31891 [007] .... 11022.251429: 0: Hello, BPF World!
這個(gè)程序使用了內(nèi)核的?tracepoint?來(lái)監(jiān)控執(zhí)行?execve?的事件,當(dāng)此事件發(fā)生 后,它會(huì)打印出?Hello,xxx?的信息,這個(gè)?execve?事件代表了一個(gè)新進(jìn)程的執(zhí) 行,我們?cè)谏厦娴氖纠锌吹降牡谝涣芯褪浅绦蛎c其?pid?號(hào)。
這個(gè) hello world 程序的編譯過(guò)程分為兩個(gè)步驟:
使用 clang 編譯生成 bpf 機(jī)器碼
使用?clang?編譯?loader.c?生成加載第一步生成的機(jī)器碼的程序?loader.c?函數(shù)的源碼如下所示:
#include?"bpf_load.h"
#include?
int?main(int?argc,?char?**argv)?{
??if?(load_bpf_file("bpf_program.o")?!=?0)?{
????printf("The?kernel?didn't?load?the?BPF?program\n");
????return?-1;
??}
??read_trace_pipe();
??return?0;
}
上述代碼其實(shí)只調(diào)用了?load_bpf_file?來(lái)加載第一步編譯生成的?ebpf?程序,?load_bpf_file?是?libbpf.so?中提供的接口。
strace 跟蹤 hello world ebpf 程序
使用 strace 跟蹤 hello world ebpf 程序能夠看到如下關(guān)鍵的系統(tǒng)調(diào)用:
bpf(BPF_PROG_LOAD,?{prog_type=BPF_PROG_TYPE_TRACEPOINT,?insn_cnt=14,?insns=0xd04c80,?license="GPL",?log_level=0,?log_size=0,?log_buf=NULL,?kern_version=KERNEL_VERSION(0,?0,?0),?prog_flags=0,?prog_name="",?prog_ifindex=0,?expected_attach_type=BPF_CGROUP_INET_INGRESS},?112)?=?4
其實(shí)?bpf?系統(tǒng)調(diào)用就是用戶態(tài)程序與內(nèi)核中的?ebpf?虛擬機(jī)交互的接口,libbpf.so?中提供的 api 實(shí)際上是對(duì) bpf 系統(tǒng)調(diào)用的封裝,可以想到?load_bpf_file?其實(shí)就是 指定?BPF_PROG_LOAD?等參數(shù)調(diào)用 bpf 系統(tǒng)調(diào)用來(lái)是實(shí)現(xiàn)的。
吐槽吐槽《Linux內(nèi)核觀測(cè)技術(shù)BPF》
一開始我并沒(méi)有下載該書的配套源碼,直接按照書中的描述編譯 hello world 程序,命令行信息如下:
$?clang?-O2?-target?bpf?-c?hello.c?-o?bpf_program.o?
結(jié)果就報(bào)了?asm/types.h cannot find?的錯(cuò)誤,整了一下發(fā)現(xiàn)它使用的是我系 統(tǒng)中?/usr/include/?目錄中的內(nèi)核頭文件,看來(lái)應(yīng)該就是個(gè)版本問(wèn)題。
我想應(yīng)該可以通過(guò)安裝內(nèi)核頭文件來(lái)解決這個(gè)問(wèn)題。
首先在內(nèi)核源碼樹根目錄中執(zhí)行?make help?中找到如下內(nèi)容:
headers_install - Install sanitised kernel headers to INSTALL_HDR_PATH
(default: ./usr)
可以看到它默認(rèn)是使用 ./usr 目錄,可以通過(guò)設(shè)定 INSTALL_HDR_PATH 來(lái)指定 其它安裝目錄。
我執(zhí)行如下命令將這些頭文件安裝到指定目錄中,示例過(guò)程如下:
$ make INSTALL_HDR_PATH="/home/longyu/ebpf/" headers_install
INSTALL include/asm-generic (36 files)
INSTALL include/drm (26 files)
......
安裝后?ls?查看,確定?asm/types.h?存在,相關(guān)信息如下:
$?ls?/home/longyu/ebpf/include/asm/types.h?
/home/longyu/ebpf/include/asm/types.h
執(zhí)行?clang -O2 -target bpf -I /home/longyu/ebpf/include -c bpf_program.c -o bpf_program.o?后成功編譯。
我覺(jué)得這個(gè)問(wèn)題不應(yīng)該遇到,但是確實(shí)遇到了,書里面也沒(méi)有相關(guān)的描述信息, 不得不吐槽這本書的內(nèi)容,這些最為基本的東西為啥不能寫的清楚一些?也不差 那幾頁(yè)么?
第二、三章內(nèi)容看描述還行,但是一嘗試編譯就會(huì)遇到問(wèn)題,還是有點(diǎn)垃圾歐。
其它的 ebpf 組件
內(nèi)核源碼樹的?tools/bpf?這個(gè)目錄中存放了了?ebpf?的匯 編,反匯編,調(diào)試程序源碼。
在編譯的過(guò)程中我遇到了如下幾個(gè)問(wèn)題:
找不到 bfd.h
報(bào)錯(cuò)信息如下:
/home/longyu/linux-git/tools/bpf/bpf_jit_disasm.c:23:10: fatal error: bfd.h: 沒(méi)有那個(gè)文件或目錄
#include
^~~~~~~
解決方案如下:
$ apt-get install binutils-dev
找不到 radline/readline.h
報(bào)錯(cuò)信息如下:
/home/longyu/linux-git/tools/bpf/bpf_dbg.c:43:10: fatal error: readline/readline.h: 沒(méi)有那個(gè)文件或目錄
#include
解決方案如下:
$ sudo apt-get install libreadline-dev
安裝了這幾個(gè)開發(fā)包后能夠成功編譯,編譯生成的文件信息如下:
bpf_asm bpf_dbg bpf_jit_disasm bpftool/bpftool
這幾個(gè)程序目前還沒(méi)有用起來(lái),后面用起來(lái)了在描述吧。
內(nèi)核源碼樹中?tools/testing/selftests/bpf?目錄中中放了?ebpf?功能的相關(guān)測(cè)試 用例程序,在編譯時(shí)遇到了如下問(wèn)題:
test_verifier.c:28:10: fatal error: sys/capability.h: 沒(méi)有那個(gè)文件或目錄
#include
可以通過(guò)執(zhí)行如下命令解決:
$ sudo apt-get install libcap-dev
這個(gè)目錄中的測(cè)試用例程序可以作為編寫 ebpf 程序的參考,這要比文檔資料更 有價(jià)值。
samples/bpf?目錄中也有一些 ebpf demo 程序,也是很好的參考資料。
內(nèi)核源碼樹中與 ebpf 相關(guān)的一手資料
內(nèi)核源碼樹中,除了上面說(shuō)過(guò)的源碼資料外,還有幾個(gè)目錄中有一些文檔資料。
tools/bpf/bpftool/Documentation?中有?bpftool?命令的資料?Documentation/bpf?中有?bpf?的一些描述資料
總結(jié)
紙上得來(lái)終覺(jué)淺,絕知此事要躬行。書上的知識(shí)一定要經(jīng)過(guò)實(shí)踐的檢驗(yàn)才能確定 它是否正確,學(xué)習(xí)從來(lái)都不只是閱讀與記憶這么簡(jiǎn)單!
原文:
https://blog.csdn.net/Longyu_wlz/article/details/109900096
