bpftrace 是一個 基于 eBPF 的新型追蹤工具,在 Fedora 28 第一次引入。Brendan Gregg、Alastair Robertson 和 Matheus Marchini 在網上的一個松散的黑客團隊的幫助下開發了 bpftrace。它是一個允許你分析系統在幕后正在執行的操作的追蹤工具,可以告訴你代碼中正在被調用的函數、傳遞給函數的參數、函數的調用次數等。
這篇文章的內容涉及了 bpftrace 的一些基礎,以及它是如何工作的,請繼續閱讀獲取更多的信息和一些有用的實例。
eBPF(擴展的伯克利數據包過濾器)
eBPF 是一個微型虛擬機,更確切的說是一個位于 Linux 內核中的虛擬 CPU。eBPF 可以在內核空間以一種安全可控的方式加載和運行小型程序,使得 eBPF 的使用更加安全,即使在生產環境系統中。eBPF 虛擬機有自己的指令集架構(ISA),類似于現代處理器架構的一個子集。通過這個 ISA,可以很容易將 eBPF 程序轉化為真實硬件上的代碼。內核即時將程序轉化為主流處理器架構上的本地代碼,從而提升性能。
eBPF 虛擬機允許通過編程擴展內核,目前已經有一些內核子系統使用這一新型強大的 Linux 內核功能,比如網絡、安全計算、追蹤等。這些子系統的主要思想是添加 eBPF 程序到特定的代碼點,從而擴展原生的內核行為。
雖然 eBPF 機器語言功能強大,由于是一種底層語言,直接用于編寫代碼很費力,bpftrace 就是為了解決這個問題而生的。eBPF 提供了一種編寫 eBPF 追蹤腳本的高級語言,然后在 clang / LLVM 庫的幫助下將這些腳本轉化為 eBPF,最終添加到特定的代碼點。
安裝和快速入門
在終端 使用 sudo 執行下面的命令安裝 bpftrace:
-
$ sudo dnf install bpftrace
使用“hello world”進行實驗:
-
$ sudo bpftrace -e 'BEGIN { printf("hello world\n"); }'
注意,出于特權級的需要,你必須使用 root 運行 bpftrace
,使用 -e
選項指明一個程序,構建一個所謂的“單行程序”。這個例子只會打印 “hello world”,接著等待你按下 Ctrl+C
。
BEGIN
是一個特殊的探針名,只在執行一開始生效一次;每次探針命中時,大括號 {}
內的操作(這個例子中只是一個 printf
)都會執行。
現在讓我們轉向一個更有用的例子:
-
$ sudo bpftrace -e 't:syscalls:sys_enter_execve { printf("%s called %s\n", comm, str(args->filename)); }'
這個例子打印了父進程的名字(comm
)和系統中正在創建的每個新進程的名稱。t:syscalls:sys_enter_execve
是一個內核追蹤點,是 tracepoint:syscalls:sys_enter_execve
的簡寫,兩種形式都可以使用。下一部分會向你展示如何列出所有可用的追蹤點。
comm
是一個 bpftrace 內建指令,代表進程名;filename
是 t:syscalls:sys_enter_execve
追蹤點的一個字段,這些字段可以通過 args
內建指令訪問。
追蹤點的所有可用字段可以通過這個命令列出:
-
bpftrace -lv "t:syscalls:sys_enter_execve"
示例用法
列出探針
bpftrace
的一個核心概念是探針點,即 eBPF 程序可以連接到的(內核或用戶空間的)代碼中的測量點,可以分成以下幾大類:
-
kprobe
——內核函數的開始處 -
kretprobe
——內核函數的返回處 -
uprobe
——用戶級函數的開始處 -
uretprobe
——用戶級函數的返回處 -
tracepoint
——內核靜態追蹤點 -
usdt
——用戶級靜態追蹤點 -
profile
——基于時間的采樣 -
interval
——基于時間的輸出 -
software
——內核軟件事件 -
hardware
——處理器級事件
所有可用的 kprobe
/ kretprobe
、tracepoints
、software
和 hardware
探針可以通過這個命令列出:
-
$ sudo bpftrace -l
uprobe
/ uretprobe
和 usdt
是用戶空間探針,專用于某個可執行文件。要使用這些探針,通過下文中的特殊語法。
profile
和 interval
探針以固定的時間間隔觸發;固定的時間間隔不在本文的范疇內。
統計系統調用數
映射 是保存計數、統計數據和柱狀圖的特殊 BPF 數據類型,你可以使用映射統計每個系統調用正在被調用的次數:
-
$ sudo bpftrace -e 't:syscalls:sys_enter_* { @[probe] = count(); }'
一些探針類型允許使用通配符匹配多個探針,你也可以使用一個逗號隔開的列表為一個操作塊指明多個連接點。上面的例子中,操作塊連接到了所有名稱以 t:syscalls:sysenter_
開頭的追蹤點,即所有可用的系統調用。
bpftrace
的內建函數 count()
統計系統調用被調用的次數;@[]
代表一個映射(一個關聯數組)。該映射的鍵 probe
是另一個內建指令,代表完整的探針名。
這個例子中,相同的操作塊連接到了每個系統調用,之后每次有系統調用被調用時,映射就會被更新,映射中和系統調用對應的項就會增加。程序終止時,自動打印出所有聲明的映射。
下面的例子統計所有的系統調用,然后通過 bpftrace
過濾語法使用 PID 過濾出某個特定進程調用的系統調用:
-
$ sudo bpftrace -e 't:syscalls:sys_enter_* / pid == 1234 / { @[probe] = count(); }'
進程寫的字節數
讓我們使用上面的概念分析每個進程正在寫的字節數:
-
$ sudo bpftrace -e 't:syscalls:sys_exit_write /args->ret > 0/ { @[comm] = sum(args->ret); }'
bpftrace
連接操作塊到寫系統調用的返回探針(t:syscalls:sys_exit_write
),然后使用過濾器丟掉代表錯誤代碼的負值(/arg->ret > 0/
)。
映射的鍵 comm
代表調用系統調用的進程名;內建函數 sum()
累計每個映射項或進程寫的字節數;args
是一個 bpftrace
內建指令,用于訪問追蹤點的參數和返回值。如果執行成功,write
系統調用返回寫的字節數,arg->ret
用于訪問這個字節數。
進程的讀取大小分布(柱狀圖):
bpftrace
支持創建柱狀圖。讓我們分析一個創建進程的 read
大小分布的柱狀圖的例子:
-
$ sudo bpftrace -e 't:syscalls:sys_exit_read { @[comm] = hist(args->ret); }'
柱狀圖是 BPF 映射,因此必須保存為一個映射(@
),這個例子中映射鍵是 comm
。
這個例子使 bpftrace
給每個調用 read
系統調用的進程生成一個柱狀圖。要生成一個全局柱狀圖,直接保存 hist()
函數到 @
(不使用任何鍵)。
程序終止時,bpftrace
自動打印出聲明的柱狀圖。創建柱狀圖的基準值是通過 args->ret 獲取到的讀取的字節數。
追蹤用戶空間程序
你也可以通過 uprobes
/ uretprobes
和 USDT(用戶級靜態定義的追蹤)追蹤用戶空間程序。下一個例子使用探測用戶級函數結尾處的 uretprobe
,獲取系統中運行的每個 bash
發出的命令行:
-
$ sudo bpftrace -e 'uretprobe:/bin/bash:readline { printf("readline: \"%s\"\n", str(retval)); }'
要列出可執行文件 bash
的所有可用 uprobes
/ uretprobes
, 執行這個命令:
-
$ sudo bpftrace -l "uprobe:/bin/bash"
uprobe
指向用戶級函數執行的開始,uretprobe
指向執行的結束(返回處);readline()
是 /bin/bash
的一個函數,返回鍵入的命令行;retval
是被探測的指令的返回值,只能在 uretprobe
訪問。
使用 uprobes
時,你可以用 arg0..argN
訪問參數。需要調用 str()
將 char *
指針轉化成一個字符串。
自帶腳本
bpftrace
軟件包附帶了許多有用的腳本,可以在 /usr/share/bpftrace/tools/
目錄找到。
這些腳本中,你可以找到:
-
killsnoop.bt
——追蹤kill()
系統調用發出的信號 -
tcpconnect.bt
——追蹤所有的 TCP 網絡連接 -
pidpersec.bt
——統計每秒鐘(通過fork
)創建的新進程 -
opensnoop.bt
——追蹤open()
系統調用 -
bfsstat.bt
——追蹤一些 VFS 調用,按秒統計
你可以直接使用這些腳本,比如:
-
$ sudo /usr/share/bpftrace/tools/killsnoop.bt
你也可以在創建新的工具時參考這些腳本。
鏈接
- bpftrace 參考指南——https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
-
Linux 2018
bpftrace
(DTrace 2.0)——http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html - BPF:通用的內核虛擬機——https://lwn.net/Articles/599755/
- Linux Extended BPF(eBPF)Tracing Tools——http://www.brendangregg.com/ebpf.html
- 深入 BPF:一個閱讀材料列表—— https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf
原文鏈接:https://linux.cn/article-13632-1.html