本學習筆記來自於閱讀 Brendan Gregg的《BPF Performance Tools》
一、 KPROBES
kprobes
調試技術是內核開發者們專門爲了便於跟蹤內核函數執行狀態所設計的一種輕量級內核調試技術。它可以在生產環境中實時進行此操作,無需重新重啓系統或以任何特殊模式運行內核。這意味着我們可以在內核的絕大多數指定函數中動態的插入探測點(動態插樁)來收集所需的調試狀態信息。
kprobes技術還具有稱爲kretprobes的接口,用於檢測函數何時返回及其返回值。 當kprobes和kretprobes具有相同的功能時,可以記錄時間戳以計算功能的持續時間; 這可能是性能分析的重要指標
1. 工作流程
-
當用戶註冊一個探測點後,kprobe首先複製和保存目標地址的字節(足夠的字節以使其可以用斷點指令替換)
-
將目標地址字節替換爲斷點指令: x86_64 上的 int3
-
當指令流達到此斷點時,斷點處理程序將檢查它是否由kprobes安裝,如果是,則執行kprobe處理程序
-
然後執行原始指令,並恢復指令流程
-
當不再需要kprobe時,原始字節將被複制回目標地址,從而將指令恢復爲原始狀態
2. kprobe接口
最初的kprobes技術是通過編寫一個內核模塊來定義的,該模塊定義了用C編寫的 pre- and post-handlers
並通過kprobe API調用註冊它們: register_kprobe()
然後,您可以加載內核模塊並通過調用printk()
的系統消息發出自定義信息。 完成後,您需要調用unregister_kprobe()
. 但幾乎沒有人使用這種方式.
如今,有三種使用kprobes的接口
kprobe API
:register_kprobe()
等- 基於
Ftrace
: 可以通過將配置字符串寫入此文件(/sys/kernel/debug/tracing/kprobe_events
)來啓用和禁用kprobes perf_event_open()
:由perf工具使用,最近由BPF跟蹤提供支持
kprobes的最大用途是通過前端跟蹤器,例如: perf, SystemTap, BPF 跟蹤器 (BCC和bpftrace)
3. BPF和kprobes
kprobes爲BCC和bpftrace提供內核動態插樁,並被衆多工具使用。
例如,從任何內核函數中提取調用計數,可用於表示內核子系統的工作負載
a. BCC
利用 attach_kprobe()
和 attach_kretprobe()
示例1: vfsstat
工具檢測對虛擬文件系統(VFS)接口的鍵調用,並顯示每秒的摘要
vfsstat
# output
TIME READ/s WRITE/s CREATE/s OPEN/s FSYNC/s
07:48:16: 736 4209 0 24 0
07:48:17: 386 3141 0 14 0
07:48:18: 308 3394 0 34 0
[...]
可以在vfsstat的源代碼中看到跟蹤的探針(probes), 其利用了 attach_kprobe()
,以在event =
後看到內核功能
grep attach_ vfsstat.py
b.attach_kprobe(event="vfs_read", fn_name="do_read")
b.attach_kprobe(event="vfs_write", fn_name="do_write")
b.attach_kprobe(event="vfs_fsync", fn_name="do_fsync")
b.attach_kprobe(event="vfs_open", fn_name="do_open")
b.attach_kprobe(event="vfs_create", fn_name="do_create")
[...]
b. bpftrace
利用 kprobe
和 kretprobe probe
類型
示例1: 作爲bpftrace的示例,此一類代碼通過匹配vfs_
來計算所有VFS函數的調用. 輸出可以看出各個函數調用次數
bpftrace -e 'kprobe:vfs_* { @[probe] = count() }'
Attaching 54 probes...
^C
# output
@[kprobe:vfs_unlink]: 2
@[kprobe:vfs_rename]: 2
@[kprobe:vfs_readlink]: 2
@[kprobe:vfs_statx]: 88
@[kprobe:vfs_statx_fd]: 91
[...]
二、UPROBES
uprobes提供了用戶級別的動態插樁, 它的接口類似於 kprobes, 但用於用戶空間過程。 uprobes可以檢測用戶級函數 enties 以及指令偏移量. 另外uretprobes可以檢測功能返回值.
uprobes也是基於文件的:跟蹤可執行文件中的功能時,將檢測使用該文件的所有進程,包括將來啓動的進程。 這樣就可以在系統範圍內跟蹤庫調用。
1. 工作流程
在目標指令處插入一個斷點,該斷點將執行傳遞給uprobe處理程序。當不再需要uprobe時,目標指令將恢復爲原始字節。
示例: 以readline()
函數爲例
原始指令
gdb -p 31817
[...]
(gdb) disas readline
Dump of assembler code for function readline:
0x000055f7fa995610 <+0>: cmpl $0xffffffff,0x2656f9(%rip)
# 0x55f7fabfad10
<rl_pending_input>
0x000055f7fa995617 <+7>: push %rbx”
當被替換爲斷點後: 可以看到第一個地址被替換成了 int3
斷點
gdb -p 31817
[...]
(gdb) disas readline
Dump of assembler code for function readline:
0x000055f7fa995610 <+0>: int3
0x000055f7fa995611 <+1>: cmp $0x2656f9,%eax
2. uprobes 接口
如今,有兩種使用uprobes的接口
- 基於
Ftrace
: 可以通過將配置字符串寫入此文件(/sys/kernel/debug/tracing/uprobes_events
)來啓用和禁用uprobes perf_event_open()
:由perf工具使用,最近由BPF跟蹤提供支持
3. BPF 和 uprobes
kprobes爲BCC和bpftrace提供用戶級別動態插樁,並被衆多工具使用。BCC中的uprobe接口支持檢測功能和任意地址的開頭,而bpftrace當前僅支持功能的開頭
開銷和未來工作: uprobes可以附加到每秒觸發數百萬次的事件,例如用戶級分配例程:malloc()
和free()
即使BPF在性能上進行了優化,但將微小的開銷乘以每秒數百萬次仍會加起來, 速度很慢
BCC
利用: attach_uprobe()
和 attach_uretprobe()
示例: gethostlatency
工具通過解析程序庫(getaddrinfo 和 gethostbyname)來檢測主機解析調用(DNS)
gethostlatency
TIME PID COMM LATms HOST
01:42:15 19488 curl 15.90 www.brendangregg.com
01:42:37 19476 curl 17.40 www.netflix.com
01:42:40 19481 curl 19.38 www.netflix.com
01:42:46 10111 DNS Res~er #659 28.70 www.google.com
在源代碼中可以看到跟蹤到的探針以獲取主機等待時間,其利用了attach_uprobe()
和attach_uretprobe()
調用。 在sym =
分配後可以看到用戶級功能
grep attach_ gethostlatency.py
# output
b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry", pid=args.pid)
b.attach_uprobe(name="c", sym="gethostbyname", fn_name="do_entry",
b.attach_uprobe(name="c", sym="gethostbyname2", fn_name="do_entry",
bpftrace
利用了 uprobe
和 uretprobe probe
類型
示例: 這些單行代碼列出了libc系統庫中所有gethost
函數的調用,然後對其進行了計數
bpftrace -l 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethost*'
# output
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname2
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostname
uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostid
[...]
bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethost* { @[probe] = count(); }'
# output
Attaching 10 probes...
^C
@[uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostname]: 2