Upgrade to Pro — share decks privately, control downloads, hide ads and more …

eBPF を拡張したい話 -カーネルモジュールで KFunc を追加する-

Avatar for KANAI Masumi KANAI Masumi
September 05, 2026

eBPF を拡張したい話 -カーネルモジュールで KFunc を追加する-

2024/12/06 に eBPF Japan Meetup #2 で発表する機会をいただきました。当時の資料です。
https://ebpf.connpass.com/event/333641/

試験コードはこちら。
https://github.com/masu-mi/ext-kfuncs

Avatar for KANAI Masumi

KANAI Masumi

September 05, 2026

More Decks by KANAI Masumi

Other Decks in Programming

Transcript

  1. 自己紹介 • • 金井真澄 (@masu_mi, masu-mi) BB akura Networks, Inc.

    (2021年2月 中途入社) • いま: モバイルコアと周辺 管理システム 2
  2. 会社紹介 持分比率 100 % 49 % 51 % 2019.08~ I

    事業 ネットワーク ソリューション ソフトウェア事業 ネットワーククラウド サービス 開発 ソフトウェア ソリューション 3
  3. 背景 • • • • • • eBPF 安全にカーネルを拡張できる 元々、危険だけど拡張できるカーネルモジュールという仕組みがある

    カーネルモジュールを使って少し危険を冒せ eBPF を拡張できるか? どういう拡張が現時点でできる か? どんな時に嬉しいか? どういう拡張が今後ほしくなりそうか? > BPF is a highly flexible and efficient virtual machine-like construct in the Linux kernel allowing to execute bytecode at various hook points in a safe manner. ref. BPF and DP eference Guide 4
  4. 今日 • • • 内容 eBPF と KFuncs 簡単な紹介 カーネルモジュールで

    KFuncs を追加する流れ ほか 拡張箇所について整理と考察 少し古いですが Linux kernel バージョン ます v6.2.16 で説明します。一部で 言及した上で v6.12.1 を抜粋してい 5
  5. eBPF プログラムからカーネルを利用する KFuncs ◦ ◦ ◦ • Helper functions ◦

    ◦ ◦ • eBPF プログラム向けにアノテーションされたカーネル内 関数 インタフェース 不安定でカーネルバージョン跨いで一致すると 限らない KFuncs インデックスページ で探せる eBPF プログラムプログラムから呼び出せるカーネル内 関数 インタフェース 安定してることが保証されている include/uapi/linux/bpf.h で定義されている Maps ◦ ◦ Process データ構造いろいろ(map types) eBPF プログラム, ユーザー空間から利用できる yscall Linux Kernel • eBPF Maps eBPF Helper eBPF KFuncs 8
  6. カーネルモジュール • カーネル空間に動的にロード・アンロードできる ELF ファイル ◦ • • • .modinfo

    など特別なセグメントをいくつか持っている init_module(2), finit_module(2) でロードする delete_module(2) でアンロードする lsmod コマンドで何が読み込まれているか確認するとイメージ掴める 9
  7. 補足: B F 簡易 説明 B F BPF プログラム、マップ向け デバッグデータを格納するフォーマット

    erifier やローダが挙動や互換性を評価するために用いられる • 型 • メモリレイアウト • シンボル • カーネルシンボル ref. https://docs.kernel.org/bpf/btf.html 12
  8. やってみた: モジュール KFunc 定義 抜粋 __bpf_kfunc int bpf_hello_const(int id__k) {

    printk(KERN_ALERT "hello_const from eBPF (id=%d)", id__k); return 0; } BTF_SET8_START(bpf_task_set) BTF_ID_FLAGS(func, bpf_hello_const) BTF_SET8_END(bpf_task_set) 13
  9. やってみた: モジュール KFunc 定義 抜粋 __bpf_kfunc int bpf_hello_const(int id__k) {

    printk(KERN_ALERT "hello_const from eBPF (id=%d)", id__k); return 0; } KFuncs 定義箇所 __bpf_kfunc マクロでインライン関数最適化される を防ぐ BTF_SET8_START(bpf_task_set) パラメータアノテーション: 変数名 接尾辞で erifier に制限を伝える BTF_ID_FLAGS(func, bpf_hello_const) B F に登録される型(タイプ)で ない BTF_SET8_END(bpf_task_set) v6.2.16 で __sz, __k, __ign, __alloc (6.12.1までにだいぶ増えている) ref. v6.12.1/source/kernel/bpf/verifier.c#L11001-L11070 14
  10. やってみた: モジュール KFunc 定義 抜粋 __bpf_kfunc int bpf_hello_const(int id__k) {

    KFuncs 定義箇所 printk(KERN_ALERT "hello_const from eBPF (id=%d)", id__k); 関数アノテーション: 引数・戻り値 扱い、全体的な挙動をフラグで渡す return 0; e.g. B F_ID_FLAG (func, bpf_hello, KF_ LEEPABLE, KF_DE C I E) KF_DE} C I E -> CAP_ _BOO ref. v6.12.1/source/include/linux/btf.h#L18-L77 BTF_SET8_START(bpf_task_set) BTF_ID_FLAGS(func, bpf_hello_const) BTF_SET8_END(bpf_task_set) 15
  11. やってみた: モジュール KFunc 登録 抜粋 static const struct btf_kfunc_id_set bpf_task_kfunc_set

    = { .owner = THIS_MODULE, .set = &bpf_task_set, }; static int mymodule_init(void) { return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set); } module_init(mymodule_init); 16
  12. やってみた: モジュール KFunc 登録 抜粋 static const struct btf_kfunc_id_set bpf_task_kfunc_set

    = { .owner = THIS_MODULE, .set = &bpf_task_set, }; int mymodule_init(void) { KFuncs static B F をカーネルモジュールと紐づける return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, .owner に HI _MOD LE マクロでカーネルモジュールへ &bpf_task_kfunc_set); .set に 先ほど作成した B F エントリーを持たせる 参照が入る } module_init(mymodule_init); 17
  13. やってみた: モジュール KFunc 登録 抜粋 static const struct btf_kfunc_id_set bpf_task_kfunc_set

    = { KFuncs とカーネルモジュール 紐付けをプログラムタイプ毎に登録する .owner = THIS_MODULE, .set = &bpf_task_set, こ }; 例で BPF_P OG_ PE_ ACING に向けて公開している ref. v6.12.1/source/include/uapi/linux/bpf.h#L1024-L1059 static int mymodule_init(void) { return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set); } module_init(mymodule_init); 18
  14. やってみた: モジュール KFunc 登録 抜粋 static const struct btf_kfunc_id_set bpf_task_kfunc_set

    = { .owner = THIS_MODULE, .set = &bpf_task_set, }; 登録作業をカーネルモジュール 読み込み時に実行させる static int mymodule_init(void) { module_init() マクロを使う return 引数 register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, シンボルに init_module というエイリアスを与える 初期化処理で参照するよう .init から始まるセグメントに配置する &bpf_task_kfunc_set); } module_init(mymodule_init); 19
  15. 補足: カーネル モジュール管理 • カーネルモジュール ◦ • エントリー カーネルモジュール ◦

    ◦ • modules 連結リスト modules に登録される struct module B F 連結リスト btf_modules に登録される btf_modules エントリー struct btf_module module へ 参照も持ってる eBPF ローダー 以下を参照する struct btf_module { ◦ ◦ struct list_head list; /proc/kallsyms /sys/kernel/btf/<mod_name> struct module *module; struct btf *btf; struct bin_attribute *sysfs_attr; int flags; }; 20
  16. 補足: hello B F $ sudo bpftool btf dump file

    /sys/kernel/btf/hello [127539] CONST '(anon)' type_id=49828 [127540] ARRAY '(anon)' type_id=127539 index_type_id=21 nr_elems=5 [127541] CONST '(anon)' type_id=127540 [127542] FUNC 'mymodule_exit' type_id=122 linkage=static [127543] FUNC 'mymodule_init' type_id=120 linkage=static [127544] FUNC_PROTO '(anon)' ret_type_id=21 vlen=1 'id__k' type_id=21 [127545] FUNC 'bpf_hello_const' type_id=127544 linkage=static [127546] FUNC_PROTO '(anon)' ret_type_id=21 vlen=1 'id' type_id=21 [127547] FUNC 'bpf_hello' type_id=127546 linkage=static 21
  17. 補足: hello B F $ sudo bpftool btf dump file

    /sys/kernel/btf/hello [127539] CONST '(anon)' type_id=49828 [127540] ARRAY '(anon)' type_id=127539 index_type_id=21 nr_elems=5 [127541] CONST '(anon)' type_id=127540 [127542] FUNC 'mymodule_exit' type_id=122 linkage=static [127543] FUNC 'mymodule_init' type_id=120 linkage=static id__k と引数名が保存されている [127544] FUNC_PROTO '(anon)' ret_type_id=21 vlen=1 'id__k' type_id=21 [127545] FUNC 'bpf_hello_const' type_id=127544 linkage=static [127546] FUNC_PROTO '(anon)' ret_type_id=21 vlen=1 'id' type_id=21 [127547] FUNC 'bpf_hello' type_id=127546 linkage=static 22
  18. やってみた: eBPF extern int bpf_hello(int id__k) __ksym; extern int bpf_hello_const(int

    id) __ksym; SEC("tp_btf/sys_enter") int BPF_PROG(sys_enter, struct pt_regs *regs, long syscall_id) { bpf_hello(syscall_id); bpf_hello_const(200); return 0; } 23
  19. やってみた: eBPF extern int bpf_hello(int id__k) __ksym; extern int bpf_hello_const(int

    id) __ksym; SEC("tp_btf/sys_enter") int BPF_PROG(sys_enter, struct pt_regs *regs, long syscall_id) { bpf_hello(syscall_id); bpf_hello_const(200); __ksym: 該当シンボルがカーネル側にあることを伝えるマクロ .B F セグメントに格納している return 0; DA} A EC .ksyms に対象識別子 タイプIDが登録される 登録されるタイプIDに 識別子も登録される 24
  20. やってみた: eBPF extern int bpf_hello(int id__k) __ksym; extern int bpf_hello_const(int

    id) __ksym; SEC("tp_btf/sys_enter") int BPF_PROG(sys_enter, struct pt_regs *regs, long syscall_id) { bpf_hello(syscall_id); アノテーションを一致させる必要 bpf_hello_const(200); ない ただ 変数名であって識別子 B F に登録されない return 0; } erifier 検証で カーネルモジュール B F を利用する 25
  21. 補足: bpf_bpfel.o B F(抜粋) $ bpftool btf dump file ./bpf_bpfel.o

    [6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1 '(anon)' type_id=4 [7] FUNC 'bpf_hello' type_id=6 linkage=extern [8] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1 '(anon)' type_id=4 [9] FUNC 'bpf_hello_const' type_id=8 linkage=extern .... [14] DATASEC '.ksyms' size=0 vlen=2 type_id=7 offset=0 size=0 (FUNC 'bpf_hello') type_id=9 offset=0 size=0 (FUNC 'bpf_hello_const') [15] DATASEC 'license' size=0 vlen=1 type_id=13 offset=0 size=13 (VAR '__license') 26
  22. 補足: bpf_bpfel.o B F(抜粋) $ bpftool btf dump file ./bpf_bpfel.o

    [6] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1 '(anon)' type_id=4 [7] FUNC 'bpf_hello' type_id=6 linkage=extern [8] FUNC_PROTO '(anon)' ret_type_id=4 vlen=1 '(anon)' type_id=4 [9] FUNC 'bpf_hello_const' type_id=8 linkage=extern .... [14] DATASEC '.ksyms' size=0 vlen=2 type_id=7 offset=0 size=0 (FUNC 'bpf_hello') type_id=9 offset=0 size=0 (FUNC 'bpf_hello_const') [15] DATASEC 'license' size=0 vlen=1 type_id=13 offset=0 size=13 (VAR '__license') 27
  23. やってみた: ユーザープログラム spec, err := loadBpf() if err := spec.RewriteConstants(map[string]interface{}{});

    err != nil { panic(err) } objs := &bpfObjects{} if err := spec.LoadAndAssign(objs, nil); err != nil { panic(err) } tracingLink, err := link.AttachTracing(link.TracingOptions{ Program: objs.bpfPrograms.SysEnter, }) defer tracingLink.Close() 28
  24. やってみた: ユーザープログラム spec, err := loadBpf() if err := spec.RewriteConstants(map[string]interface{}{});

    err != nil { panic(err) 主に ELF ファイル読み込みをしてる } - .B F.ext セクションから関数 B Fを取得 objs := &bpfObjects{} - .B F セクション .ksyms テーブルでカーネル内シンボル type_id 取得 if err := spec.LoadAndAssign(objs, nil); err != nil { - .symtab セクションなど(section_type = 2) から利用シンボルを取得 panic(err) } tracingLink, err := link.AttachTracing(link.TracingOptions{ Program: objs.bpfPrograms.SysEnter, }) defer tracingLink.Close() 29
  25. 補足: .B F/.ksyms から type_id 集める err := ec.btf.TypeByName(".ksyms", &ds)

    // .. for _, v := range ds.Vars { switch t := v.Type.(type) { case *btf.Func: ec.kfuncs[t.TypeName()] = t case *btf.Var: ec.ksyms[t.TypeName()] = struct{}{} default: return fmt.Errorf("unexpected variable type in .ksyms: %T", v) } } ref. loadKsyms ection() in cilium/ebpf 30
  26. やってみた: ユーザープログラム spec, err := loadBpf() if err := spec.RewriteConstants(map[string]interface{}{});

    err != nil { panic(err) } objs := &bpfObjects{} if err := spec.LoadAndAssign(objs, nil); err != nil { panic(err) } 主にプログラム ロードと下処理 tracingLink, err := link.AttachTracing(link.TracingOptions{ - シンボルとモジュール 対応 Program: objs.bpfPrograms.SysEnter, - /proc/kallsyms からカーネル内シンボル 所有モジュール名を特定 - B}) F 取得 - /sys/kernel/btf/<module_name> を open(2) defer tracingLink.Close() - リンク - fixupKfuncs() でバイト命令 書き換え 31
  27. 補足: kallsyms からモジュール名を取得 func KernelModule(fn string) (string, error) { //

    .. f, err := os.Open("/proc/kallsyms") if err != nil { return "", err } // .. defer f.Close() kmods, err = loadKernelModuleMapping(f) 下 ような行を見つけて特定してる 0000000000000000 t bpf_hello_const [hello] ref. ksymalls.KernelModule() in cilium/ebpf 32
  28. 補足: /sys/kernel/btf/* をopen func loadKernelModuleSpec(module string, base *Spec) (*Spec, error)

    { dir, file := filepath.Split(module) if dir != "" || filepath.Ext(file) != "" { return nil, fmt.Errorf("invalid module name %q", module) } fh, err := os.Open(filepath.Join("/sys/kernel/btf", module)) ref. ksymalls.KernelModule() in cilium/ebpf 33
  29. 補足: オペランド書き換え idx, err := fdArray.add(module) // .. if err

    := btf.CheckTypeCompatibility(kfm.Func.Type, target.(*btf.Func).Type); err != nil { return nil, &incompatibleKfuncError{kfm.Func.Name, err} } // .. id, err := spec.TypeID(target) // .. ins.Constant = int64(id) ins.Offset = int16(idx) - id: B F/type_id idx: カーネルモジュール B F fdリスト offset - fd リストへ ポインタ カーネルに渡される ref. ebpf/linker.go fixupKfuncs() in cilium/ebpf 34
  30. 補足: erifier 動き • • B F を用いて KFuncs 呼び出しを検証する

    erifier が動く時点で cilium/ebpf B F をロードしてる ◦ CALL: call helper function by B F ID ▪ offset に fd 配列 インデックスが含まれる ▪ const に btf_id が入っている 35
  31. 補足: erifier 動き • add_kfunc_call() ◦ ◦ • check_kfunc_call() ◦

    ◦ • オペランド offset を使いopen(2)されたカーネルモジュール B F をメタ情報に格納 シンボル名からアドレスを取得して __bpf_call_base と 相対アドレスを計算 __k など アノテーションを検証する check_kfunc_args() で KF_ ELEA E など フラグを検証する fixup_kfunc_call() ◦ 検証後に imm 置き換えをしてる • こ 後ろで JI に入っていく ref. • • • add_kfunc_call() __bpf_call_base check_kfunc_call() 37
  32. 拡張できそうな箇所を整理: 呼び出し対象 KFuncs Helper functions Maps Process yscall Linux Kernel

    • • • • • 状況的に KFuncs 以外 提供されているAPI ◦ ◦ ◦ eBPF Maps eBPF Helper eBPF KFuncs 追加不可 register_btf_kfunc_id_set register_btf_fmodret_id_set register_btf_id_dtor_kfuncs ref. include/linux/btf.h (v6.12.1) 40
  33. KFuncs を追加して嬉しい • 制限 ◦ • 使えない (独自にホワイトリストで管理している) B F

    非公開カーネル関数を利用できる 命令数 ◦ • BCC で 機能追加 ◦ • か? 大きなループ処理をカーネルで実行 Maps ◦ ◦ ◦ 緩和 代わり 現状 Maps を動的に追加する仕組み ない ほしい場合あり ▪ キャッシュ戦略を変更するとか 難しい ▪ リンクを cilium/ebpf, libbpf が扱わない ▪ 型 管理(K, ) ▪ ユーザースペースと 通信まわり 準備が煩雑 41
  34. 今日 • カーネルモジュールで eBPF 向けに KFuncs を追加できる ◦ • 追加

    できない (v6.12.0 時点でも) as 非公開カーネル関数 ラッパー as Maps 追加 代替(かなり辛い) for eBPF プログラム 命令数制約 ありそう 緩和 カーネルモジュールで eBPF を拡張する ◦ ◦ ◦ • 一方で、Helper funcs, Maps カーネルモジュールで eBPF を拡張する用途 ◦ ◦ ◦ • ふりかえり 慎重に クラッシュ怖いし ビルド ロード済み環境 or B F からヘッダファイル生成 Maps 代替として 可能だけど非常に不便 ▪ K, 型 取り扱い、初期化問題 感想 ◦ ◦ 安全にカーネル拡張できる eBPF すごい カーネル本体に欲しい機構がありそうだ ▪ Maps 動的追加 42
  35. Appx.: モジュール登録 add_unformed_module() &modules にカーネルモジュールを登録している prepare_coming_module() &btf_modules に B F

    テーブルを登録している ref. v6.2.16 add_unformed_module() prepare_comming_module() btf_module_notify() 44
  36. Appx.: 再掲 モジュール KFunc 登録 static const struct btf_kfunc_id_set bpf_task_kfunc_set

    = { KFuncs とカーネルモジュール 紐付けをプログラムタイプ毎に登録する .owner = THIS_MODULE, .set = &bpf_task_set, }; こ 例で BPF_P OG_ PE_ ACING に向けて公開している ref. v6.12.1/source/include/uapi/linux/bpf.h#L1024-L1059 static int mymodule_init(void) { return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set); } module_init(mymodule_init); 46
  37. Appx.: 登録可能プログラムタイプ v6.2.12 static int bpf_prog_type_to_kfunc_hook(enum bpf_prog_type prog_type) { switch

    (prog_type) { case BPF_PROG_TYPE_UNSPEC: return BTF_KFUNC_HOOK_COMMON; case BPF_PROG_TYPE_XDP: return BTF_KFUNC_HOOK_XDP; case BPF_PROG_TYPE_SCHED_CLS: return BTF_KFUNC_HOOK_TC; case BPF_PROG_TYPE_STRUCT_OPS: return BTF_KFUNC_HOOK_STRUCT_OPS; case BPF_PROG_TYPE_TRACING: case BPF_PROG_TYPE_LSM: return BTF_KFUNC_HOOK_TRACING; case BPF_PROG_TYPE_SYSCALL: return BTF_KFUNC_HOOK_SYSCALL; default: return BTF_KFUNC_HOOK_MAX; } } ref. - kernel/bpf/btf.c (v6.2.16) kernel/bpf/btf.c (v6.12.1) 新しい みたけれ ここ 48