ebpf-rust について
Ayaを使ってRustで本番向けeBPFプログラムを構築・デバッグするためのスキルです。カーネル側コード、ユーザー空間ローダー、共有マップ、ロギング、Tokio連携を扱います。
主な機能
- 01Aya eBPFプロジェクトの設定
- 02カーネル側とユーザー空間のRustコード
- 03BPFマップの共有
- 04aya-logによる構造化ログ
- 05Tokio連携とイベント読み取り
- 06ロードエラーとベリファイアのデバッグ
活用シーン
- 01AyaでeBPFプロジェクトを始める
- 02カーネルとユーザー空間間でイベントを渡す
- 03BPFプログラムに構造化ログを追加する
- 04Ayaのロードおよびベリファイアエラーを調査する
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill ebpf-rustインストール後、Claude Code で/ebpf-rustを入力して読み込まれたか確認します。
詳細
Rust(Aya)による eBPF
目的
Aya フレームワークを使って Rust で本番環境向けの eBPF プログラムを構築する方法をエージェントに案内します。aya-bpf を使ったカーネル側の BPF コードの記述、aya-log を使った構造化ログ、BPF とユーザー空間間でのマップの共有、async tokio との統合を扱います。
トリガー
- 「Rust で eBPF プログラムを書くにはどうすればよいですか?」
- 「Aya フレームワークを使うにはどうすればよいですか?」
- 「Rust でカーネルとユーザー空間の間で BPF マップを共有するにはどうすればよいですか?」
- 「Rust の BPF プログラムからログを記録するにはどうすればよいですか?」
- 「Aya プログラムの読み込みに失敗します。デバッグするにはどうすればよいですか?」
- 「eBPF プログラムを tokio と統合するにはどうすればよいですか?」
ワークフロー
1. プロジェクトのセットアップ
# Install aya-tool (generates bindings from vmlinux BTF)
cargo install aya-tool
# Create new Aya project from template
cargo install cargo-generate
cargo generate https://github.com/aya-rs/aya-template
# Workspace layout (generated)
# my-ebpf/
# ├── my-ebpf-ebpf/ <- kernel-side crate (target: bpf)
# ├── my-ebpf/ <- userspace crate (runs on host)
# └── xtask/ <- build helper (cargo xtask build/run)
# Build both sides
cargo xtask build-ebpf # builds BPF object
cargo xtask run # builds + runs with sudo
2. カーネル側の BPF プログラム
// my-ebpf-ebpf/src/main.rs
#![no_std]
#![no_main]
use aya_bpf::{
macros::{map, tracepoint},
maps::HashMap,
programs::TracePointContext,
helpers::bpf_get_current_pid_tgid,
};
use aya_log_ebpf::info;
#[map]
static CALL_COUNT: HashMap<u32, u64> = HashMap::with_max_entries(1024, 0);
#[tracepoint]
pub fn trace_read(ctx: TracePointContext) -> u32 {
let pid = (bpf_get_current_pid_tgid() >> 32) as u32;
// Lookup or insert
match unsafe { CALL_COUNT.get(&pid) } {
Some(count) => {
let _ = CALL_COUNT.insert(&pid, &(count + 1), 0);
}
None => {
let _ = CALL_COUNT.insert(&pid, &1u64, 0);
}
}
info!(&ctx, "read() called by pid {}", pid);
0
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
}
3. tokio を使ったユーザー空間のローダー
// my-ebpf/src/main.rs
use aya::{Bpf, programs::TracePoint, maps::HashMap};
use aya_log::BpfLogger;
use tokio::signal;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Load compiled BPF object (embedded at build time)
let mut bpf = Bpf::load(include_bytes_aligned!(
"../../target/bpf/my-ebpf-ebpf.bpf.o"
))?;
// Initialize structured logging from BPF programs
BpfLogger::init(&mut bpf)?;
// Attach tracepoint
let program: &mut TracePoint = bpf.program_mut("trace_read").unwrap().try_into()?;
program.load()?;
program.attach("syscalls", "sys_enter_read")?;
// Read from shared map
let map: HashMap<_, u32, u64> = HashMap::try_from(bpf.map("CALL_COUNT").unwrap())?;
// Wait for Ctrl+C
signal::ctrl_c().await?;
// Print final counts
for (pid, count) in map.iter().filter_map(|r| r.ok()) {
println!("PID {}: {} reads", pid, count);
}
Ok(())
}
4. Aya のマップ型
use aya_bpf::maps::{HashMap, Array, RingBuf, PerfEventArray, LruHashMap};
// Hash map
#[map]
static MY_MAP: HashMap<u32, u64> = HashMap::with_max_entries(1024, 0);
// Ring buffer (preferred for events)
#[map]
static EVENTS: RingBuf = RingBuf::with_byte_size(256 * 1024, 0);
// LRU hash (connection tracking)
#[map]
static CONNS: LruHashMap<u32, ConnInfo> = LruHashMap::with_max_entries(10000, 0);
// Sending events via RingBuf (kernel side)
if let Ok(mut entry) = unsafe { EVENTS.reserve::<MyEvent>(0) } {
entry.write(MyEvent { pid, ts });
entry.submit(0);
}
// Reading RingBuf events (userspace)
use aya::maps::RingBuf;
use tokio::io::unix::AsyncFd;
let ring = RingBuf::try_from(bpf.take_map("EVENTS").unwrap())?;
let mut ring = AsyncFd::new(ring)?;
loop {
let mut guard = ring.readable_mut().await?;
let rb = guard.get_inner_mut();
while let Some(item) = rb.next() {
let event: &MyEvent = unsafe { &*(item.as_ptr() as *const MyEvent) };
println!("Event from PID {}", event.pid);
}
guard.clear_ready();
}
5. サポートされているプログラム型
| Aya マクロ | プログラム型 | アタッチ先 |
|---|---|---|
#[tracepoint] | トレースポイント | "syscalls", "sys_enter_read" |
#[kprobe] | kprobe | 関数名 |
#[kretprobe] | kretprobe | 関数名 |
#[uprobe] | uprobe | ユーザー空間のバイナリ + オフセット |
#[xdp] | XDP | ネットワークインターフェース |
#[tc] | TC(トラフィック制御) | ネットデバイス + 方向 |
#[socket_filter] | ソケットフィルター | raw ソケット fd |
#[perf_event] | Perf イベント | perf_event fd |
#[lsm] | LSM フック | セキュリティフック名 |
#[sk_msg] | Sockmap | ソケットマップ |
6. カーネル型バインディングの生成
# Generate bindings from running kernel's BTF
aya-tool generate task_struct > src/vmlinux.rs
# Or use btf_type_tag and CO-RE
# aya-bpf supports CO-RE via bpf_core_read! macro
// CO-RE field access in Aya
use aya_bpf::helpers::bpf_core_read;
let dport: u16 = unsafe {
bpf_core_read!(sk, __sk_common.skc_dport)?
};
7. 読み込み失敗のデバッグ
# Check verifier errors (Aya surfaces them as Rust errors)
# Run with RUST_LOG=debug for verbose output
RUST_LOG=debug cargo xtask run 2>&1 | grep -A 20 "verifier"
# Check BTF info
bpftool btf dump file /sys/kernel/btf/vmlinux | grep task_struct
# Inspect loaded programs after load
bpftool prog list
bpftool prog dump xlated name trace_read
| エラー | 原因 | 対処方法 |
|---|---|---|
invalid mem access | ポインターの参照範囲に制限がない | 読み取り前に null チェックを追加する |
Type not found | カーネルとの BTF の不一致 | vmlinux バインディングを再生成する |
Permission denied | CAP_BPF または CAP_SYS_ADMIN がない | sudo で実行するか、ケイパビリティを設定する |
map already exists | マップがピン留めされている、または名前が重複している | マップのピン留めを解除するか、名前を変更する |
関連スキル
- libbpf を使った C ベースの eBPF には
skills/observability/ebpfを使用します - ユーザー空間で使用する tokio の非同期パターンには
skills/rust/rust-async-internalsを使用します - BPF ヘルパーでの unsafe コードのパターンには
skills/rust/rust-unsafeを使用します
コメント
Markdown 対応