吴忠躺衫网络科技有限公司

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

eBPF動(dòng)手實(shí)踐系列三:基于原生libbpf庫(kù)的eBPF編程改進(jìn)方案簡(jiǎn)析

Linux閱碼場(chǎng) ? 來(lái)源:阿里云大數(shù)據(jù)AI平臺(tái) ? 2024-03-19 14:19 ? 次閱讀

01

在上一篇文章《eBPF動(dòng)手實(shí)踐系列二:構(gòu)建基于純C語(yǔ)言的eBPF項(xiàng)目》中,我們初步實(shí)現(xiàn)了脫離內(nèi)核源碼進(jìn)行純C語(yǔ)言eBPF項(xiàng)目的構(gòu)建。libbpf庫(kù)在早期和內(nèi)核源碼結(jié)合的比較緊密,如今的libbpf庫(kù)更加成熟,已經(jīng)完全脫離內(nèi)核源碼獨(dú)立發(fā)展。

為了更加具體的理解linux內(nèi)核版本演進(jìn)和libbpf版本演進(jìn)的關(guān)系,本文在“附錄A”中總結(jié)了各個(gè)內(nèi)核版本源碼示例中所依賴(lài)的libbpf庫(kù)的對(duì)應(yīng)版本信息

大部分版本的內(nèi)核獲取libbpf版本的方法如下,從libbpf庫(kù)目錄的libbpf.map文件中提取最大的版本號(hào)信息。這里的"source"為內(nèi)核源碼所在目錄。

$ cat ./source/tools/lib/bpf/libbpf.map | grep -oE '^LIBBPF_([0-9.]+)' | sort -rV | head -n1 | cut -d'_' -f2

較早版本的內(nèi)核在./tools/lib/bpf/Makefile文件中直接定義了libbpf的版本信息。

$ cat ./source/tools/lib/bpf/Makefile
BPF_VERSION = 0
BPF_PATCHLEVEL = 0
BPF_EXTRAVERSION = 2

02

eBPF編程方案簡(jiǎn)介

為了簡(jiǎn)化 eBPF程序的開(kāi)發(fā)流程,降低開(kāi)發(fā)者在使用 libbpf 庫(kù)時(shí)的入門(mén)難度,libbpf-bootstrap 框架應(yīng)運(yùn)而生。基于libbpf-bootstrap框架的編程方案是目前網(wǎng)絡(luò)上看到的最主流編程方案。此外,網(wǎng)絡(luò)上也偶見(jiàn)比較古老的僅依賴(lài)一個(gè)bpf_load.c文件的C語(yǔ)言編程方案,這個(gè)方案并不需要依賴(lài)libbpf庫(kù)的支持。

主流的C語(yǔ)言實(shí)現(xiàn)的eBPF編程方案,大體上就是以下三種,筆者總共將其歸納為3代。

f81d4f66-e59f-11ee-a297-92fbcf53809c.png

除了經(jīng)典的C語(yǔ)言編程方案,一些編程框架還選擇使用Python語(yǔ)言,Go語(yǔ)言,或者Rust語(yǔ)言作為用戶(hù)態(tài)加載的實(shí)現(xiàn)語(yǔ)言。

盡管libbpf-bootstrap骨架C語(yǔ)言方案、基于libbpfgo庫(kù)的go語(yǔ)言方案等已經(jīng)被大家廣泛使用和接受。但筆者認(rèn)為基于原生libbpf庫(kù)的eBPF編程方案仍然具備很多獨(dú)特的優(yōu)勢(shì)。以下是原生libbpf庫(kù)eBPF編程方案的一些獨(dú)特優(yōu)勢(shì):

更深的控制和靈活性:直接使用原生libbpf 庫(kù)的方案意味著可以與更底層交互,實(shí)現(xiàn)更多的控制,定制加載和管理 eBPF 程序和 maps 過(guò)程,滿(mǎn)足更復(fù)雜的需求。

更好的學(xué)習(xí)和理解:libbpf-bootstrap封裝抽象屏蔽了很多細(xì)節(jié),直接使用原生libbpf可以對(duì) eBPF 子系統(tǒng)有更深入的理解,有利于開(kāi)發(fā)者對(duì) eBPF 內(nèi)部工作原理的理解。

更細(xì)粒度的依賴(lài)管理:直接使用原生libbpf庫(kù)能夠指定依賴(lài)的 libbpf 庫(kù)版本和功能,進(jìn)而更精細(xì)化地管理項(xiàng)目依賴(lài)關(guān)系。

更好的低版本內(nèi)核適應(yīng)性:基于原生libbpf庫(kù)的方案,在低版本操作系統(tǒng)發(fā)行版和低版本內(nèi)核上可以有更好的兼容性。

本文將由淺入深介紹第 2 代原生libbpf庫(kù)的eBPF編程方案,并提出一種改進(jìn)思路。

03

準(zhǔn)備eBPF開(kāi)發(fā)的基礎(chǔ)環(huán)境

主流的linux發(fā)行版大多是基于rpm包或deb包的包管理系統(tǒng)。不同的包管理系統(tǒng),初始化eBPF開(kāi)發(fā)環(huán)境時(shí)所依賴(lài)的包,也略有差別。本文將分別進(jìn)行介紹。

3.1 rpm包基礎(chǔ)環(huán)境初始化

在RPM包發(fā)行版環(huán)境,需要安裝一些編譯過(guò)程的基礎(chǔ)包、編譯工具包、庫(kù)依賴(lài)包和頭文件依賴(lài)包等。我們推薦使用如下一些發(fā)行版及其兼容環(huán)境:Anolis 8.8、Kylin V10、CentOS 8.5、和 Fedora 39 等。

詳細(xì)安裝步驟如下:

$  yum install git make                               # 基礎(chǔ)包
$  yum install kernel-headers-$(uname -r)             # 頭文件依賴(lài)包
$  yum install clang llvm elfutils-libelf-devel       # 編譯工具和依賴(lài)庫(kù)包


## 依次選擇如下命令之一,安裝bpftool工具
$  yum install bpftool-$(uname -r)
$  yum install bpftool

3.2 deb包基礎(chǔ)環(huán)境初始化

在 DEB 包發(fā)行版環(huán)境,需要安裝一些編譯過(guò)程的基礎(chǔ)包、編譯工具包、庫(kù)依賴(lài)包和頭文件依賴(lài)包等。推薦使用Ubuntu 22.04 或Debian 12 等發(fā)行版及其兼容環(huán)境。

詳細(xì)安裝步驟如下:

$  apt-get update                                     # 更新apt源信息
$  apt install git make                               # 基礎(chǔ)包 
$  apt install linux-libc-dev                         # 頭文件依賴(lài)包
$  apt install clang llvm libelf-dev                  # 編譯工具和依賴(lài)庫(kù)包


## 依次選擇如下命令之一,安裝bpftool工具
$  apt install linux-tools-common linux-tools-$(uname -r)
$  apt install linux-tools-common linux-tools-generic
$  apt install linux-tools-$(uname -r) linux-cloud-tools-$(uname -r)
$  apt install bpftool

04

構(gòu)建基于原生libbpf庫(kù)的eBPF項(xiàng)目

本文的目的是向大家分享一個(gè)以第2代 ebpf 編程方案為基礎(chǔ)的改進(jìn)ebpf編譯構(gòu)建方案。本節(jié)先用一些篇幅內(nèi)容,對(duì)第2代方案本身的構(gòu)建編譯過(guò)程做一些介紹。

libbpf庫(kù)具有一定的向下兼容能力,可以選擇使用截至目前最新的歸檔版本libbpf-1.3.0來(lái)搭建編程環(huán)境。以 libbpf-1.3.0版本libbpf庫(kù)為基礎(chǔ),下文會(huì)提供若干實(shí)例代碼,來(lái)剖析ebpf構(gòu)建原理。完成了基礎(chǔ)環(huán)境的初始化,就可以開(kāi)始搭建我們的eBPF項(xiàng)目。所有的代碼示例都可以通過(guò)如下git項(xiàng)目獲取。為了后面訪問(wèn)方便,這里用一個(gè)shell變量NATIVE_LIBBPF用來(lái)存儲(chǔ)工作目錄。

$ cd ~
$ git clone https://github.com/alibaba/sreworks-ext.git
$ NATIVE_LIBBPF=~/sreworks-ext/demos/native_libbpf_guide/

4.1 初步構(gòu)建基于原生libbpf庫(kù)的eBPF項(xiàng)目

首先來(lái)看一個(gè)基于原生libbpf庫(kù)的第2代eBPF構(gòu)建實(shí)例。ebpf初學(xué)者,可以考慮選擇跟蹤 execve 系統(tǒng)調(diào)用產(chǎn)生的事件。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_execve_libbpf130                            # 進(jìn)入項(xiàng)目目錄   
$ make
$ sudo ./trace_execve
trace_execve 15836221 5501 bash 1534 bash 0 /usr/bin/ls
trace_execve 15914126 5502 bash 1534 bash 0 /usr/bin/ps


$ make clean

執(zhí)行trace_execve命令,對(duì)編譯結(jié)果進(jìn)行驗(yàn)證,完美驗(yàn)證通過(guò)。

4.2 eBPF項(xiàng)目的目錄結(jié)構(gòu)解析

介紹下trace_execve_libbpf130的目錄結(jié)構(gòu)。

trace_execve_libbpf130目錄 說(shuō)明
./ 項(xiàng)目用戶(hù)態(tài)代碼和主Makefile
./progs 項(xiàng)目?jī)?nèi)核態(tài)bpf程序代碼
./include 項(xiàng)目的業(yè)務(wù)代碼相關(guān)的頭文件
./helpers 非來(lái)自于libbpf庫(kù)的一些helpler文件
./tools/lib/bpf/ 來(lái)自于libbpf-1.3.0/src/
./tools/include/ 來(lái)自于libbpf-1.3.0/include/
./tools/build/ 項(xiàng)目構(gòu)建時(shí)一些feature探測(cè)代碼
./tools/scripts/ 項(xiàng)目Makefile所依賴(lài)的一些功能函數(shù)

再介紹下本項(xiàng)目trace_execve_libbpf130和libbpf-1.3.0庫(kù)的對(duì)應(yīng)關(guān)系。下載libbpf-1.3.0庫(kù)解壓后,使用diff命令進(jìn)行目錄對(duì)比。

目錄native_libbpf_guide/trace_execve_libbpf130/tools/lib/bpf/內(nèi)容,除Makefile內(nèi)容外都來(lái)自目錄~/libbpf-1.3.0/src/。

目錄native_libbpf_guide/trace_execve_libbpf130/tools/include/來(lái)自目錄~/libbpf-1.3.0/include/,所有內(nèi)容都完全一致。

除以上兩部分來(lái)自libbpf-1.3.0庫(kù)以外的文件,其余都由本項(xiàng)目原創(chuàng)貢獻(xiàn)。

$ cd ~
$ wget http://github.com/libbpf/libbpf/archive/refs/tags/v1.3.0.tar.gz
$ tar -zxvf v1.3.0.tar.gz
$ diff -qr $NATIVE_LIBBPF/trace_execve_libbpf130/tools/lib/bpf/ ~/libbpf-1.3.0/src/
Only in ~/libbpf-1.3.0/src/: .gitignore
Files ~/native_libbpf_guide/trace_execve_libbpf130/tools/lib/bpf/Makefile and ~/libbpf-1.3.0/src/Makefile differ


$ diff -qr $NATIVE_LIBBPF/trace_execve_libbpf130/tools/include/ ~/libbpf-1.3.0/include/

在這個(gè)項(xiàng)目中添加ebpf的代碼,可以遵循這樣的目錄結(jié)構(gòu)。用戶(hù)態(tài)加載文件放到根目錄下,內(nèi)核態(tài)bpf文件放到progs目錄下,用戶(hù)態(tài)和內(nèi)核態(tài)公共的頭文件放到include目錄下。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_execve_libbpf130                            # 進(jìn)入項(xiàng)目目錄  
$ find . -name "trace_execve*"
./trace_execve.c
./progs/trace_execve.bpf.c
./include/trace_execve.h

4.3 eBPF項(xiàng)目的Makefile解析

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_execve_libbpf130                            # 進(jìn)入項(xiàng)目目錄
$ find . -name Makefile 
./Makefile
./progs/Makefile
./tools/lib/bpf/Makefile
./tools/build/feature/Makefile

trace_execve_libbpf130項(xiàng)目有4個(gè)Makefile,分別如下:

./Makefile是主文件,用于生成用戶(hù)態(tài)eBPF程序trace_execve。

./progs/Makefile 用于生成內(nèi)核態(tài)BPF程序trace_execve.bpf.o。

./tools/lib/bpf/Makefile 用于生成libbpf.a靜態(tài)庫(kù)。

./tools/build/feature/Makefile 用于一些feature的探測(cè)。

在項(xiàng)目空間的根目錄運(yùn)行make命令進(jìn)行項(xiàng)目構(gòu)建時(shí),會(huì)首先執(zhí)行Makefile文件。在Makefile文件中會(huì)通過(guò)make的-C選項(xiàng)間接觸發(fā)progs/Makefile和tools/lib/bpf/Makefile的執(zhí)行。

感興趣的同學(xué)可以通過(guò)上一章節(jié)中提到的make --debug=v,m SHELL="bash -x" 命令逐步debug這些makefile的執(zhí)行過(guò)程。

下文重點(diǎn)分析下編譯過(guò)程的一些編譯參數(shù),讓我們加深對(duì)eBPF構(gòu)建過(guò)程的理解。

4.4 C語(yǔ)言編譯器的目錄搜索選項(xiàng)

在開(kāi)始分析eBPF程序的編譯參數(shù)之前,先要簡(jiǎn)單說(shuō)一下C語(yǔ)言編譯器(gcc/clang)的目錄搜索選項(xiàng)。C語(yǔ)言的頭文件都需要按照目錄搜索選項(xiàng)的指引,才能正確找到它所在位置。

除了日常我們熟知的-I選項(xiàng),clang/gcc的目錄搜索選項(xiàng)還有很多,它們優(yōu)先級(jí)的順序依次如下:

頭文件引用方式include "myheader.h",則在當(dāng)前文件所在目錄查找myheader.h頭文件。

頭文件引用方式include "myheader.h",如果有-iquote mydir選項(xiàng),則在mydir目錄查找myheader.h頭文件。

頭文件引用方式include ,如果有-I mydir選項(xiàng),則在mydir目錄查找myheader.h頭文件。

頭文件引用方式include ,如果有-isystem mydir選項(xiàng),則在mydir目錄查找myheader.h頭文件。

頭文件引用方式include ,繼續(xù)在標(biāo)準(zhǔn)系統(tǒng)目錄(Standard system directories)查找myheader.h頭文件。標(biāo)準(zhǔn)系統(tǒng)目錄是指/usr/lib64/clang/15.0.7/include 、/usr/local/include 和/usr/include。

頭文件引用方式include ,如果有-idirafter mydir選項(xiàng),則在mydir目錄查找myheader.h頭文件。

4.5 內(nèi)核態(tài)bpf程序編譯參數(shù)解析

內(nèi)核態(tài)bpf程序trace_execve.bpf.o文件,是由 bpf 文件trace_execve.bpf.c使用clang命令編譯產(chǎn)生。trace_execve.bpf.c文件的頭文件依賴(lài)如下。

$ cat progs/trace_execve.bpf.c
// SPDX-License-Identifier: GPL-2.0
#include 
#include 
#include 


#include "common.h"
#include "trace_execve.h"

從前面項(xiàng)目構(gòu)建過(guò)程中,可以提取出完整的內(nèi)核態(tài)bpf程序的編譯命令。

$ clang -iquote ./../include/ -iquote ./../helpers -I./../tools/lib/ -I./../tools/include/uapi -idirafter /usr/lib64/clang/15.0.7/include 
  -idirafter /usr/include -idirafter /usr/include/x86_64-linux-gnu/ -DENABLE_ATOMICS_TESTS -D__KERNEL__ -D__BPF_TRACING__ 
  -D__TARGET_ARCH_x86 -g -Werror -O2 -mlittle-endian -target bpf -mcpu=v3 -c trace_execve.bpf.c -o trace_execve.bpf.o  

下面對(duì)一些關(guān)鍵環(huán)節(jié)做一些解析:

頭文件vmlinux.h由bpftool工具在編譯時(shí)動(dòng)態(tài)生成,vmlinux.h包含了絕大多數(shù)bpf程序的內(nèi)核態(tài)和用戶(hù)態(tài)(uapi)依賴(lài)。通過(guò)編譯選項(xiàng)-I./../tools/lib/可以搜索到vmlinux.h頭文件。

通過(guò)-I./../tools/lib/編譯選項(xiàng),可以在./tools/lib/目錄下的bpf子目錄中查找到bpf_helpers.h和bpf_tracing.h頭文件,這些頭文件都是對(duì)vmlinux.h頭文件內(nèi)核態(tài)依賴(lài)的補(bǔ)充。

通過(guò)-iquote ./../include/編譯選項(xiàng),可以在./include/目錄中查找到trace_execve.h和common.h頭文件。

在上面這些頭文件依賴(lài)的預(yù)處理過(guò)程中,會(huì)依賴(lài)一些宏變量來(lái)決定預(yù)處理的展開(kāi)邏輯。上面編譯命令中的宏就是起這些作用,-DENABLE_ATOMICS_TESTS -D__KERNEL__ -D__BPF_TRACING__ -D__TARGET_ARCH_x86。比如在bpf_tracing.h頭文件中,就有#if defined(__TARGET_ARCH_x86)的宏判斷語(yǔ)句,來(lái)決定預(yù)處理展開(kāi)邏輯走x86分支。

編譯選項(xiàng)-target bpf,指示Clang將代碼生成為針對(duì)eBPF目標(biāo)的目標(biāo)代碼。編譯選項(xiàng)-mcpu=v3,指示Clang生成針對(duì)v3版本的eBPF處理器的目標(biāo)代碼。編譯選項(xiàng)-mlittle-endian:指示Clang生成適用于小端序處理器的目標(biāo)代碼。

通過(guò)-I./../tools/include/uapi編譯選項(xiàng),可以在./tools/include/uapi/目錄下的linux子目錄中查找到bpf.h頭文件。同時(shí)kernel-headers包引入的/usr/include/linux/目錄下也有bpf.h,./tools/include/uapi下的bpf.h優(yōu)先級(jí)會(huì)覆蓋它。此外,目錄./tools/include/uapi/linux下的頭文件和vmlinux.h頭文件存在一定的重疊,通常情況下同時(shí)加載會(huì)出現(xiàn)編譯沖突。如果在一些簡(jiǎn)單的 ebpf 使用場(chǎng)景,可以使用替代

4.6 用戶(hù)態(tài)加載程序編譯參數(shù)解析

用戶(hù)態(tài)eBPF程序trace_execve文件,是由源文件trace_execve.c文件使用gcc命令編譯。trace_execve.c文件的頭文件依賴(lài)如下。

$ cat trace_execve.c
// from kernel-headers
#include 
#include 
#include 
#include 
#include 
#include 
#include 


// from libbpf
#include 
#include 


#include "common.h"
#include "trace_execve.h"

從前面項(xiàng)目構(gòu)建過(guò)程中,也可以提取出完整的用戶(hù)態(tài)程序的編譯命令。

gcc -iquote ./helpers/ -iquote ./include/ -I./tools/lib/ -I./tools/include/ -g -c -o trace_execve.o trace_execve.c

通過(guò)-I./tools/include/編譯選項(xiàng),可以在./tools/include/目錄下的linux子目錄中查找到頭文件。

通過(guò)-I./tools/lib/編譯選項(xiàng),可以在./tools/lib/目錄下的bpf子目錄中查找到頭文件。在一些古老的代碼示例中,有這樣使用頭文件的用法,目前最新的ebpf項(xiàng)目實(shí)例,都會(huì)將libbpf庫(kù)的libbpf.h以及同目錄的頭文件都放到bpf子目錄下,因此推薦統(tǒng)一使用的用法。

通過(guò)-iquote ./include/編譯選項(xiàng),可以在./include/目錄中查找到trace_execve.h和common.h頭文件。

其他頭文件都可以在由kerne-headers包提供的標(biāo)準(zhǔn)系統(tǒng)目錄(Standard system directories)的/usr/include/目錄及子目錄中查找到。所以,最終會(huì)在/usr/include/linux/perf_event.h位置被查找到。可以看出同樣是形式的頭文件,卻在兩個(gè)完全不同的搜索路徑查找到。

4.7 libbpf.a靜態(tài)庫(kù)編譯解析

關(guān)于libbpf.a靜態(tài)庫(kù)的編譯過(guò)程,上一篇文章已經(jīng)有所介紹。這里僅再次強(qiáng)調(diào)下,在本項(xiàng)目中,我們完全實(shí)現(xiàn)了libbpf庫(kù)的自主可控,可控源代碼,可控編譯構(gòu)建過(guò)程。這至少給我們帶來(lái)如下兩方面好處:

對(duì)于一些ebpf的資深人士,可以自主修改libbpf庫(kù)中不盡如人意的地方,實(shí)現(xiàn)滿(mǎn)足自己業(yè)務(wù)需求的優(yōu)化。

對(duì)于一些ebpf的初學(xué)者,完全可以在libbpf庫(kù)中任意感興趣的地方,通過(guò)插入printf或其他斷點(diǎn)方式,深入學(xué)習(xí)libbpf庫(kù)的原理。

05

改進(jìn)基于原生libbpf庫(kù)的eBPF項(xiàng)目構(gòu)建

5.1 傳統(tǒng)方案美中不足

在上文中,我們初步實(shí)現(xiàn)了基于libbpf庫(kù)的第 2 代 eBPF項(xiàng)目的構(gòu)建。但截止到目前,此方案還有一個(gè)明顯的缺陷。讓我們繼續(xù)上一篇的案例來(lái)分析,在搭建完開(kāi)發(fā)環(huán)境后執(zhí)行如下步驟。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_execve_libbpf130                            # 進(jìn)入項(xiàng)目目錄
$ make clean
$ make
$ sudo ./trace_execve
trace_execve 160646349 5503 sa1 1 systemd 0 /usr/lib64/sa/sa1
trace_execve 160646371 5503 sa1 1 systemd 0 /usr/lib64/sa/sadc


$ mv progs/trace_execve.bpf.o progs/trace_execve.bpf.o.bak
$ sudo ./trace_execve
libbpf: elf: failed to open progs/trace_execve.bpf.o: No such file or directory
ERROR: failed to open prog: 'No such file or directory'


$ mv progs/trace_execve.bpf.o.bak progs/trace_execve.bpf.o
$ sudo ./trace_execve
trace_execve 190767474 5566 crond 5565 crond 0 /bin/bash
trace_execve 190767486 5566 bash  5565 crond 0 /bin/run-parts

從實(shí)驗(yàn)結(jié)果可以看出,當(dāng)我們把bpf目標(biāo)文件trace_execve.bpf.o改名為trace_execve.bpf.o.bak后,trace_execve程序執(zhí)行會(huì)報(bào)錯(cuò),提示讀取trace_execve.bpf.o文件不存在。而當(dāng)我們?cè)俅螌浞莺蟮腷pf目標(biāo)文件trace_execve.bpf.o.bak改回原名trace_execve.bpf.o后,重新執(zhí)行trace_execve程序又一切正常了。這說(shuō)明,當(dāng)前方案構(gòu)建后,需要將trace_execve程序和bpf目標(biāo)文件trace_execve.bpf.o這一組文件一起進(jìn)行分發(fā),才能正常執(zhí)行。這給我們?cè)诠こ痰膶?shí)現(xiàn)上帶來(lái)了很大的挑戰(zhàn)。

為了解決上面提到的問(wèn)題,第 3 代 ebpf 編程方案 libbpf-bootstrap框架發(fā)明了skeleton骨架,即使用*.skel.h頭文件的方式,將bpf目標(biāo)文件trace_execve.bpf.o的內(nèi)容編譯進(jìn)trace_execve程序。這樣后續(xù)只需分發(fā)trace_execve二進(jìn)制程序文件即可。

如果不依賴(lài)libbpf-bootstrap編程框架,繼續(xù)僅依賴(lài) libbpf 庫(kù)是否可以做到這一點(diǎn)呢?答案是可以的,本文獨(dú)辟蹊徑,給大家分享一個(gè)使用hexdump命令輕松實(shí)現(xiàn)*.skel.h頭文件的方式。

5.2 使用hexdump生成skel.h頭文件

簡(jiǎn)單歸納一下使用libbpf-bootstrap框架編程過(guò)程中的構(gòu)建步驟。

步驟 libbpf-bootstrap框架構(gòu)建 可改進(jìn)機(jī)會(huì)點(diǎn)
1 bpftool btf dump file vmlinux format c > vmlinux.h
2 clang -O2 -target bpf -c trace_execve.bpf.c -o trace_execve.bpf.o
3 bpftool gen skeleton trace_execve.bpf.o > trace_execve.skel.h 此步驟用hexdump替換bpftool
4 gcc -o trace_execve trace_execve.c -lbpf -lelf 此步驟更改加載函數(shù)為libbpf標(biāo)準(zhǔn)函數(shù)

分析libbpf-bootstrap編程框架的實(shí)現(xiàn)原理,可以了解到。在第3步會(huì)依靠bpftool工具將trace_execve.bpf.o這個(gè)目標(biāo)文件轉(zhuǎn)換成十六進(jìn)制格式的文本,并將這個(gè)文本內(nèi)容作為trace_execve.skel.h頭文件中的一個(gè)變量的值,最后還需要讓trace_execve.c用戶(hù)態(tài)加載文件包含這個(gè)trace_execve.skel.h頭文件。這其中將bpf目標(biāo)文件轉(zhuǎn)換成十六進(jìn)制文本并生成skel.h頭文件的過(guò)程最為關(guān)鍵。

libbpf-bootstrap編程框架非常成熟,但方案使用中必須遵循他的一些規(guī)則,比如頭文件trace_execve.skel.h的命令必須包含程序的關(guān)鍵詞trace_execve,再比如加載函數(shù)trace_execve_bpf__load()也必須包含程序的關(guān)鍵詞trace_execve。如何能不依賴(lài)這個(gè)規(guī)范,實(shí)現(xiàn)一個(gè)更加輕量級(jí)的編程方案呢?這讓我們想到了hexdump命令,可以用它替換bpftool工具,并且生成符合自己期望的頭文件。

$ hexdump -v -e '"\x" 1/1 "%02x"' trace_execve.bpf.o > trace_execve.hex

5.3 深入構(gòu)建基于原生libbpf庫(kù)的eBPF項(xiàng)目

下面我們就嘗試依靠hexdump命令實(shí)現(xiàn)一個(gè)單一可執(zhí)行文件的解決方案。開(kāi)始體驗(yàn)我們基于第 2 代編程方案改進(jìn)的eBPF項(xiàng)目,進(jìn)入項(xiàng)目代碼。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd hexdump_skel_libbpf130                            # 進(jìn)入項(xiàng)目目錄
$ make
$ sudo ./trace_execve
trace_execve bash su 74113 74112 0 /usr/bin/bash
trace_execve bash su 74113 74112 0 /usr/bin/bash


$ sudo ./probe_execve
probe_execve 19076757 5572 0anacron 5570 0anacron 0
probe_execve 19076758 5573 0anacron 5570 0anacron 0

分別執(zhí)行trace_execve和probe_execve兩個(gè)命令,對(duì)編譯結(jié)果進(jìn)行驗(yàn)證,均完美驗(yàn)證通過(guò)。這里我們?cè)趖race_execve實(shí)例基礎(chǔ)上又增加了一個(gè)probe_execve實(shí)例,說(shuō)明hexdump_skel_libbpf130項(xiàng)目是支持多實(shí)例編譯的。

下面我們來(lái)驗(yàn)證下本文開(kāi)頭的情況,看看沒(méi)有了bpf目標(biāo)文件時(shí)的情形。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd hexdump_skel_libbpf130                            # 進(jìn)入項(xiàng)目目錄 
$ rm -fr progs/trace_execve.bpf.o progs/probe_execve.bpf.o
$ sudo ./trace_execve
trace_execve 19076759 5574 run-parts 5566 run-parts 0 /bin/basename
trace_execve 19076760 5575 run-parts 5566 run-parts 0 /bin/logger


$ sudo ./probe_execve
probe_execve sh python 78841 78838 0 
probe_execve sh python 78841 78838 0

從運(yùn)行結(jié)果看,雖然刪除了兩個(gè)bpf目標(biāo)文件trace_execve.bpf.o和probe_execve.bpf.o,僅僅依靠trace_execve和probe_execve兩個(gè)文件即可成功執(zhí)行。可以再?lài)L試將trace_execve 可執(zhí)行文件拷貝到其他目錄,結(jié)果依然可行。

5.4 改進(jìn)的eBPF項(xiàng)目Makefile解析

hexdump_skel_libbpf130項(xiàng)目也是同樣的4個(gè)Makefile,其中將bpf目標(biāo)文件編譯到用戶(hù)態(tài)加載進(jìn)程中的環(huán)節(jié)主要在項(xiàng)目的主Makefile中實(shí)現(xiàn)。還是老辦法獲取make構(gòu)建的詳細(xì)過(guò)程。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd hexdump_skel_libbpf130                            # 進(jìn)入項(xiàng)目目錄 
$ make clean
$ make --debug=v,m SHELL="bash -x" > make.log 2>&1

對(duì)于構(gòu)建日志的分析可以參考前面文章,我們把關(guān)鍵環(huán)節(jié)提取出來(lái)。

$ cat make.log | grep -n "Considering target file"
14:Considering target file 'all'.
16:  Considering target file 'tools/lib/bpf/libbpf.a'.
21:  Considering target file 'helpers/uprobe_helper.o'.
23:    Considering target file 'helpers/uprobe_helper.c'.
31:  Considering target file 'probe_execve'.
33:    Considering target file 'probe_execve.o'.
35:      Considering target file 'probe_execve.c'.
38:      Considering target file 'probe_execve.skel.h'.
40:        Considering target file 'probe_execve.hex'.
42:          Considering target file 'progs/probe_execve.bpf.o'.
44:            Considering target file 'progs/probe_execve.bpf.c'.
145:  Considering target file 'trace_execve'.
147:    Considering target file 'trace_execve.o'.
149:      Considering target file 'trace_execve.c'.
152:      Considering target file 'trace_execve.skel.h'.
154:        Considering target file 'trace_execve.hex'.
156:          Considering target file 'progs/trace_execve.bpf.o'.
158:            Considering target file 'progs/trace_execve.bpf.c'.

從關(guān)鍵構(gòu)建步驟中,我們可以了解到:

probe_execve和trace_execve兩個(gè)target都是all目標(biāo)的下級(jí)目標(biāo),并且probe_execve和trace_execve是串行的。這個(gè)里隱含的一個(gè)意思是,當(dāng)trace_execve開(kāi)始構(gòu)建的時(shí)候,probe_execve已經(jīng)完全構(gòu)建完畢,probe_execve這個(gè)最終可執(zhí)行文件已經(jīng)生成完畢。此時(shí),probe_execve構(gòu)建過(guò)程中所依賴(lài)的所有中間文件都不再需要了。所以,probe_execve和trace_execve構(gòu)建過(guò)程中依賴(lài)的中間文件是可以重名的。

tools/lib/bpf/libbpf.a和helpers/uprobe_helper.o已經(jīng)提前編譯好了,就不再做過(guò)多的說(shuō)明了。最終的用戶(hù)態(tài)可執(zhí)行加載程序的主要依賴(lài)鏈條如下。

trace_execve
├── trace_execve.o
│   ├── trace_execve.c
│   ├── trace_execve.skel.h
│   │   ├── trace_execve.hex
│   │   │   ├──progs/trace_execve.bpf.o
│   │   │   │   └── progs/trace_execve.bpf.c

再看一下主Makefile的源碼,為了實(shí)現(xiàn)以上的目標(biāo)依賴(lài),我們連用了5個(gè)靜態(tài)模式規(guī)則(Static Pattern Rules)。

$(HELPER_OBJECTS): %.o:%.c


$(BPF_OBJECT):./progs/%.bpf.o:./progs/%.bpf.c


$(HEX_OBJECT):%.hex:./progs/%.bpf.o


$(SKEL_OBJECT):%.skel.h:%.hex


$(USER_OBJECT):%.o:%.c %.skel.h


$(LOADER_OBJECT): %:%.o

其中任何一個(gè)靜態(tài)模式規(guī)則的目標(biāo)集合,都是通過(guò)項(xiàng)目根目錄下*.c文件的集合,進(jìn)行局部字符串替換獲得。

SOURCES := $(wildcard *.c)
HELPER_OBJECTS := $(patsubst %.c,%.o,$(wildcard $(HELPERS_PATH)/*.c))
LOADER_OBJECT  := $(patsubst %.c,%,$(SOURCES))
USER_OBJECT    := $(patsubst %.c,%.o,$(SOURCES))
SKEL_OBJECT    := $(patsubst %.c,%.skel.h,$(SOURCES))
HEX_OBJECT     := $(patsubst %.c,%.hex,$(SOURCES))
BPF_OBJECT     := $(patsubst %.c,./progs/%.bpf.o,$(SOURCES))

5.5 從file到memory實(shí)現(xiàn)讀取elf的轉(zhuǎn)變

本方案的主要邏輯是在主Makefile中實(shí)現(xiàn),但也需要c代碼中做一些調(diào)整。bpf文件trace_execve.bpf.c并不需要任何修改,只需要在用戶(hù)態(tài)加載程序trace_execve.c做一些調(diào)整。

傳統(tǒng)的讀取bpf目標(biāo)文件方式,相關(guān)代碼如下:

char filename[256] = "progs/trace_execve.bpf.o";
struct bpf_object * bpf_obj = bpf_object__open_file(filename, NULL);

改進(jìn)后的讀取memory方式,相關(guān)代碼如下:

#include "skeleton.skel.h"


struct bpf_object * bpf_obj = bpf_object__open_mem(obj_buf, obj_buf_sz, NULL);

很明顯libbpf庫(kù)提供了bpf_object__open_file(bpf_object__open)和bpf_object__open_mem兩個(gè)函數(shù)用于讀取elf格式的bpf目標(biāo)文件trace_execve.bpf.o。區(qū)別是bpf_object__open_file是在trace_execve運(yùn)行時(shí),再去讀取trace_execve.bpf.o文件內(nèi)容,而bpf_object__open_mem是在編譯時(shí),已經(jīng)把elf內(nèi)容編譯進(jìn)trace_execve程序。至于bpf_object__open函數(shù)在libbpf庫(kù)的libbpf.c文件中是對(duì)bpf_object__open_file函數(shù)的封裝。

這兩個(gè)libbpf庫(kù)函數(shù),最終都是調(diào)用elf標(biāo)準(zhǔn)庫(kù)函數(shù)實(shí)現(xiàn)了相關(guān)功能,具體代碼實(shí)現(xiàn)是在libbpf庫(kù)的libbpf.c文件中的bpf_object__elf_init函數(shù)中,代碼如下:

static int bpf_object__elf_init(struct bpf_object *obj){
        ......
        if (obj->efile.obj_buf_sz > 0) {
                elf = elf_memory((char *)obj->efile.obj_buf, obj->efile.obj_buf_sz);
        } else {
                obj->efile.fd = open(obj->path, O_RDONLY | O_CLOEXEC);
                ...... 
                elf = elf_begin(obj->efile.fd, ELF_C_READ_MMAP, NULL);
        }
        ......
}

可以看出,bpf_object__open_mem函數(shù)的最終實(shí)現(xiàn)是elf的elf_memory函數(shù),bpf_object__open_file函數(shù)的最終實(shí)現(xiàn)是elf的elf_begin函數(shù)。

5.6 原生libbpf庫(kù)與libbpf-bootstrap的若干區(qū)別

相比較第3代的 libbpf-bootstrap框架方案和第2代的傳統(tǒng)libbpf庫(kù)方案,使用hexdump命令的原生libbpf庫(kù)第 2 代改進(jìn)方案方案在實(shí)現(xiàn)方法上,有一些獨(dú)特的優(yōu)勢(shì)。

這里將這三種方案的主要區(qū)別歸納總結(jié)如下:

f85489fe-e59f-11ee-a297-92fbcf53809c.png

這里補(bǔ)充下,trace_execve_bpf__open()函數(shù)的實(shí)現(xiàn),也是間接通過(guò)libbpf庫(kù)的bpf_object__open_skeleton()函數(shù),最終也調(diào)用了bpf_object__open_mem()函數(shù)。

5.7 使用attach_tracepoint替代attach

在ebpf用戶(hù)態(tài)程序的加載過(guò)程中,有一個(gè)attach的步驟。細(xì)心的讀者應(yīng)該已經(jīng)發(fā)現(xiàn)了,在trace_execve_libbpf130項(xiàng)目中,我們使用的是bpf_program__attach()函數(shù)實(shí)現(xiàn)的靜態(tài)探針點(diǎn)的attach。而在hexdump_skel_libbpf130項(xiàng)目中,我們使用的卻是bpf_program__attach_tracepoint()函數(shù)實(shí)現(xiàn)的靜態(tài)探針點(diǎn)的attach。區(qū)別是bpf_program__attach_tracepoint函數(shù)的參數(shù)中會(huì)指定靜態(tài)探針點(diǎn)的具體信息,而bpf_program__attach不用指定靜態(tài)探針點(diǎn)的信息。進(jìn)一步閱讀bpf_program__attach函數(shù)的源代碼可以了解到,它是依靠?jī)?nèi)核態(tài)的bpf目標(biāo)文件中SEC的節(jié)名稱(chēng)信息來(lái)獲取和確定靜態(tài)探針點(diǎn)的信息的。總結(jié)這兩種方法如下:

trace_execve.c中相關(guān)代碼 trace_execve.bpf.c中相關(guān)代碼
attach方案A bpf_program__attach(bpf_prog) SEC("tracepoint/syscalls/sys_enter_execve")
attach方案B bpf_program__attach_tracepoint(bpf_prog, "syscalls", "sys_enter_execve") SEC("tracepoint")

很明顯,在trace_execve.c和trace_execve.bpf.c的代碼中,只要有一處設(shè)置靜態(tài)探針點(diǎn)即可。如果兩處都設(shè)置,而且兩處設(shè)置的靜態(tài)探針點(diǎn)信息沖突的情況下,會(huì)以用戶(hù)態(tài)的bpf_program__attach_tracepoint函數(shù)設(shè)置的信息為準(zhǔn)。

libbpf庫(kù)中的bpf_link__destroy()函數(shù)是負(fù)責(zé)對(duì)attach函數(shù)生成的link進(jìn)行銷(xiāo)毀的函數(shù)。attach和destroy的過(guò)程實(shí)際上就是對(duì)內(nèi)核靜態(tài)探針點(diǎn)開(kāi)啟和關(guān)閉的過(guò)程。

在這里特別推薦使用方案B中的bpf_program__attach_tracepoint替代方案A中的bpf_program__attach方法,這樣方便我們?cè)谟脩?hù)態(tài)代碼中靈活的開(kāi)關(guān)ebpf的采集。除了專(zhuān)門(mén)用于靜態(tài)探針點(diǎn)的bpf_program__attach_tracepoint()函數(shù),還有適用于其他類(lèi)型的專(zhuān)用的attach函數(shù),例如bpf_program__attach_kprobe()、bpf_program__attach_kprobe()、bpf_program__attach_uprobe()和bpf_program__attach_usdt()等。

5.8 使用by_name替代by_title

在稍早一些libbpf庫(kù)中提供2個(gè)函數(shù)用于獲取bpf progam 類(lèi)型數(shù)據(jù),分別是bpf_object__find_program_by_name()函數(shù)和bpf_object__find_program_by_title()函數(shù)。以trace_execve_libbpf130項(xiàng)目的 bpf代碼為例。

SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve_enter(struct syscalls_enter_execve_args *ctx){
    ......
}

其中tracepoint/syscalls/sys_enter_execve這個(gè)字符串就稱(chēng)為title,trace_execve_enter這個(gè)函數(shù)名就稱(chēng)為name。結(jié)合上文的結(jié)論,后續(xù)推薦bpf內(nèi)核態(tài)代碼中都使用SEC("tracepoint")的語(yǔ)法格式,那么使用by_title函數(shù)將不再能做出區(qū)分。因此這里特別推薦大家今后使用by_name的函數(shù)替代by_titile的函數(shù)。而且,在最新版的libbpf庫(kù)中,也徹底移除了bpf_object__find_program_by_title()函數(shù)。

06

基于原生libbpf庫(kù)改進(jìn)方案構(gòu)建USDT和Uprobe項(xiàng)目

基于hexdump命令的改進(jìn)型原生libbpf庫(kù)編程方案不但在內(nèi)核態(tài)跟蹤診斷上表現(xiàn)完美,在用戶(hù)態(tài)應(yīng)用進(jìn)程的跟蹤診斷上依然可以表現(xiàn)得非常出色。本節(jié)內(nèi)容將在上文的基礎(chǔ)上,繼續(xù)分析如何使用原生libbpf庫(kù)開(kāi)發(fā)和構(gòu)建USDT和Uprobe項(xiàng)目。

6.1 用戶(hù)態(tài)模擬程序

用戶(hù)態(tài)應(yīng)用程序的ebpf,還需要準(zhǔn)備一個(gè)模擬程序。尤其是針對(duì)USDT類(lèi)型,還需要在模擬程序中進(jìn)行靜態(tài)打點(diǎn)。本小節(jié)將提供一個(gè)如何打USDT跟蹤點(diǎn)的實(shí)例。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd mark_usdt_uprobe                                  # 進(jìn)入項(xiàng)目目錄
$ make
$ sudo cp umark /usr/bin/
$ sudo umark >/dev/null 2>/dev/null &
$ make clean

執(zhí)行完以上步驟,就啟動(dòng)了用戶(hù)態(tài)模擬程序umark,后續(xù)即可通過(guò)USDT和Uprobe方式,追蹤umark進(jìn)程的運(yùn)行情況。

下面初步對(duì)umark模擬程序的代碼做一些介紹。

$ ls 
Makefile  README.md  sdt.h  umark.c


$ cat umark.c 
#include 
#include 
//#include 
#include "sdt.h"


unsigned long long int func_uprobe1(unsigned long long int x){
    return x + 1;
}
unsigned long long int func_uprobe2(unsigned long long int x, unsigned long long int y){
    return x + y;
}
int main(int argc, char const *argv[]) {
    unsigned long long int i;
    int var1 = 10, var2 = 20, var3 = 30;
    for (i = 0; i < 86400000; i++) {
        sleep(1);
        DTRACE_PROBE1(groupa, probe1, var1);
        DTRACE_PROBE2(groupb, probe2, var2, var3);
        printf("hit uprobe1 %llu
", func_uprobe1(i));
        printf("hit uprobe2 %llu
", func_uprobe2(i + 3, i + 8));
    }
    return 0;
}

其中func_uprobe1和func_uprobe2是兩個(gè)C語(yǔ)言函數(shù)用于下文的uprobe跟蹤實(shí)例的追蹤。DTRACE_PROBE1和DTRACE_PROBE2是兩個(gè)宏函數(shù),用于在umark.c程序中打USDT的靜態(tài)跟蹤點(diǎn)。最多支持傳入12個(gè)跟蹤點(diǎn)參數(shù),即DTRACE_PROBE1、DTRACE_PROBE2,一直到DTRACE_PROBE12。probe1和probe2是這個(gè)靜態(tài)跟蹤點(diǎn)的name,groupa和groupb是跟蹤點(diǎn)name的分組名,可以省略。

DTRACE_PROBE1宏函數(shù)定義在std.h頭文件內(nèi),需要提前安裝頭文件所在包。

在rpm包環(huán)境,sdt.h頭文件屬于systemtap-sdt-devel這個(gè)rpm包。

$ find /usr/include/ -name sdt.h
/usr/include/sys/sdt.h


$ rpm -qf /usr/include/sys/sdt.h
systemtap-sdt-devel-4.8-2.0.2.al8.x86_64

在deb包環(huán)境,sdt.h頭文件屬于systemtap-sdt-dev這個(gè)deb包。

$ find /usr/include/ -name sdt.h
/usr/include/x86_64-linux-gnu/sys/sdt.h


$ dpkg -S /usr/include/x86_64-linux-gnu/sys/sdt.h
systemtap-sdt-dev:amd64: /usr/include/x86_64-linux-gnu/sys/sdt.h

令人欣慰的是,這個(gè)sdt.h頭文件并無(wú)太多額外依賴(lài),簡(jiǎn)單修改后,可以獨(dú)立維護(hù)。于是,我們可以將其拷貝到本項(xiàng)目根目錄。并將的頭文件引用方式改為"sdt.h"。

6.2 構(gòu)建基于libbpf庫(kù)的USDT和Uprobe項(xiàng)目

下面我們就進(jìn)一步介紹下使用第 2 代改進(jìn)編程方案的ebpf跟蹤用戶(hù)態(tài)進(jìn)程的解決方案。開(kāi)始體驗(yàn)我們的eBPF項(xiàng)目trace_user_libbpf130,進(jìn)入項(xiàng)目代碼。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_user_libbpf130                              # 進(jìn)入項(xiàng)目目錄
$ make
$ sudo ./uprobe_test
func_uprobe1 2374242 4604 umark 1534 bash 0 23368 23373
func_uprobe2 2374242 4604 umark 1534 bash 0 23371 23376


$ sudo ./usdt_test
func_usdt1 2375442 4604 umark 1534 bash 0 10 17
func_usdt2 2375442 4604 umark 1534 bash 0 20 30

分別執(zhí)行uprobe_test和usdt_test兩個(gè)命令,對(duì)編譯結(jié)果進(jìn)行驗(yàn)證,均完美驗(yàn)證通過(guò)。

trace_user_libbpf130項(xiàng)目的構(gòu)建和編譯過(guò)程與前面項(xiàng)目hexdump_skel_libbpf130無(wú)太多差異,不再做過(guò)多贅述。下文將著重對(duì)本項(xiàng)目中USDT和Uprobe的相關(guān)C語(yǔ)言源碼進(jìn)行解析。

6.3 USDT代碼解析

trace_user_libbpf130項(xiàng)目中的USDT部分,開(kāi)啟了2個(gè)usdt靜態(tài)探針點(diǎn)的跟蹤,這2個(gè)靜態(tài)探針點(diǎn)分別是probe1和probe2。

第一個(gè)靜態(tài)探針點(diǎn)實(shí)例,選擇在attach時(shí),通過(guò)bpf_program__attach_usdt函數(shù)的參數(shù)指定靜態(tài)探針點(diǎn)的相關(guān)信息。包括跟蹤的進(jìn)程信息"/usr/bin/umark",usdt組名信息"groupa",usdt名稱(chēng)信息"probe1"等,代碼如下:

bpf_program__attach_usdt(bpf_prog1, -1, "/usr/bin/umark", "groupa", "probe1", NULL);

?第二個(gè)靜態(tài)探針點(diǎn)實(shí)例,選擇在bpf目標(biāo)文件中,通過(guò)SEC宏的方式指定靜態(tài)探針點(diǎn)的相關(guān)信息。包括跟蹤的進(jìn)程信息"/usr/bin/umark",usdt組名信息"groupb",usdt名稱(chēng)信息"probe2"等,代碼如下:

SEC("usdt//usr/bin/umarkprobe2")
?

6.4 BPF_USDT宏函數(shù)解析

目前主流的USDT類(lèi)型的ebpf代碼實(shí)例,在bpf目標(biāo)文件中都使用BPF_USDT宏函數(shù)來(lái)定義ebpf的處理函數(shù),例如本項(xiàng)目實(shí)例中。

int BPF_USDT(usdt_probe1, int x)

在這里,宏函數(shù)BPF_USDT的第1個(gè)參數(shù)"usdt_probe1"才是真正的函數(shù)名,也就是前文所述by_name的name信息。宏函數(shù)的第2個(gè)參數(shù)"int x"才是usdt_probe1函數(shù)的第一個(gè)參數(shù),依次類(lèi)推。

各種USDT類(lèi)型的ebpf代碼實(shí)例中,很少見(jiàn)到對(duì)這個(gè)宏函數(shù)BPF_USDT原理的分析。此處,我們借助第二個(gè)USDT靜態(tài)探針點(diǎn)在bpf目標(biāo)文件中的使用來(lái)解析它。代碼實(shí)例的關(guān)鍵部分如下:

int usdt_probe2(struct pt_regs *ctx);


static inline __attribute__((always_inline)) typeof(usdt_probe2(0)) ____usdt_probe2(struct pt_regs *ctx, int x, int y);


typeof(usdt_probe2(0)) usdt_probe2(struct pt_regs *ctx) {
    return ____usdt_probe2(ctx, ({ long _x; bpf_usdt_arg(ctx, 0, &_x); (void *)_x; }), ({ long _x; bpf_usdt_arg(ctx, 1, &_x); (void *)_x; }));
}


static inline __attribute__((always_inline)) typeof(usdt_probe2(0)) ____usdt_probe2(struct pt_regs *ctx, int x, int y)
{
    ......
}

這4行代碼,前兩行是函數(shù)聲明,后兩行是函數(shù)定義。usdt_probe2函數(shù)內(nèi)部調(diào)用了____usdt_probe2函數(shù)。一些代碼解讀:

always_inline,意味著無(wú)論優(yōu)化設(shè)置如何,編譯器都應(yīng)該始終將這個(gè)函數(shù)內(nèi)聯(lián)到任何調(diào)用它的地方。

typeof(usdt_probe2(0)) 用于確定 usdt_probe2 的返回類(lèi)型,從而確保 ____usdt_probe2 與 usdt_probe2 有相同的返回類(lèi)型。

({ long _x; bpf_usdt_arg(ctx, 0, &_x); (void *)_x; }) 這個(gè)復(fù)合語(yǔ)句用于獲取USDT探針的參數(shù)值。

使用 bpf_usdt_arg 輔助函數(shù)來(lái)獲取探針的第一個(gè)參數(shù),并將其存儲(chǔ)到局部變量 _x 中。再將 _x 強(qiáng)制轉(zhuǎn)換為 void * 類(lèi)型并傳遞給 ____usdt_probe2 函數(shù)。同樣的操作也對(duì)第二個(gè)參數(shù) y 進(jìn)行。

特別強(qiáng)調(diào)一下bpf_usdt_arg輔助函數(shù)來(lái)自于usdt.bpf.h頭文件,但本項(xiàng)目有2個(gè)usdt.bpf.h頭文件,其中一個(gè)在libbpf庫(kù)中,另外一個(gè)在./helpers/目錄下,helpers 目錄下的是經(jīng)過(guò)本項(xiàng)目改造過(guò)的。此示例中生效的是./helpers/目錄下的。

$ cd $NATIVE_LIBBPF                                    # 返回工作目錄
$ cd trace_user_libbpf130                              # 進(jìn)入項(xiàng)目目錄
$ find . -name usdt.bpf.h
./tools/lib/bpf/usdt.bpf.h
./helpers/usdt.bpf.h

6.5 Uprobe代碼解析

trace_user_libbpf130項(xiàng)目中的Uprobe部分,開(kāi)啟了2個(gè)uprobe類(lèi)型探針點(diǎn)的跟蹤,這2個(gè)uprobe探針點(diǎn)分別是probe1和probe2。

第一個(gè)uprobe探針點(diǎn)實(shí)例,選擇在attach時(shí),通過(guò)bpf_program__attach_uprobe函數(shù)的參數(shù)指定uprobe探針點(diǎn)的相關(guān)信息。包括uprobe的類(lèi)型(0表示函數(shù)進(jìn)入時(shí),1表示函數(shù)返回時(shí)),跟蹤的進(jìn)程信息"/usr/bin/umark",被跟蹤的函數(shù)在進(jìn)程中的偏移量 func_off1等。需要提前通過(guò)get_elf_func_offset()函數(shù)計(jì)算出這個(gè)偏移量,此函數(shù)定義在了helpers/uprobe_helper.c文件內(nèi)。相關(guān)代碼如下:

func_off1 = get_elf_func_offset("/usr/bin/umark", "func_uprobe1");
bpf_program__attach_uprobe(bpf_prog1, 0, -1, "/usr/bin/umark", func_off1);

第二個(gè)uprobe探針點(diǎn)實(shí)例,選擇在bpf目標(biāo)文件中,通過(guò)SEC宏的方式指定uprobe探針點(diǎn)的相關(guān)信息。包括跟蹤的進(jìn)程信息"/usr/bin/umark",被跟蹤的應(yīng)用進(jìn)程中的函數(shù)"func_uprobe2"等。此種情況,libbpf庫(kù)會(huì)自動(dòng)計(jì)算這個(gè)偏移量。代碼如下:

SEC("uprobe//usr/bin/umark:func_uprobe2")

6.6 BPF_KPROBE宏函數(shù)解析

目前主流的Uprobe類(lèi)型的ebpf代碼實(shí)例,在bpf目標(biāo)文件中都使用BPF_KPROBE宏函數(shù)來(lái)定義ebpf的處理函數(shù),例如本項(xiàng)目實(shí)例中。

int BPF_KPROBE(user_probe1, unsigned long long int x)

?在這里,宏函數(shù)BPF_KPROBE的第1個(gè)參數(shù)"user_probe1"才是真正的函數(shù)名,也就是前文所述by_name的name信息。宏函數(shù)的第2個(gè)參數(shù)"unsigned long long int x"才是user_probe1函數(shù)的第一個(gè)參數(shù),依次類(lèi)推。

各種Uprobe類(lèi)型的ebpf代碼實(shí)例中,也同樣很少見(jiàn)到對(duì)這個(gè)宏函數(shù)BPF_KPROBE原理的分析。此處,我們借助第二個(gè)Uprobe探針點(diǎn)在bpf目標(biāo)文件中的使用來(lái)解析它。關(guān)鍵的代碼實(shí)例如下:

long user_probe2(struct pt_regs *ctx);


inline typeof(user_probe2(0)) ____user_probe2(struct pt_regs *ctx, unsigned long long int x, unsigned long long int y);


inline typeof(user_probe2(0)) ____user_probe2(struct pt_regs *ctx, unsigned long long int x, unsigned long long int y)
{
    ......
}


typeof(user_probe2(0)) user_probe2(struct pt_regs *ctx) {
    return ____user_probe2(ctx, (unsigned long long int)PT_REGS_PARM1(ctx), (unsigned long long int)PT_REGS_PARM2(ctx));
}

這4行代碼,前兩行是函數(shù)聲明,后兩行是函數(shù)定義。user_probe2函數(shù)內(nèi)部調(diào)用了____user_probe2函數(shù)。一些代碼解讀:

inline typeof(user_probe2(0)) ____user_probe2(struct pt_regs *ctx, unsigned long long int x, unsigned long long int y); 這是內(nèi)聯(lián)函數(shù)____user_probe2的聲明。

typeof(user_probe2(0))用于確定____user_probe2函數(shù)的返回類(lèi)型,保證與user_probe2函數(shù)的返回類(lèi)型一致。

typeof(user_probe2(0)) user_probe2(struct pt_regs *ctx) { return ____user_probe2(ctx, (unsigned long long int)PT_REGS_PARM1(ctx), (unsigned long long int)PT_REGS_PARM2(ctx)); } 這是user_probe2函數(shù)的定義。它使用PT_REGS_PARM1(ctx)和PT_REGS_PARM2(ctx)宏來(lái)獲取用戶(hù)空間探針傳遞給eBPF程序的前兩個(gè)參數(shù)。

如果對(duì)于以上的代碼解讀如果還有不明白的地方,可以嘗試問(wèn)問(wèn)GPT。

07

技術(shù)交流

本文為eBPF動(dòng)手實(shí)踐系列的第三篇,我們實(shí)現(xiàn)了基于libbpf庫(kù)的純C語(yǔ)言eBPF項(xiàng)目的構(gòu)建。




審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7613

    瀏覽量

    137239
  • LINUX內(nèi)核
    +關(guān)注

    關(guān)注

    1

    文章

    316

    瀏覽量

    21688
  • RPM
    RPM
    +關(guān)注

    關(guān)注

    0

    文章

    45

    瀏覽量

    17725
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    實(shí)戰(zhàn)eBPF kprobe函數(shù)插樁

    本文作者為團(tuán)隊(duì)小伙伴阿松,在Linux文件監(jiān)控領(lǐng)域?qū)崙?zhàn)經(jīng)驗(yàn)豐富。本次引入eBPF在文件監(jiān)控上應(yīng)用,提升文件變更的關(guān)聯(lián)進(jìn)程信息等。在實(shí)現(xiàn)過(guò)程中,分享了eBPF kbproe時(shí),被插樁函數(shù)超多參數(shù)獲取的解決方案
    發(fā)表于 11-29 09:03 ?2112次閱讀

    解構(gòu)內(nèi)核源碼eBPF樣例編譯過(guò)程

    了解和掌握純c語(yǔ)言的ebpf編譯和使用,有助于我們加深對(duì)于eBPF技術(shù)原理的進(jìn)一步掌握,也有助于開(kāi)發(fā)符合自己業(yè)務(wù)需求的高性能的ebpf程序。
    的頭像 發(fā)表于 04-17 14:05 ?1593次閱讀

    基于ebpf的性能工具-bpftrace腳本語(yǔ)法

    bpftrace 通過(guò)高度抽象的封裝來(lái)使用 eBPF,大多數(shù)功能只需要寥寥幾筆就可以運(yùn)行起來(lái),可以很快讓我們搞清楚 eBPF 是什么樣的,而暫時(shí)不關(guān)心 eBPF 復(fù)雜的內(nèi)部機(jī)理。由于
    的頭像 發(fā)表于 09-04 16:04 ?1077次閱讀
    基于<b class='flag-5'>ebpf</b>的性能工具-bpftrace腳本語(yǔ)法

    關(guān)于 eBPF 安全可觀測(cè)性,你需要知道的那些事兒

    寄存器溢出eBPF 的可觀測(cè)性應(yīng)用場(chǎng)景主要有以下類(lèi):1.云原生容器的安全可觀測(cè)性隨著云網(wǎng)邊端的急速發(fā)展,人們的目光越發(fā)的聚焦在目前最火熱的云原生場(chǎng)景上。Falco、Tracee、Te
    發(fā)表于 09-08 15:31

    openEuler 倡議建立 eBPF 軟件發(fā)布標(biāo)準(zhǔn)

    eBPF 被廣泛應(yīng)用在云原生、可觀測(cè)、性能調(diào)優(yōu)、安全、硬件加速等領(lǐng)域,并且其應(yīng)用場(chǎng)景還在快速擴(kuò)展,各種場(chǎng)景基于 eBPF 技術(shù)的創(chuàng)新 idea 呈現(xiàn)井噴現(xiàn)象,eBPF 的時(shí)代已經(jīng)來(lái)臨
    發(fā)表于 12-23 16:21

    eBPF是什么以及eBPF能干什么

    一、eBPF是什么 eBPF是extended BPF的縮寫(xiě),而B(niǎo)PF是Berkeley Packet Filter的縮寫(xiě)。對(duì)linux網(wǎng)絡(luò)比較熟悉的伙伴對(duì)BPF應(yīng)該比較了解,它通過(guò)特定的語(yǔ)法
    的頭像 發(fā)表于 07-05 15:17 ?1.2w次閱讀
    <b class='flag-5'>eBPF</b>是什么以及<b class='flag-5'>eBPF</b>能干什么

    eBPF深入理解和實(shí)現(xiàn)原理

    簡(jiǎn)單來(lái)說(shuō),wBPF 是一個(gè)在硬件上直接執(zhí)行 eBPF 程序的系統(tǒng)。
    的頭像 發(fā)表于 06-14 09:25 ?3656次閱讀

    eBPF安全可觀測(cè)性的前景展望

    本次分享將從監(jiān)控和可觀測(cè)性、eBPF安全可觀測(cè)性分析、內(nèi)核安全可觀測(cè)性展望個(gè)方面展開(kāi)。
    的頭像 發(fā)表于 08-17 11:27 ?1592次閱讀

    openEuler倡議建立eBPF軟件發(fā)布標(biāo)準(zhǔn)

    eBPF 是一個(gè)能夠在內(nèi)核運(yùn)行沙箱程序的技術(shù),提供了一種在內(nèi)核事件和用戶(hù)程序事件發(fā)生時(shí)安全注入代碼的機(jī)制,使得非內(nèi)核開(kāi)發(fā)人員也可以對(duì)內(nèi)核進(jìn)行控制。隨著內(nèi)核的發(fā)展,eBPF 逐步從最初的數(shù)據(jù)包過(guò)濾
    的頭像 發(fā)表于 12-06 10:29 ?580次閱讀

    Linux 內(nèi)核:eBPF優(yōu)勢(shì)和eBPF潛力總結(jié)

    Express Data Path (XDP):網(wǎng)絡(luò)驅(qū)動(dòng)程序是最早可以附加 XDP BPF 鉤子的點(diǎn)。當(dāng)收到一個(gè)數(shù)據(jù)包時(shí),eBPF 程序就會(huì)被觸發(fā)運(yùn)行。
    發(fā)表于 01-10 11:37 ?3224次閱讀

    什么是eBPFeBPF為何備受追捧?

    用云杉網(wǎng)絡(luò) VP 向陽(yáng)的話(huà)來(lái)說(shuō):“ eBPF 最重要(沒(méi)有之一)的特點(diǎn)是安全性” 。他表示,以往必須編寫(xiě)內(nèi)核模塊才能做到的工作現(xiàn)在基本都能做到。
    的頭像 發(fā)表于 05-06 11:41 ?2306次閱讀

    eBPF的前世今生?eBPF在使用中遇到的問(wèn)題有哪些?

    在介紹eBPF (Extended Berkeley Packet Filter)之前,我們先來(lái)了解一下它的前身-BPF (Berkeley Packet Filter)伯克利數(shù)據(jù)包過(guò)濾器。
    的頭像 發(fā)表于 08-12 15:10 ?1661次閱讀
    <b class='flag-5'>eBPF</b>的前世今生?<b class='flag-5'>eBPF</b>在使用中遇到的問(wèn)題有哪些?

    基于ebpf的性能工具-bpftrace

    在前面我已經(jīng)分享了關(guān)于ebpf入門(mén)的文章: 基于ubuntu22.04-深入淺出 eBPF 。 這篇文章介紹一個(gè)基于ebpf技術(shù)的強(qiáng)大工具--bpftrace。 在現(xiàn)代計(jì)算機(jī)系統(tǒng)中,了解系統(tǒng)的內(nèi)部
    的頭像 發(fā)表于 09-04 16:02 ?693次閱讀
    基于<b class='flag-5'>ebpf</b>的性能工具-bpftrace

    ebpf的快速開(kāi)發(fā)工具--libbpf-bootstrap

    )和libbpf的程序。eBPF是一種可以在Linux內(nèi)核中運(yùn)行的程序,提供了強(qiáng)大的網(wǎng)絡(luò)過(guò)濾、系統(tǒng)調(diào)用監(jiān)控和性能分析等功能。libbpf是一個(gè)庫(kù),用于加載和管理
    的頭像 發(fā)表于 09-25 09:04 ?1046次閱讀
    <b class='flag-5'>ebpf</b>的快速開(kāi)發(fā)工具--<b class='flag-5'>libbpf</b>-bootstrap

    基于原生libbpf庫(kù)eBPF編程改進(jìn)方案

    為了簡(jiǎn)化 eBPF程序的開(kāi)發(fā)流程,降低開(kāi)發(fā)者在使用 libbpf 庫(kù)時(shí)的入門(mén)難度,libbpf-bootstrap 框架應(yīng)運(yùn)而生。基于libbpf
    發(fā)表于 03-19 14:19 ?694次閱讀
    基于<b class='flag-5'>原生</b><b class='flag-5'>libbpf</b><b class='flag-5'>庫(kù)</b>的<b class='flag-5'>eBPF</b><b class='flag-5'>編程</b><b class='flag-5'>改進(jìn)</b><b class='flag-5'>方案</b>
    基础百家乐的玩法技巧和规则| 澳门百家乐官网怎赌才能赚钱| 上游棋牌大厅| 大发888娱乐游戏账号| 王牌百家乐的玩法技巧和规则| 南京百家乐赌博现场被| 百家乐博娱乐网| 好望角百家乐的玩法技巧和规则| 华侨人百家乐的玩法技巧和规则 | 百家乐赌场合作| 澳门百家乐赌场文| 威尼斯人娱乐场安全吗| 大发888官网充值| 棋牌室| 临沭县| 海立方娱乐城线路| 庆安县| 申请百家乐官网会员送彩金| 在线玩百家乐官网的玩法技巧和规则 | 百家乐官网游戏官网| 百家乐官网怎样玩才能赢| 百家乐官网六合彩3535 | 火箭百家乐官网的玩法技巧和规则| 百家乐压钱技巧| 蓝盾百家乐代理打| 大发888娱乐城大奖| 大发888官方下| 巩义市| 百家乐官网客户端LV| 百家乐官网庄闲的比例| 寅午戌 24山图| 老人头百家乐的玩法技巧和规则| 丹东亿酷棋牌下载| 帝王百家乐官网全讯网2| 丽星百家乐官网的玩法技巧和规则 | 大发888方管下载| 龙虎机| 最新百家乐官网出千赌具| 怎么玩百家乐网上赌博| 全讯网ceo| 百家乐官网投注开户|