前回はBPF(Berkely Packet Filter)について簡単に紹介しました。そこで今回は、BPFプログラム作成のためのライブラリBCC(BPF Compiler Collection)を利用してみましょう。
BPF Compiler Collection (BCC)とは?
BCCとは、BPFプログラムの作成を簡単にするツールです。これにより、トレーシングやネットワーク処理のBPFプログラムを作成できます。
早速、ツールをインストールしてみましょう。インストールが完了すると、/usr/share/bcc/tools/配下にツールが置かれます。RHEL8に対するインストール方法を記しています。
# yum install bcc-tools
# ll /usr/share/bcc/tools/
total 924
-rwxr-xr-x. 1 root root 34547 Sep 2 22:17 argdist
-rwxr-xr-x. 1 root root 2408 Sep 2 22:17 bashreadline
-rwxr-xr-x. 1 root root 16382 Sep 2 22:17 bindsnoop
-rwxr-xr-x. 1 root root 6242 Sep 2 22:17 biolatency
-rwxr-xr-x. 1 root root 8117 Sep 2 22:17 biolatpcts
-rwxr-xr-x. 1 root root 5535 Sep 2 22:17 biosnoop
-rwxr-xr-x. 1 root root 6450 Sep 2 22:17 biotop
-rwxr-xr-x. 1 root root 1163 Sep 2 22:17 bitesize
-rwxr-xr-x. 1 root root 2612 Sep 2 22:17 bpflist
-rwxr-xr-x. 1 root root 4728 Sep 2 22:17 cachestat
-rwxr-xr-x. 1 root root 7312 Sep 2 22:17 cachetop
...
例としてtcpacceptというeBPF機能を利用したツールを使って、カーネルがacceptキューに追加するすべての接続を表示してみましょう。また、manページには、利用可能なオプションなどの情報を確認することができます。
# /usr/share/bcc/tools/tcpaccept
PID COMM IP RADDR RPORT LADDR LPORT
1056 sshd 4 192.168.122.1 54786 192.168.122.12 22
# man tcpaccept
また、実行の際に、以下のようなエラーが発生した場合には、カーネルとカーネルヘッダのバージョンが一致するか、確認してください。
# /usr/share/bcc/tools/tcpaccept
modprobe: FATAL: Module kheaders not found in directory /lib/modules/4.18.0-193.el8.x86_64
Unable to find kernel headers. Try rebuilding kernel with CONFIG_IKHEADERS=m (module) or installing the kernel development package for your running kernel version.
chdir(/lib/modules/4.18.0-193.el8.x86_64/build): No such file or directory
Traceback (most recent call last):
File "/usr/share/bcc/tools/tcpaccept", line 248, in <module>
b = BPF(text=bpf_text)
File "/usr/lib/python3.6/site-packages/bcc/__init__.py", line 357, in __init__
raise Exception("Failed to compile BPF module %s" % (src_file or "<text>"))
Exception: Failed to compile BPF module <text>
他にも、tcpconnlatというツールでは、送信したSYNパケットと受信した応答パケットの時間を測定することもできます。
# /usr/share/bcc/tools/tcpconnlat
PID COMM IP SADDR DADDR DPORT LAT(ms)
5944 curl 4 192.168.122.12 172.217.161.78 80 46.38
他の例では、VFSをトレースするfiletopを利用してみます。これは、最も頻繁にREADまたはWRITEしたプロセス、コマンド名、ファイル名のサマリーを表示します。デフォルトでは、1秒毎ですが、ここでは10秒間隔で表示してみます。
# /usr/share/bcc/tools/filetop 10
12:46:25 loadavg: 0.00 0.00 0.00 1/613 3818
TID COMM READS WRITES R_Kb W_Kb T FILE
2387 gsd-housekeepin 6 0 79 0 R mountinfo
3818 clear 2 0 60 0 R xterm-256color
2387 gsd-housekeepin 4 0 16 0 R utab
3814 filetop 2 0 15 0 R loadavg
2387 gsd-housekeepin 2 0 8 0 R fstab
921 irqbalance 2 0 2 0 R stat
921 irqbalance 2 0 2 0 R interrupts
3818 clear 3 0 0 0 R libtinfo.so.6.1
3818 filetop 3 0 0 0 R clear
3818 clear 1 0 0 0 R libc-2.28.so
3818 filetop 2 0 0 0 R ld-2.28.so
2320 sssd_kcm 1 0 0 0 R cmdline
Detaching...
TIDは、thread IDです。Tは、タイプでRがRegular file、SがSocket、OがOtherです。
トレースプログラムの作成: KPROBES
ここからは、BCCを使って簡単なカーネルトレースをするプログラムを作成してみます。
#!/usr/bin/python3
from bcc import BPF
bpf_source = """
#include <uapi/linux/ptrace.h>
int do_sys_execve(struct pt_regs *ctx) {
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
bpf_trace_printk("executing program: %s\\n", comm);
return 0;
}
"""
bpf = BPF(text=bpf_source)
execve_function = bpf.get_syscall_fnname("execve")
bpf.attach_kprobe(event=execve_function, fn_name="do_sys_execve")
bpf.trace_print()
コマンド名の長さに制限があるため、ここでは文字列長を16に設定しています。カーネルが実行するコマンド名をbpf_get_current_comm()を利用して取得しています。
BPF(text=bpf_source)により、BPFオブジェクトを作成します。
また、get_syscall_fnname()により、execveシステムコールがカーネルのバージョンによって変更されても、BCCが正しい名前を取得してくれます。
attach_kprobe()では、eventを計測し、カーネル関数が呼び出されると、ここで定義した関数が呼び出されます。
trace_print()によって、トレースログを出力します。
上記プログラムを実行すると、つぎのような結果が表示されます。
# ./example.py
b' bash-18066 [000] .... 77107.496996: 0: executing program: bash'
b' <...>-18068 [001] .... 77121.614395: 0: executing program: polkitd'
b' polkitd-18073 [000] .... 77121.640226: 0: executing program: polkitd'
b' <...>-18072 [001] .... 77121.643645: 0: executing program: (ystemctl)'
b' <...>-18080 [001] .... 77121.679964: 0: executing program: (time-dir)'
...
トレースプログラムの作成: KRETPROBES
先程のKprobeは、カーネルインストラクションの実行の前に、BPFプログラムを挿入することができます。
一方、Kretprobeは、カーネルインストラクションの実行が終わり、値が返ってきたときに、BPFプログラムを実行します。
#!/usr/bin/python3
from bcc import BPF
bpf_source = """
#include <uapi/linux/ptrace.h>
int ret_sys_execve(struct pt_regs *ctx) {
int return_value;
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
return_value = PT_REGS_RC(ctx);
bpf_trace_printk("program: %s, return: %d\\n", comm, return_value);
return 0;
}
"""
bpf = BPF(text=bpf_source)
execve_function = bpf.get_syscall_fnname("execve")
bpf.attach_kretprobe(event=execve_function, fn_name="ret_sys_execve")
bpf.trace_print()
PT_REGS_RCというマクロによって、BPFレジスタからの返り値を読み込んでいます。また、attach_kretprobeにより、カーネル関数のeventの戻りを計測し、カーネル関数が戻る時に呼び出されるfn_nameをアタッチします。
上記プログラムを実行すると、つぎのような結果が表示されます。
# ./example.py
b' <...>-46764 [001] d... 250889.032279: 0: program: pkla-check-auth, return: 0'
b' <...>-46766 [001] d... 250889.040176: 0: program: systemctl, return: 0'
b' <...>-46770 [000] d... 250889.059477: 0: program: systemd-user-ru, return: 0'
b' <...>-46777 [001] d... 250889.089293: 0: program: pkla-check-auth, return: 0'
b' <...>-46779 [000] d... 250889.104686: 0: program: pkla-check-auth, return: 0'
b' <...>-46781 [000] d... 250889.120083: 0: program: pkla-check-auth, return: 0'
b' <...>-46783 [001] d... 250892.589964: 0: program: sshd, return: 0'
b' <...>-46785 [000] d... 250895.521192: 0: program: unix_chkpwd, return: 0'
b' <...>-46786 [000] d... 250895.577331: 0: program: unix_chkpwd, return: 0'
b' <...>-46787 [000] d... 250895.650476: 0: program: pkla-check-auth, return: 0'
b' <...>-46788 [001] d... 250895.653045: 0: program: systemd-user-ru, return: 0'
b' <...>-46793 [001] d... 250895.685208: 0: program: unix_chkpwd, return: 0'
b' <...>-46789 [001] d... 250895.725003: 0: program: systemd, return: 0'
...
BCC レファレンス ガイド
bccをインストールすると、README.mdも同時にインストールされます。
.mdファイルは、次のようにして参照することもできます。
$ pandoc /usr/share/doc/bcc/README.md|lynx -stdin
最新のガイドは、https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md でも見られるため、よかったら参考にしてみてください。
まとめ
BCC(BPF Compiler Collection)を利用することにより、簡単にカーネルトレースができます。
またBCCには、サンプルのツールもいくつか含まれているので、実際に実行して動作などを確認してみてはいかがでしょうか。
コメント