BCC(BPF Compiler Collection)とは?

IT

前回は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には、サンプルのツールもいくつか含まれているので、実際に実行して動作などを確認してみてはいかがでしょうか。

コメント

タイトルとURLをコピーしました