入門 eBPF 読書メモ

入門eBPF を読んだので、 読書メモを残しておきたい。去年発売された本でずっと読みたかったもの。 lizrice/learning-ebpf - github.com にサンプルコードが豊富に準備されているので参考になる。 文脈は意識せずとりあえず個人的に気になったところをメモしておく。 BCC まずはBCCを使った例から始まる。 bpf_trace_printk() を使うと擬似ファイル/sys/kernel/debug/tracing/trace_pipeにテキストを出力できる。 簡易的にはこれを使えば良いが、eBPFプログラムごとに出力先を分けたい場合には、BPF MAP を使って自前で カーネルとユーザ間でデータをやり取りすれば良い。 PerfリングバッファやBPFリングバッファを使えば柔軟なデータ構造(構造体)でやり取りできる。 前者はCPUごとに領域が分かれているが、後者は全てのCPUで共通の領域で順番も正しい。さらに性能も良いらしい。 Tail Callという仕組みを使えば別のeBPFプログラムを呼び出せる。 Tail Callは完了しても元のeBPFプログラムには戻ってこないのでジャンプのようなもの。 Tail Callを使うにはあらかじめ BPF_MAP_TYPE_PROG_ARRAY 型のMAPを準備しておく必要がある。 仮想マシン 汎用レジスタは10個、そしてスタックフレームポインタがある 1。 Calling Conventionについて解説がされていた。 reg0がeBPFプログラムの引数で、reg1がその戻り値となる。 関数呼び出しではreg1からreg5が引数となる。 命令長は64bitだけど、それを組み合わせたワイド命令もある。 SEC()マクロでコンパイル後のセクション名がわかる。 CからeBPFバイトコードへ、あるいはRustからeBPFバイトコードへコンパイルできる。 bpf(2) bpftool コマンドは eBPF のロードやアタッチができる便利なツールだが、その中では bpf(2)が使われている。 多くの操作は bpf(2) で実現できるが、アタッチに関してはいくつかバリエーションがあり、bpf(2)な場合もあればperf_even_open(2)とioctl(2)を組み合わせる場合もある。 カーネルにロードされたeBPFプログラムやMAPは、複数参照カウンタが0になれば自動削除されるが、BPF linkや特殊なファイルへのピニングによって参照カウンタを1つ増やせる。 これによって bpftool コマンドは実行が終了したとしても、eBPFプログラムをロードしたままにできる。 CO-RE, libbpf CO-RE (Compile Once - Run Everywhere)を使うには BCC ではなく libbpf を使う。 カーネル5.4以降でサポートされている 。vmlinux.h を include するとカーネル内の多くの構造体を使える。 bpf_core_read を使うと実行カーネルバージョンとコンパイル時カーネルバージョンの差を埋めるよう、自動的にリロケートを考慮しつつカーネルデータを読み込める。 -g をつけてコンパイルしておくとeBPF検証機のデバッグも楽になる。 以前はループをunrollする必要があったが、今はbpf_loopやbpf_for_eachが用意されている。 プログラムタイプ 30個くらいのプログラムタイムと40を超えるアタッチメントタイプがある。 bpftool features でプログラムタイプごとに利用できるヘルパ関数一覧を取得できる。 Kfuncs という仕組みを使うと、カーネル内の関数をBPFサブシステムに登録できる。 さらにCORE BPF Kfuncs というカーネルバージョン互換のあるものもある x86限定であるが、kprobe/kretprobeよりもfentry/fexitを使うと良い。 fentry/fexitはカーネル5.5でのBPFトランポリンのアイデアと一緒に導入された。 fexit は引数と返り値をまとめて取得できる点でも便利。 tracepoint は安定したインターフェースを提供してくれている。 さらにBTF Tracepointもあり、カーネルバージョンごとの構造体メンバの差分を吸収してくれる。 uprobe/uretprobeやUSDTでユーザ空間の関数にアタッチできる。 プログラムタイプにはLSM(Linux Security Module)向けのものもある。 これによってeBPFからセキュリティポリシーを強制できる。もともとはカーネルモジュールがやっていたこと。 ...

December 5, 2024