diff --git a/AGENTS.md b/AGENTS.md index fa44271..912a576 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -267,7 +267,7 @@ sudo tests/run_e2e.sh --filter dwarf ### BPF Verifier - All loops MUST be bounded (`for _ in 0..CONST`). No `while`, no dynamic bounds. - Map accesses must be bounds-checked. The verifier tracks these statically. -- `shard_lookup()` uses an 8-way static `match` — dynamic array indexing fails verification. +- `shard_lookup()` uses `ArrayOfMaps::get_value()` (from the Brskt/aya `hashmapofmaps-new` branch) which fuses the outer and inner `bpf_map_lookup_elem` into a single call without intermediate struct indirection. Using the two-step approach (`get()` then `Array::get()`) causes verifier state explosion (~10s vs ~400ms). The fused `get_value()` is the required pattern. - The eBPF crate uses extreme optimization (`opt-level=3, lto, codegen-units=1`) even in debug — required for verifier to accept the code. - Adding code to `collect_trace` can push it over the verifier instruction limit. The DWARF inline path (21 iterations × 8 mappings × 16 binary search steps) is near the edge. diff --git a/profile-bee-ebpf/src/lib.rs b/profile-bee-ebpf/src/lib.rs index 0e6cf53..6ff486d 100644 --- a/profile-bee-ebpf/src/lib.rs +++ b/profile-bee-ebpf/src/lib.rs @@ -7,8 +7,8 @@ use aya_ebpf::{ bindings::{bpf_raw_tracepoint_args, pt_regs, BPF_F_USER_STACK}, helpers::{ bpf_get_current_pid_tgid, bpf_get_current_task_btf, bpf_get_smp_processor_id, - bpf_ktime_get_ns, bpf_map_lookup_elem, bpf_probe_read, bpf_probe_read_kernel, - bpf_probe_read_user, bpf_task_pt_regs, + bpf_ktime_get_ns, bpf_probe_read, bpf_probe_read_kernel, bpf_probe_read_user, + bpf_task_pt_regs, }, macros::map, maps::{Array, ArrayOfMaps, HashMap, PerCpuArray, ProgramArray, RingBuf, StackTrace}, @@ -1086,25 +1086,14 @@ unsafe fn try_fp_step(bp: u64) -> Option<(u64, u64)> { /// Look up an UnwindEntry from the array-of-maps by shard_id and index. /// -/// 1. Look up shard_id in the outer UNWIND_SHARDS → get pointer to inner map -/// 2. Look up idx in the inner map via raw bpf_map_lookup_elem -/// -/// Uses the typed ArrayOfMaps for the outer lookup but raw helper for the inner -/// lookup to avoid verifier complexity from the typed Array::get() code path. +/// Uses the fused `get_value()` API which performs both outer and inner +/// `bpf_map_lookup_elem` calls without intermediate struct indirection. +/// This avoids verifier state explosion that occurs when the two lookups +/// are separated by typed wrapper code (MapDef::as_ptr() on the inner map). #[inline(always)] unsafe fn shard_lookup(shard_id: u8, idx: u32) -> Option { - // Step 1: look up the inner map in the outer array-of-maps - let inner_map_ptr = UNWIND_SHARDS.get(shard_id as u32)?; - - // Step 2: look up the entry in the inner map using raw helper - let entry_ptr = bpf_map_lookup_elem( - inner_map_ptr as *const _ as *mut core::ffi::c_void, - &idx as *const u32 as *const core::ffi::c_void, - ); - if entry_ptr.is_null() { - return None; - } - Some(core::ptr::read_unaligned(entry_ptr as *const UnwindEntry)) + let entry: &UnwindEntry = UNWIND_SHARDS.get_value(shard_id as u32, &idx)?; + Some(*entry) } #[inline(always)] diff --git a/profile-bee/bin/profile-bee.rs b/profile-bee/bin/profile-bee.rs index 439dd6c..e7efcfb 100644 --- a/profile-bee/bin/profile-bee.rs +++ b/profile-bee/bin/profile-bee.rs @@ -1,4 +1,4 @@ -use aya::maps::{InnerMap, MapData, RingBuf, StackTraceMap}; +use aya::maps::{MapData, RingBuf, StackTraceMap}; use aya::Ebpf; use clap::Parser; use inferno::flamegraph::{self, Options}; @@ -1191,11 +1191,14 @@ fn apply_dwarf_refresh(bpf: &mut Ebpf, update: DwarfRefreshUpdate) { let map = bpf.map_mut("unwind_shards").ok_or_else(|| { tracing::warn!("DWARF refresh: unwind_shards map not found"); })?; - let mut outer = aya::maps::ArrayOfMaps::try_from(map).map_err(|e| { + let mut outer: aya::maps::ArrayOfMaps< + &mut aya::maps::MapData, + aya::maps::Array, + > = aya::maps::ArrayOfMaps::try_from(map).map_err(|e| { tracing::warn!("DWARF refresh: unwind_shards is not ArrayOfMaps: {}", e); })?; for (shard_id, inner_array) in &created_maps { - if let Err(e) = outer.set(*shard_id as u32, inner_array.fd(), 0) { + if let Err(e) = outer.set(*shard_id as u32, inner_array, 0) { tracing::warn!( "DWARF refresh: failed to insert shard_{} into outer map: {}", shard_id, diff --git a/profile-bee/ebpf-bin/profile-bee.bpf.o b/profile-bee/ebpf-bin/profile-bee.bpf.o index 4074ee3..47d4591 100644 Binary files a/profile-bee/ebpf-bin/profile-bee.bpf.o and b/profile-bee/ebpf-bin/profile-bee.bpf.o differ diff --git a/profile-bee/src/ebpf.rs b/profile-bee/src/ebpf.rs index 9446353..9df5632 100644 --- a/profile-bee/src/ebpf.rs +++ b/profile-bee/src/ebpf.rs @@ -513,21 +513,22 @@ impl EbpfProfiler { let inner_array = create_and_populate_inner_map(shard_id, entries)?; // Get the outer ArrayOfMaps and insert the inner map's FD - let mut outer: aya::maps::ArrayOfMaps<&mut MapData> = aya::maps::ArrayOfMaps::try_from( + let mut outer: aya::maps::ArrayOfMaps< + &mut MapData, + aya::maps::Array, + > = aya::maps::ArrayOfMaps::try_from( self.bpf .map_mut("unwind_shards") .ok_or(anyhow!("unwind_shards map not found"))?, )?; - outer - .set(shard_id as u32, inner_array.fd(), 0) - .map_err(|e| { - anyhow!( - "failed to insert shard_{} into outer ArrayOfMaps: {}", - shard_id, - e - ) - })?; + outer.set(shard_id as u32, &inner_array, 0).map_err(|e| { + anyhow!( + "failed to insert shard_{} into outer ArrayOfMaps: {}", + shard_id, + e + ) + })?; // inner_array is dropped here — the kernel holds a reference to the inner map // via the outer ArrayOfMaps, so the inner map stays alive.