BCCでBPF toolsの開発をするための基礎知識

最近仕事で h2olog を開発しています (H2O の QUIC 層をトレースしよう | Toru Maesaka) 。これはh2oのUSDT からデータを取り出すBPF toolです。その関係でBPFとかBCCをいろいろ触ったので、BPF toolsの開発に必要なことを書いておきます。

なおBCCまわりはまだ細かいところの作りが荒いので、思わぬトラブルがあるかもしれません。幸いpull-reqのレビュー&マージもかなり早いので、気になったところはどんどん直していっています。

ToC

開発環境のセットアップ

Linuxマシンが必要です。Ubuntu 18.04 以降だと簡単にインストールできましたが、Ubuntu 16.04だとビルドまわりでトラブルことが多いです。BPFはLinux 4.xのときに大きく開発が進んだ機能なので、カーネルだけ新しめのものにすることも必要かもしれません。

BCCのビルドはINSTALL.mdにあるとおりです。

トレース対象のプログラムのdtrace supportを有効にする

H2Oの場合は、SystemTap package (Linux用のdtrace)がインストールされていると自動でDTrace supportが有効になります。

トレース対象のプログラムのUSDTを確認する

BCC toolsに付属している tplist(8) (tplist-bpfcc という名前でインストールされる) で確認できます。

USDTは library (path to object), provider, probe という構成要素で、たとえばh2oの場合、h2oバイナリに組み込まれたUSDTは次のように確認します。

# h2o project dirで build/ でビルドした場合
$ sudo tplist-bpfcc -l build/h2o
build/h2o picotls:new_secret
...
build/h2o quicly:new_token_acked
...
build/h2o h2o:h1_close
...

h2o binaryにはpicotls, quicly, h2oというproviderのもとでそれぞれいくつかUSDTがある、ということがわかりますね。

生きているプロセスのUSDTは次のようにして確認できます。

$ sudo tplist-bpfcc -p $(pgrep -o h2o)
...
/proc/63337/root/usr/lib/x86_64-linux-gnu/libc-2.30.so libc:memory_mallopt_mxfast
/proc/63337/root/usr/lib/x86_64-linux-gnu/libpthread-2.30.so libpthread:pthread_start
...

h2oバイナリのほか、リンクされているライブラリのUSDTも確認できました。

開発する

あとは https://github.com/iovisor/bcc/blob/master/docs/tutorial_bcc_python_developer.md を眺めながら開発するだけ!

実際のコードはこんな感じです。

https://github.com/toru/h2olog/blob/master/h2olog

BCCPython bindingを使っているので、Python code中の文字列リテラルのなかのC言語がメインロジックという、一見するとちょっとつらい感じのコードになってますが、まあC言語なのでsyntax highlightや補完がなくても意外となんとかなります。

h2ologはC++で書き直されました。C++のなかの文字列リテラルにCでBPFプログラムを書くという構造になってます。

用語集

BPF

Berkley Packet Filter。現在は eBPF (extended BPF) と統合され汎用的なトレース技術となっている。Linux kernelに組み込まれた機能。トレーサープログラム (BPF tools) が対象プロセスにアタッチして対象プロセス内で発生するイベントを取得する。

BPFはカーネルに組み込まれているので、バグ修正や新機能追加の恩恵をうけるにはカーネルのアップデートが必要。

BPF module (BPF program)

Linux kernelに組み込まれたBPF virtual machineで実行されるプログラムで、バイトコードで表現される。

BCC (iovisor/bcc)

BPF Compiler Collection。BPF moduleのフロントエンドのひとつで、C言語で書いたコードをLLVMをつかってBPF moduleにコンパイルする。C言語とBPF moduleには表現力に大きな違いがあるため、実質的にはC言語のサブセットとなっている。たとえば、初期化されていない可能性のある変数へのアクセスや一部のループはBPF verifierによって弾かれる。また、関数のスタックが非常に小さい(512バイト)。

BPF verifier

BPF moduleのverifier。kernel内で任意のコードを実行させるため安全性を確認する必要がある。

eBPF

extended BPF。オリジナルのBPFを拡張して汎用性を高めたもの。現在は "BPF" といえばオリジナルのBPFではなくeBPFのこと。 なお、eBPF以前のBPFは cBPF (classic BPF) と呼ばれることもある。

USDT

User Statically-Defined Tracing。 ユーザースペースで静的に定義したトレースポイント。Linuxには様々なトレーシング技術があり、そのなかの一つという位置付け。

USDTはD言語で構造を宣言し、それをdtrace(1)でCヘッダファイルに変換してCプログラムに手動で埋め込む。

bpftrace (iovisor/bpftrace)

BPFのフロントエンドのひとつで、DスクリプトでBPF moduleを書く。ツール・スクリプト言語ともにDTrace互換らしい…が、h2oのdtrace単体テストをみるかぎり分岐もそこそこあるため、まったく同じように使えるわけではない。

https://github.com/h2o/h2o/blob/master/t/90dtrace.t

IO Visor

Linux Foundation傘下のプロジェクト。BCCやbpftraceを開発している。

SystemTap

Linux用のdtrace(1)を提供するプロジェクト。このdtrace(1)を使って、D言語で書いたUSDT (*.d) を使うためのCヘッダファイルを生成する。このあたりの作業フローはmacOSのdtrace(1)とほぼ同じ。

https://sourceware.org/systemtap/

参考文献