Ubuntu Weekly Recipe

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

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

異なるカーネルのvmlinux.hを生成するには

bpftoolプログラムはvmlinuxファイルからもvmlinux.hを生成してくれます。もしamd64マシンの上から異なるアーキテクチャーのvmlnux.hを生成したいなら次のようにデバッグシンボル付きパッケージを取得・生成することも可能です。

$ wget http://ddebs.ubuntu.com/pool/main/l/linux/linux-image-unsigned-5.15.0-12-generic-dbgsym_5.15.0-12.12_amd64.ddeb
$ dpkg-deb -x linux-image-unsigned-5.15.0-12-generic-dbgsym_5.15.0-12.12_amd64.ddeb .
$ bpftool btf dump file usr/lib/debug/boot/vmlinux-5.15.0-12-generic format c > vmlinux.h

デバッグシンボル付きパッケージは,通常のリポジトリは異なるリポジトリに置かれています※5⁠。このうちamd64/arm64/ppc64el/s390xのカーネルパッケージはmain/l/linux以下にありますし,他にも各クラウド向けカーネルなどはmain/l/linux-awsmain/l/linux-azuremain/l/linux-gcpなどに配置されています。また,main/l/linux-riscvのようにサポートされたばかりのアーキテクチャーは別ディレクトリになっていることもあるため,とりあえずは「main/l/」以下を眺めてみると良いでしょう※6⁠。

※5
詳細は第674回カーネルのクラッシュ情報を解析するを参照してください。
※6
リリースごのフレーバーとバージョンはカーネルチームのサイトにあるバージョン情報が参考になります。

もし実機があるなら次の方法でダウンロードURLを取得できます。

$ apt download --print-uris linux-image-unsigned-$(uname -r)
'http://jp.archive.ubuntu.com/ubuntu/pool/main/l/linux/linux-image-unsigned-5.13.0-21-generic_5.13.0-21.21_amd64.deb' (略)

ここの「pool/」以下が「ddebs.ubuntu.com」のそれに一致します。なおデバッグシンボル付きパッケージは,数GBのサイズなのでダウンロードする際はネットワークやストレージの状態に注意しましょう。

ユーザーランドプログラムの作成

次に,BPFオブジェクトをロード・適用するための,ユーザーランドプログラムexecsnoop.cを次のように作成します。

/* SPDX-License-Identifier: CC0-1.0 */

#include <stdio.h>
#include <unistd.h>
#include "execsnoop.skel.h"

int main(void)
{
    struct execsnoop_bpf *obj;

    obj = execsnoop_bpf__open();
    if (!obj) {
        fprintf(stderr, "failed to open BPF object\n");
        return 1;
    }

    if (execsnoop_bpf__load(obj)) {
        fprintf(stderr, "failed to load BPF object\n");
        goto cleanup;
    }

    if (execsnoop_bpf__attach(obj)) {
        fprintf(stderr, "failed to attach BPF object\n");
        goto cleanup;
    }

    for (;;) {
        sleep(1);
    }

cleanup:
    execsnoop_bpf__destroy(obj);
    return 0;
}

これが実質最低限必要なプログラムです。このうちexecsnoop_bpfで始まる名前はすべて,スケルトンヘッダーファイルとして自動生成されたものです。上記で実施しているのは次のような処理です。

  • FOO__open()でBPFオブジェクトをメモリ上に展開します。一度成功したあとは,不要になったらFOO__destroy()で破棄する必要があります。
  • FOO__load()でBPFオブジェクトの検証が行われます。コードに問題があった場合は,だいたいはここでエラーになります。
  • FOO__attach()でカーネルにアタッチします。実際にBPFプログラムが動き出すのはこのタイミングです。

FOO__open()FOO__load()はまとめて実行するFOO__open_and_load()も用意されているので,普段はそちらを使っても良いでしょう。

今回はbpf_printk()でトレースバッファーに書き出すためのプログラムです。そこでユーザーランドプログラム側は,アタッチしたあとは単に無限ループでBPFプログラムが動き続けるのを眺めているだけになります。

ユーザーランドプログラムをビルドしましょう。こちらはGCCなりClangなり,好みのコンパイラーを使えます。

$ cc -g -O2 -Wall -c execsnoop.c -o execsnoop.o
$ cc -g -O2 -Wall execsnoop.o -lbpf -o execsnoop
$ ldd execsnoop
        linux-vdso.so.1 (0x00007fff2413c000)
        libbpf.so.0 => /lib/x86_64-linux-gnu/libbpf.so.0 (0x00007f83d6ece000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f83d6ca6000)
        libelf.so.1 => /lib/x86_64-linux-gnu/libelf.so.1 (0x00007f83d6c88000)
        libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f83d6c6c000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f83d6f2b000)

やっていることは単純です。lddの実行結果だとlibelfやlibzをリンクしていますが,これはlibbpf側の依存関係によるものです。これらのライブラリはいずれも原則的にどのUbuntuにもインストールされていると思って良いため,ライブラリのバージョンが合う限りにおいては,作られたバイナリファイルだけを別のUbuntuに持っていって実行することが可能というわけです。

著者プロフィール

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

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