第688回と第690回では,
Python版BCCの問題点
これまで紹介していたBPF Compiler Collection
実行環境でBPFオブジェクトをビルドする必要があるこの方法にはいくつかの問題点が存在します。
- 実行環境にコンパイラをインストールする必要がある
- 実行環境にカーネルのヘッダーファイルが必要になる
- 実行時にコンパイルという重い処理が走る
この問題は日常的に開発に利用しているデスクトップ環境やサーバーならそこまで影響はありません。しかしながらプロダクション用途となると話は別です。セキュティ的にもメンテナンス的にもできる限り余計なものはインストールしたくないでしょう。障害の事前検知手段としてeBPFを使いたいと考えたときに,
- ※1
- BPFの移植性の問題と今回紹介するCO-REによってどのように解決されるか,
執筆時点での状況などは 「BPF CO-RE (Compile Once – Run Everywhere)」 が非常によくまとまっています。とても丁寧でわかりやすいまとめなので, もしかしたら誰かが許可を得て, 日本語訳を作ってくれているかもしれません。
対応策として,
- Clang等でBPFオブジェクトをコンパイルし,
別途ロードする - sysfsの機能を用いてトレーシングする
- bpftrace等の別のツールを使う
- BPF CO-REによりポータブルなバイナリを生成する
Clang等でBPFオブジェクトをコンパイルし,別途ロードする
Clang等を使えばBPFオブジェクトをコンパイルできます。これをさらにカーネルにロードしてしまえば,
sysfsの機能を用いてトレーシングする
2番目の選択肢としてカーネルのsysfsにあるトレース機能を使ってトレーシングを行う方法が考えられます。この方法ならシステムに追加でソフトウェアをインストールしなくても,trace_
でしか取得できないことでしょう。つまりユーザーランド側はtrace_
の出力結果を解析する必要があります。表示するデータの量は変更可能ではあるのですが,
bpftrace等の別のツールを使う
より高機能なトレーシングツールとしてbpftraceが存在します。内部的にやっていることはBCCと同じでその場でコンパイルすることになるのですが,
BPF CO-REによりポータブルなバイナリを生成する
最後の方法が現在注目されている,
BPF CO-REもbpftraceと同じくカーネルのBTFを活用します。BTFは端的に言うとBPFプログラムを動かすために必要な,
つまり
結局何を使うべきか
このように,
今回例をあげたもの中だと,
- ※2
- 古いカーネルでもBTFを追加ロードすることでBPF CO-REなオブジェクトをロードできるようにする
「BPF Hub」 というプロジェクトもあるようです。
今回はsysfsとbpftraceを使う方法を紹介します。BPF CO-REについては次回以降に解説予定です。
今一度execveをトレースする
まずは最初に第690回で解説した,execve()
をトレースするPython版BCC向けのコードexecve.
)
#!/usr/bin/python3
from bcc import BPF
bpf_text="""
#include <linux/sched.h>
struct data_t {
u32 pid;
u32 ppid;
char comm[TASK_COMM_LEN];
char fname[128];
};
BPF_PERF_OUTPUT(events);
int syscall__execve(struct pt_regs *ctx, const char __user *filename)
{
struct data_t data = {};
struct task_struct *task;
data.pid = bpf_get_current_pid_tgid() >> 32;
task = (struct task_struct *)bpf_get_current_task();
data.ppid = task->real_parent->tgid;
bpf_get_current_comm(&data.comm, sizeof(data.comm));
bpf_probe_read_user(data.fname, sizeof(data.fname), (void *)filename);
events.perf_submit(ctx, &data, sizeof(struct data_t));
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_kprobe(event=b.get_syscall_fnname("execve"), fn_name="syscall__execve")
print("PID PPID COMM FNAME")
def print_event(cpu, data, size):
event = b["events"].event(data)
print("{:<8} {:<8} {:16} {}".format(event.pid, event.ppid, event.comm.decode(), event.fname.decode()))
b["events"].open_perf_buffer(print_event)
while True:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()
BCC
$ sudo apt instal python3-bpfcc $ sudo python3 execve.py (中略) PID PPID COMM FNAME 2822958 5480 tmux: server /bin/sh 2822960 2822958 sh /usr/bin/byobu-status 2822959 5480 tmux: server /bin/sh 2822961 2822960 byobu-status /usr/bin/sed 2822963 2822959 sh /usr/bin/byobu-status 2822964 2822960 byobu-status /usr/bin/tmux