Ubuntu Weekly Recipe

第694回 libbpfとclangでポータブルなBPF CO-REバイナリ作成

この記事を読むのに必要な時間:およそ 10.5 分

第688回第690回では,カーネルのトレーシングツールとして注目されているeBPFを活用するためのツールとしてBCCを紹介しました。 また第692回ではBCC以外のeBPFを活用したツールの利用方法も紹介しています。

今回は一般的なコンパイラのようにバイナリを生成でき,移植性が高く,そして近い将来eBPFを使うための本流のひとつとなりそうなBPF CO-REについて紹介しましょう。

BPF CO-REの登場

BPF CO-RE(Compile Once - Run Everywhere)については第692回でも軽く紹介しました。改めてまとめると,次のような機能を実現する仕組みです。

  • BPFを利用したバイナリを実行環境とは異なる環境でビルドできる
  • カーネルバージョン間の違いもある程度は吸収してくれる
  • 実行バイナリのサイズはそれなりに小さくなる
  • コンテナ内部などターゲットと異なるカーネルが動いている環境でもビルドできる

つまりステージング環境や開発環境でビルド&テストしたバイナリを,プロダクション環境に配布・実行できるということです。特にモニタリングやデバッグ用途で,できるだけプロダクション環境をいじることなくeBPFバイナリを活用したい場合に,非常に便利な仕組みとなります。

実はBCCでは,大半のツールにもBPF CO-RE版のコードが用意されています。将来的にPython版は廃止して,libbpfを利用したBPF CO-RE版に全面的に移行する予定のようです。つまりこのコードを参考にすればどんな風に実現すれば良いかがすぐにわかるでしょう。

ただしBPF CO-REを使用するためには,比較的新しいカーネルやClang/LLVMなどのツールチェイン,それに実行したバイナリをロードするためのlibbpfが必要になります。特にeBPFのポータブル性を実現するためには,実行中のカーネルの内部情報を提供するBPF Type Format(BTF)のサポートが重要になってくるのです。

Ubuntuの場合,最初にBPF CO-REが動くようになったのは,20.10の頃になります。つまり現時点で最新のLTSであるUbuntu 20.04 LTSでは,BPF CO-REの恩恵は受けられません。また,BPF/BTF自体がまだ活発に機能拡張されているため,本格的に使うならより新しいカーネルやツールチェインを使いたいところです。よってUbuntuの場合は,2022年4月にリリースされる次のLTSであるUbuntu 22.04 LTSぐらいから,プロダクション用途でも使われるようになっていくのではないでしょうか※1⁠。

※1
古いカーネルでもBTFを追加ロードすることでBPF CO-REなオブジェクトをロードできるようにするBPF Hubというプロジェクトもあるようです。

たとえばUbuntu 21.10のKernel 5.13における,BPF/BTF関連のカーネルコンフィグは次のようになっています。

$ grep -E "(BPF|BTF)[_= ]" /boot/config-$(uname -r)
CONFIG_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
# BPF subsystem
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
# CONFIG_BPF_PRELOAD is not set
CONFIG_BPF_LSM=y
# end of BPF subsystem
CONFIG_CGROUP_BPF=y
CONFIG_IPV6_SEG6_BPF=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_NET_CLS_BPF=m
CONFIG_NET_ACT_BPF=m
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_VIDEO_SONY_BTF_MPX=m
CONFIG_DEBUG_INFO_BTF=y
CONFIG_PAHOLE_HAS_SPLIT_BTF=y
CONFIG_DEBUG_INFO_BTF_MODULES=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_TEST_BPF=m

ちなみにUbuntu 21.04以降は,libbpfが最初からインストールされるようになりました。これはiproute2がlibbpfをサポートするようになったためで,それに合わせてlibbpfもデスクトップ版・サーバー版ともに最初からインストールされていることを期待できます。

今回はUbuntu 21.10ベース(libbpf v0.4)で話を進めます。また22.04ではv0.5以降が採用される見込みです。将来的にUbuntu 22.04 LTSに移行する予定がある場合は,今のうちに21.10や現在開発中のJemmyを試しておくと良いでしょう。

BPF CO-REのコンパイル環境の準備

今回も第690回と同じように,システムコールであるexecve()をトレースするコードを題材として使いましょう。ちなみに第692回ではsysfs以下を使ってツールレスに同等の機能を実現したり,bpftraceでより簡単に実現する方法も紹介しています。興味のあるかたはそちらも参照してください。

BPF CO-REのバイナリをコンパイルするには,これまでよりも若干複雑な手順を踏む必要があります。

  • bpftoolコマンドでターゲットとなるカーネルバージョンのvmlinux.hを入手する
  • ターゲットのカーネルにロードされるBPFプログラムをClangでコンパイルしてBPFオブジェクトファイルを生成する
  • そのBPFオブジェクトファイルから,bpftoolコマンドを用いてユーザーランドのプログラムで使うためのスケルトンヘッダーファイルを生成する
  • スケルトンヘッダーファイルを使って,ユーザーランドのプログラムをコンパイルする

簡単に言うと,BCCやbpftraceがこれまで肩代わりしてくれた諸々を,自分でもやる必要があるわけです。

まずはビルドに必要なツール一式をインストールしておきましょう。

$ sudo apt install build-essential libbpf-dev clang llvm linux-tools-generic

前半はコンパイルに必要なツール群です。最後のlinux-tools-genericパッケージはビルドに使うbpftoolコマンドと,実行中のカーネル固有の設定を取得するためのライブラリがセットになったパッケージです。このうちbpftoolコマンドは,linux-tools-commonパッケージが提供していますので,bpftoolのカーネルバージョンに依存しないサブコマンドだけを使うなら,こちらのパッケージだけインストールすると良いでしょう。

今回は最低限のコードのみを紹介します。必要なのは「BPFプログラムのコード」「それをロードするためのコード」の2ファイルです。

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。