🐦
入門 eBPF ①「Hello World」を通して、カーネルに耳をすませよう
今回の目的
- Pythonの BCC(BPF Compiler Collection) ライブラリを使って、カーネル空間で「Hello World」を表示する最小のeBPFプログラムを実行してみます。
- eBPFがどうやってカーネルの内部を観察できるのかを体感することが今回の目標です。
筆者の実行環境
- 実行環境:UTM上の Linux(Ubuntu)
→ Mac上で仮想的にLinuxを動かして検証しています。 - eBPFの動作確認にはroot権限が必要なため、実行時は
sudoを使用します。
そもそもeBPFとは?
- eBPF(extended Berkeley Packet Filter) とは、Linuxカーネル内でイベントが発生した際に、それに対応する処理を安全かつ柔軟に実行できる仕組みです。
- カーネル空間で動作する仮想マシン(VM)のような存在で、現在はLinuxカーネルの一機能として提供されています。
-
ハイパーバイザのように実際のOSを仮想化するものではなく、
専用の命令セットを持つ、仮想的なCPUとして動作します。 - eBPF専用のバイトコード(VM上で動く中間コード)をカーネルに渡すと、そのコードは安全性の検証を経て、CPUが直接実行できるマシンコードへと変換・実行されます。
- さらに詳しく知りたい方はこちら ↓
実際に動かしてみよう!
以下のコードをhello.pyとして保存し、実行してみます。
#!/usr/bin/python3
from bcc import BPF
#カーネル側で動くeBPFプログラム(C言語風)
program = r"""
int hello(void *ctx) {
bpf_trace_printk("Hello World!");
return 0;
}
"""
#ここからはユーザー空間側の処理
#eBPFプログラムをコンパイルしてカーネルにロード
b = BPF(text=program)
#execve(新しいプロセス実行)の関数名を取得
syscall = b.get_syscall_fnname("execve")
#execve呼び出し時にhello()をフックします
b.attach_kprobe(event=syscall, fn_name="hello")
#カーネルログをリアルタイムに出力します
b.trace_print()
execve() を呼んでログを観測してみよう!
まず、今のターミナルからsudo python3 hello.py を実行します。
これでeBPFプログラムがカーネルにロードされ、execve() の呼び出しを監視し始めます。
次に、別のターミナルを並列で起動し、lsやecho testなど、 プロセスを実行するコマンドを打ってみましょう。
すると、execve() が呼ばれるたびに
Hello Worldのログがカーネルから出力されるはずです。
出力例
[002] d..21 3175.804551: bpf_trace_printk: Hello Wor
sh-1254 ...
この出力を分解してみましょう!
| 部分 | 意味 |
|---|---|
| [002] | CPUの番号。マルチコアのうち「どのCPU上でイベントが起きたか」。この例だとCPU 2。 |
| d..21 | トレースのフラグ。スケジューラ関連の状態を表す。 |
| 3175.804551 | イベントが発生したタイムスタンプ(秒)。起動後3175秒くらいに発生。 |
| bpf_trace_printk: | eBPFプログラム内で呼ばれたトレース出力関数。 |
| Hello Wor... | bpf_trace_printk("Hello World!") で出したメッセージ。途中で切れてるのは文字数制限のため。 |
| sh-1254 | イベントを起こしたプロセス名とPID。たとえば sh(シェル)プロセスのPID 1254。 |
つまり何を意味しているのか
- これらの情報を通じて、カーネル内部でどんなプロセスがどのCPU上で動いているかを観察できます。
Discussion