bpftrace 通過高度抽象的封裝來使用 eBPF,大多數功能只需要寥寥幾筆就可以運行起來,可以很快讓我們搞清楚 eBPF 是什么樣的,而暫時不關心 eBPF 復雜的內部機理。由于 bpftrace 深受 AWK 和 c 的影響,bpftrace 使用起來于 AWK 非常相似,那些內核 hook 注入點幾乎可以按普通字符串匹配來理解,非常容易上手。
前面我們介紹了如何部署bpftrace工具,并且介紹了如何運行bpftrace腳本,這篇文章將介紹bpftrace腳本的語法。
基于ubuntu22.04-深入淺出 eBPF
基于ebpf的性能工具-bpftrace
bpftrace腳本語法
腳本格式
- bpftrace腳本基本格式如下:
probe{
actions;
}
- bpftrace語法深受AWK的影響,{前的部分相當于AWK的condition,{}中的部分相當于AWK的action。只不過bpftrace執行actions的條件是觸發probe名稱指定的事件。
- probe是探針的名稱,我們知道內核中函數非常多,為了方便,內核對probe做了namespace處理,這里的probe通常是以冒號:分割的一組名稱,比如:
tracepointtick_stop
kprobe:do_sys_open
-
顯然,最后一部分表示的是函數名稱,其他部分則是namespace,這樣做有兩點好處:①便于查找函數;②便于定位不同模塊中的同名函數。
-
bpftrace除了可以監聽指定的probe事件,還有兩個特殊的probe:BEGIN,END。這與AWK類似,它們分別在bpftrace程序執行開始、結束時,無條件的執行一些操作,比如完成一些初始化、清理工作等。
BEGIN{
print("helloworld.n");
}
END{
print("byeworld.n");
}
- filter是可選的,有時候我們只需要探測特定條件下函數的行為,比如參數為某個值的時候,就可以用到filter,這需要了解bpftrace如何訪問probe的變量,我們稍晚再說。
prbbe參數
ebpf支持的probe:hardware,iter,kfunc,kprobe,software,tracepoint,uprobe。

- dynamic tracing
-
ebpf提供了內核和應用的動態trace,分別用于探測函數入口處和函數返回(ret)處的信息。
- ①面向內核的 kprobe/kretprobe,k = kernel
- ②面向應用的 uprobe/uretprobe,u = user land
-
kprobe/kretprobe 可以探測內核大部分函數,出于安全考慮,有部分內核函數不允許安裝探針,另外也可以配合 offset 探測函數中任意位置的信息。
-
uprobe/uretprobe 則可以為應用的任意函數安裝探針。
-
動態 trace 技術依賴內核和應用的符號表,對于那些 inline 或者 static 函數則無法直接安裝探針,需要自行通過 offset 實現。可以借助 nm 或者 strings 指令查看應用的符號表。
-
這兩種動態 trace 技術的原理與 GDB 類似,當對某段代碼安裝探針,內核會將目標位置指令復制一份,并替換為 int3 中斷, 執行流跳轉到用戶指定的探針 handler,再執行備份的指令,如果此時也指定了 ret 探針,也會被執行,最后再跳轉回原來的指令序列。
-
kprobe 和 uprobe 可以通過 arg0、arg1... ... 訪問所有參數;kretprobe 和 uretprobe 通過 retval 訪問函數的返回值。除了基本類型:char、int 等,字符串需通過 str() 函數才能訪問。
- static tracing
-
靜態 trace,所謂 “靜態” 是指探針的位置、名稱都是在代碼中硬編碼的,編譯時就確定了。靜態 trace 的實現原理類似 callback,當被激活時執行,關閉時不執行,性能比動態 trace 高一些。
- ① 內核中的靜態trace:tracepoint
- ② 應用中的靜態trace: usdt = Userland Statically Defined Tracing
-
靜態 trace 已經在內核和應用中飽含了探針參數信息,可以直接通過 args->參數名 訪問函數參數。tracepoint 的 參數 format 信息可以通過 bpftrace -v probe 查看:
youyeetoo@youyeetoo:~$bpftrace-lvtracepointsys_exit
tracepointsys_exit
longid
longret
youyeetoo@youyeetoo:~$
- 或者訪問debugfs:
youyeetoo@youyeetoo:~$cat/sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/format
name:sys_exit
ID:348
format:
field:unsignedshortcommon_type;offset:0;size:2;signed:0;
field:unsignedcharcommon_flags;offset:2;size:1;signed:0;
field:unsignedcharcommon_preempt_count;offset:3;size:1;signed:0;
field:intcommon_pid;offset:4;size:4;signed:1;
field:longid;offset:8;size:8;signed:1;
field:longret;offset:16;size:8;signed:1;
printfmt:"NR%ld=%ld",REC->id,REC->ret
youyeetoo@youyeetoo:~$
內置變量
無論 Dynamic tracing 或者 Static tracing,它們的目的都是監聽特定函數調用事件,這些函數即可以在內核中,也可以在用戶態的應用或者 lib 中。獲知這些函數調用時的參數、返回值就已經實現了開發者大半目標。除此之外,bpfstrace 還內置了一些變量,用戶訪獲得探測對象自身信息。這些變量在 bpftrace 中直接訪問即可,如下:
- pid / tid:Bpftrace或者說eBPF工作在內核,因此這些變量都與內核中進程表示有關。先說tid,內核中線程與進程沒做作明確區分,它們都是相同的調度對象task_sruct。tid是thread id的縮寫,由于歷史原因,在task中的成員是task_sruct.pid。所以對于Linux內核,線程=輕量級進程。而pid實際上指的是內核中進程組,由task中的task_sruct.tgid成員表示。也就是說,進程=線程組。
- uid / gid:執行函數的用戶ID、組ID。
- nsecs:時間戳,納秒。
- elapsed:ebpfs 啟動后的納秒數。
- numaid:NUMA = Non-Uniform Memory Access,與多核 CPU 的內存訪問相關。
- cpu:當前 cpu 編號,從 0 開始。
- comm:進程名稱,通常為進程可執行文件名。
- kstack:內核棧。
- ustack: 用戶棧。
- arg0, arg1, ..., argN:函數參數。
- sarg0, sarg1, ..., sargN:函數參數(棧中)。
- retval:返回值。
- func:函數名,可以在可執行文件的符號表中這個函數名。
- probe:探針的完整名稱,也就是 bpftrace 中 形如 'kprobe:do_nanosleep'
- curtask:當前 task struct。
- rand:一個無符號 32 位隨機數。
- cgroup:當前進程的 Cgroup,內核資源組,類似 namespace,docker 等虛擬化技術即基于內核提供的這一基礎設施。
- cpid:子進程 pid,bpftrace 允許通過 -c 指定一個 cmd 運行,然后在該進程上安裝 probe。
- 2, ..., #:bpftrace 程序自身的位置參數
全局變量
-
全局變量@name,所謂的全局變量:①對所有的probe actions可見,②bpftrace生命周期內可見。
-
bpftrace支持兩種變量形式:
-
測試例子:
kprobe:do_nanosleep{
@start[tid]=nsecs;
}
kretprobe:do_nanosleep/@start[tid]!=0/{
printf("sleptfor%dmsn",(nsecs-@start[tid])/1000000);
delete(@start[tid]);
}
- 運行效果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt
Attaching2probes...
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
sleptfor0ms
臨時變量
$name, 只在當前action中有效,超出action的{}不具備記憶能力。
內置函數
bpftrace無法自定義函數,但提供了約36個內置函數,可以在bpftrace腳本的任意位置調用它們。完整的列表可以參考官方文檔:(https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md)。
bpftrace的函數非常有限,原因是bpftrace腳本會編譯為bytecode,交由內核中的eBPF VM執行,出于安全和效率考慮,eBPF VM不能允許用戶執行任意函數,僅允許執行限定的函數,或缺有限的數據。
- printf -- printf(fmt, ...)bpftrace的printf函數行為與C語言基本一致,區別在于它只支持有限的格式化字符,不如C語言支持的那么多。
BEGIN{
print("helloworld.n");
}
END{
print("byeworld.n");
}
- time -- time(fmt)time函數用于打印當前時間,可以通過參數中的格式化字符串指定,如果沒有指定格式化字符串,那么默認格式是%H:%M:%Sn。time函數完全兼容strftime的格式化字符,下面列出一些常用項:
- %S 秒,00-60;
- %M 分鐘,00-59;
- %I 小時,01-12;%H 小時,00-23;
- %d 每月的第幾天,01-31;
- %w 星期,0-6, 0 指 星期日;
- %m 月份,01-12;
- %y 年份,00-99;%Y 完整的年份;
「注意:格式化字符結尾不要忘記換行,否則不會自動清空緩沖區到標準輸出,就看不到輸出了。」
youyeetoo@youyeetoo:~$bpftrace-e'interval1{time("%Y%H:%M:%Sn");}'
Attaching1probe...
202316:35:30
202316:35:31
202316:35:32
^C
- system該函數可以調用 shell,用于 probe 觸發其他用戶態可執行程序非常有用。下面是一個簡單的例子,定時調用 `ps. 查看當前進程:
youyeetoo@youyeetoo:~$bpftrace--unsafe-e'kprobe:do_nanosleep{system("ps-p%dn",pid);}'
Attaching1probe...
PIDTTYTIMECMD
933?00:00:00cron
^C
- ustack當使用 uprobe 時,很可能需要關注用戶進程的 stack 情況,ustack 函數接受 2 個參數,這兩個參數可以同時使用,或者只用 1 個。
- mode,stack 模式,可選 bpftrace、perf;
- limit,一個整數,獲取 stack 的最大深度;
youyeetoo@youyeetoo:~$bpftrace-e'uprobereadline{printf("%sn",ustack(perf,3));}'
stdin:1:1-21:WARNING:attachingtouprobetargetfile'/usr/bin/bash'butmatched2binaries
uprobereadline{printf("%sn",ustack(perf,3));}
~~~~~~~~~~~~~~~~~~~~
Attaching1probe...
56440bb42690readline+0(/usr/bin/bash)
56440bb42690readline+0(/usr/bin/bash)
56440bb42690readline+0(/usr/bin/bash)
控制語句
bpftrace 也提供了常見的流程控制語句:① 條件語句 ② 循環語句
- 條件語句
- bpftrace的條件語句用法與C語言完全一樣:
if(condition){
statements;//A
}else{
statements;//B
}
- 當滿足條件時執行 A 處語句,否則執行 B 處語句。當然也可能有以下更簡單的形式,沒有 else 部分,條件滿足時執行 A 處語句,然后執行 B 處語句,否則跳過 A 處語句:
if(condition){
statements;//A
}
statements;//B
- 多個 if-else 也可能連接在一起:
if(condition){
statements;//A
}elseif(condition){
statements;//B
}elseif(condition){
statements;//C
}else{
statements;//D
}
- 測試樣例:
BEGIN{
$num=$1;
if($num>=10){
$result="A";
}elseif($num>=5){
$result="B";
}else{
$result="C"
}
printf("result:%sn",$result);
exit();
}
- 測試樣例結果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt15
Attaching1probe...
result:A
youyeetoo@youyeetoo:~$bpftracebpf_test.bt8
Attaching1probe...
result:B
youyeetoo@youyeetoo:~$bpftracebpf_test.bt3
Attaching1probe...
result:C
youyeetoo@youyeetoo:~$
- 循環語句
- bpftrace 支持一種最常見的循環形式:
while(condition){
//dosomething
}
- 測試樣例:
BEGIN{
$i=0;
while($i10){
printf("i=%dn",$i);
$i++
}
exit();
}
- 測試樣例結果:
youyeetoo@youyeetoo:~$bpftracebpf_test.bt
Attaching1probe...
i=0
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9
審核編輯 黃宇
-
嵌入式
+關注
關注
5150文章
19665瀏覽量
317454 -
函數
+關注
關注
3文章
4380瀏覽量
64850 -
語法
+關注
關注
0文章
44瀏覽量
10167 -
腳本
+關注
關注
1文章
398瀏覽量
28453
發布評論請先 登錄
解構內核源碼eBPF樣例編譯過程
如何在 Shell 腳本中執行語法檢查調試模式
openEuler 倡議建立 eBPF 軟件發布標準
服務器端腳本與動態網頁設計,下載
強勁的Linux Trace工具 bpftrace for Linux 2018

eBPF是什么以及eBPF能干什么

基于ebpf的性能工具-bpftrace

基于ebpf的性能工具應用

評論