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

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

如何在 Shell 腳本中執(zhí)行語法檢查調(diào)試模式
總結(jié)linux腳本語法和正則表達(dá)式的應(yīng)用
openEuler 倡議建立 eBPF 軟件發(fā)布標(biāo)準(zhǔn)
服務(wù)器端腳本與動(dòng)態(tài)網(wǎng)頁設(shè)計(jì),下載
強(qiáng)勁的Linux Trace工具 bpftrace for Linux 2018

eBPF是什么以及eBPF能干什么

基于ebpf的性能工具-bpftrace

基于ebpf的性能工具應(yīng)用

腳本錯(cuò)誤scripterror怎么解決
腳本調(diào)試工具有哪些?腳本調(diào)試工具怎么用?
Shell腳本檢查工具ShellCheck介紹

評(píng)論