
2026/05/22 1:25
Show HN: BPF プログラムは Go で、C では書かなくて OK
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Gobee ツールは、厳密な Go サブセットを BPF C に変換し、ユーザーランド側向けに型付き Go バインディングを生成することで、実行中のカーネルに対してロードをゲートし、Go から開発の重要なボトルネックを解消します。その最大の利点は安全性であり、ロード時に
bpfvet を実行してホストシステムよりも新しいカーネル機能が必要な場合に即座に失敗させ、ランタイムクラッシュを防ぎます。また、CO-RE、BTF、 verifier-friendly コード生成のために成熟した clang バックエンドと互換性のある読みやすい BPF C コードをエミットし、コンパイラを再構築することなく実現します。
Gobee は診断プロセスを簡素化し、verifier エラーを Go ソースファイル内(例:
counter.go:18:5)に自動的に注釈付けることで、手動の診断パイピングを行う必要性を排除します。本ツールは Solod および Aya などのプロジェクトからインスピレーションを得ており、移植可能な Go パッケージが bpf/ に、カーネルソースと clang アーティファクトが bpf/src/ に配置される清潔なレイアウトを維持しています。各 .bpf.c ファイルには、オフラインでの Gobee 診断に用いられる sourcemap サイドカー(<stem>.bpf.c.map)がペア付けられています。
ツールチェーンは Linux arm64 と amd64 のクロスアーキテクチャビルドをサポートし、XDP、tracepoint、kprobe/kretprobe、uprobe/uretprobe、sock_ops、TC、cgroup_skb、LSM という 8 つのプログラムタイプ、ならびに ringbuf、perf_event_array、storage maps を含む 19 のマップタイプをカバーします。Gobee は
vmlinux.h を含み BPF_CORE_READ を使用することで BTF レディな出力をエミットしますが、clang コンパイルの責任はユーザーに委ねており、Ubuntu 24.04(カーネル 6.x)での実カーネル verifier 受入を含む 4 つのレイヤーにおける CI サポートを提供します。ツールチェーンには、どこでも動作する純粋な Go トランpiler と clang(.bpf.o をコンパイルするために必要)が組み合わさっており、ディストリビューションパッケージまたは macOS 上での brew install llvm で入手可能です。最終的に、MIT ライセンスのこのプロジェクトは、コンパイラの再構築を行わないままシステムレイヤー全体にわたる信頼性の高い CI カバレッジを求めているチームにとって、堅牢で移植可能なソリューションを提供しており、libbpf v1.5.0 ヘルパーセットをカバーする約 200 の型付き Go スタブをエミットし、誤りしやすい文字列ベースのルックアップを置き換えるために、ユーザー定義ヘルパーを静的な __always_inline C 関数として出力します。本文
gobee: Go で記述する eBPF
gobee は、Go の厳密なサブセットを eBPF C コードに変換(トランスパイール)し、ユーザースペース向けの型付きバインディングを生成するツールです。 このツールは成熟した
clang のバックエンドを活用しつつ、カーネル開発における古くからの「C で記述せよ」という制約から解放されます。
特徴と仕組み
1. トランスパイールによるアプローチ
- gobee: Go のサブセットを C に変換し、成熟した clang の BPF バックエンドを活用します。
- Aya (Rust):
に BPF バックエンドを追加するアプローチを採用しています。rustc
2. 生成されるアーティファクト
gobee は以下の 3 つの要素を生成します。
- BPF C コード:
ファイルとして出力されます。.bpf.c - ソースマップ (Sourcemap): Verifier エラーと Go ソースコードを行番号付きでマッピングしたファイル(例:
)。events.bpf.c.map - 型付きバインディング: ユーザースペースで使用するための型定義ファイル(例:
)。bpf/events_bindings.go
3. ユーザースペースとの親和性
- 冗長な文字列検索 (
) を避け、同一の構造体を用いてリングバッファへの直接デコードが可能です。coll.Programs["..."] - 生成された C コードは意図的に可読性を高めており、
が意図しない奇妙なコードを出力しても確認可能に設計されています。gobee
使用例:Tracepoint からのイベント収集
リングバッファ経由で全ての
execve システムコールをストリーミングするトレースポイントの構成例です。
Go コード (入力)
//go:build ignore package main import "github.com/boratanrikulu/gobee/bpf" //bpf:license GPL type Event struct { Pid uint32 Comm [16]byte } var Events = bpf.RingBuf[Event]{ MaxEntries: 4096, } //bpf:section tracepoint/syscalls/sys_enter_execve func OnExec(ctx *bpf.ExecveEnterCtx) bpf.TpReturn { e, ok := Events.Reserve() if !ok { return bpf.TpOk } e.Pid = bpf.GetCurrentPid() bpf.GetTaskComm(&e.Comm) Events.Submit(e) return bpf.TpOk } func main() {}
生成された BPF C コード (出力)
// Code generated by gobee. DO NOT EDIT. #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_core_read.h> char _license[] SEC("license") = "GPL"; struct Event { __u32 Pid; __u8 Comm[16]; }; struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 4096); } Events SEC(".maps"); SEC("tracepoint/syscalls/sys_enter_execve") int OnExec(struct trace_event_raw_sys_enter *ctx) { struct Event *e = bpf_ringbuf_reserve( &Events, sizeof(struct Event), 0); if (!e) { return 0; } e->Pid = (__u32)( bpf_get_current_pid_tgid() >> 32); bpf_get_current_comm(&e->Comm, 16); bpf_ringbuf_submit(e, 0); return 0; }
コマンド:
gobee translate --bindings-dir ./bpf ./bpf/src
他のツールとの比較
| 機能 | gobee | C + clang + bpf2go | Aya (Rust) | bpftrace | BCC |
|---|---|---|---|---|---|
| カーネル側言語 | Go のサブセット | C | Rust | DSL | C |
| ユーザースペース統合 | 型付き Go バインディング | bpf2go | aya-runtime | なし | Python |
| CO-RE 対応 | ✅ clang を介して | ✅ | ✅ LLVM を介して | ✅ | ✅ |
| ヘルパー関数カバレッジ | 200 の型付き Go ラッパー | C で記述必要 | 完全 | 限定的 | C で記述必要 |
| ソースマッピング | ✅ Go ファイル:行:列 | ❌ 生の C コード | ✅ Rust ファイル:行 | ❌ | パーシャル |
| バージョンゲート | ✅ bpfvet を介して | マニュアル | マニュアル | なし | ランタイム |
注記: gobee は既存の C/libbpf ワークフローを置き換えることを意図しておりません。カーネル側・ユーザースペース側・ビルドパイプラインを一つの Go モジュール内で管理したい場合に最適です。
対応状況 (What's supported today)
詳細は docs/status.md を参照してください。
| 項目 | カバレッジ |
|---|---|
| プログラムタイプ | XDP, tracepoint, kprobe/kretprobe, uprobe/uretprobe, sock_ops, TC, cgroup_skb, LSM (全 8 種) |
| マップタイプ | array, hash, lru_hash, per-CPU 変種、bloom_filter, lpm_trie, ringbuf, perf_event_array, prog_array, queue, stack, ストレージ型、devmap/cpumap/xskmap (全 19 種) |
| BPF ヘルパー | libbpf v1.5.0 のヘッダーから自動生成された ~200 の型付き Go スタブ |
| CO-RE | ✅ 自動検出。内核構造体 (, など) と UAPI コンテキストへの対応済み。※ Ubuntu 24.04 (Linux 6.x) で確認済み。旧バージョンは未対応。 |
| BTF レディな出力 | ✅ C コードに BTF 情報を埋め込み、適切なリロケーション情報が含まれます。 ※ は含みません。clang の管理はユーザー責任です。 |
| ユーザー定義ヘルパー | ✅ トップレベルの Go 関数は、静的な C 関数として生成されます。 |
| 型付きバインディング | ✅ , , , などの機能と構造体タイプの再公開に対応。 |
| バージョンゲート | ✅ ローダ () で実行。ホストとプログラムが異なるバージョンの場合、迅速に失敗します。 |
| エラーマッピング | ✅ Verifier エラーに対し、Go ソースコードへの自動アノテーションを実行。 |
| クロスアーキテクチャ | ✅ Linux arm64 + amd64 に対応 |
gobee が行うこと (Summary)
- Go のサブセットを BPF C にトランスパイールし、誤利用をファイル・行・列単位で警告します (
を事前実行)。go/types
の隣に型付きバインディング.bpf.c
を生成します。<Stem>_bindings.go
からの Verifier エラーに対し、Go ソース位置を自動アノテーションします(手動パイプ不要)。LoadAndAssign- ローダ内 (
) でLoad<Stem>
を実行し、カーネルバージョンの不整合を早期に検出します。bpfvet - ~200 のヘルパー・スタブに加え、ユーザー定義ヘルパーも静的関数として生成します。
gobee が行わないこと (Limitations)
- clang の置き換えはしません: CO-RE や BTF などの機能は clang に既に実装されており、再実装のメリットはありません。
- cilium/ebpf の置き換えはしません: 生成されたバインディングは cilium/ebpf と互換性のあるスタックの上で動作します。
- BPF プログラムを隠しません: Go サブセットは BPF C に 1:1 マッピングし、BPF を知らない場合でもマニュアル参照が必要です。
- 自動ビルド・ロードは行いません: コンパイルからロードまでの手順は依然としてユーザーの手動作業(bpf2go と同様)が必要です。
なぜトランスパイールするか?
Go のコンパイラ (gc) には、LLVM ベースの BPF バックエンドが存在しません。rustc と異なり、その追加には多大なコストがかかります。
gobee は C を介して
を再利用することで、以下のメリットを無料で得ています。clang
- 成熟したコード生成
- CO-RE / BTF サポート
- Verifier フriendly な構造
クイックスタート
前提条件
- Linux: ディストリビューションのパッケージから clang (BPF ターゲット) をインストール。
- macOS:
で LLVM/clang をインストールしてください。brew install llvm - Go バイナリ生成環境: arm64 または amd64 の Linux が必要です。
go install github.com/boratanrikulu/gobee/cmd/gobee@latest cd example/helloworld make build # トランスパイル、コンパイル、ビルドのワンステップ sudo ./helloworld eth0
プロジェクト構造例
yourproject/ ├── bpf/ # import 可能な Go パッケージ │ ├── embed_amd64.go # CGO エンベディメント定義 │ ├── embed_arm64.go │ ├── your_bindings.go # gobee が生成したバインディング │ ├── bin/{x86,arm64}/your.bpf.o # コンパイル済みオブジェクトファイル │ └── src/ # clang の入力用ディレクトリ (Go パッケージではない) │ ├── your.go //go:build ignore を持つ BPF ソース │ ├── your.bpf.c # gobee が生成した C コード │ ├── Makefile # アーキテクチャ別のclang コンパイル設定 │ └── vmlinux.h # バインダされた BTF ドンプ ├── main.go # bpf パッケージを import する実装ファイル └── Makefile # 全工程のビルド管理
この構造は、
bpf/ を純粋な Go モジュールとしつつ、ネイティブコード生成物を src/ に隔離することで、クリーンな開発環境を維持します。
リソース
実装例 (Examples)
- example/helloworld: キャンノニカルな XDP パケットカウンタ(BPF 40 行 / ユーザースペース 80 行)。
- example/sysmon: XDP、tracepoint、kprobe を統合。リングバッファ共有と
のデモ。AttachAll
ドキュメント
- docs/design.md: アーキテクチャと設計思想
- docs/go-subset.md: 受け入れられる Go サブセットの言語仕様
- docs/directives.md:
ディレクティブのリファレンス//bpf:* - docs/status.md: サポートマトリックス(真の情報源)
影響を受けたプロジェクト
- Solod: Go から C へのトランスパイールパターン。
- Aya: ergonomics を重視する Rust eBPF フレームワーク。
ライセンス
MIT License Copyright (c) 2026 Bora Tanrikulu me@bora.sh