Skip to main content

ghostscope_loader/
lib.rs

1//! GhostScope eBPF Loader
2//!
3//! This crate provides the `GhostScopeLoader` which manages the lifecycle of eBPF programs:
4//! - Loading eBPF bytecode into the kernel
5//! - Attaching/detaching uprobes to target binaries
6//! - Reading trace events from RingBuf or PerfEventArray
7//! - Managing BPF maps for process module offsets
8//!
9//! ## Architecture
10//!
11//! The loader supports two event output mechanisms:
12//! - **RingBuf**: Modern kernel (>= 5.8) continuous byte stream
13//! - **PerfEventArray**: Legacy kernel (< 5.8) per-CPU independent events
14//!
15//! Event parsing is handled by `ghostscope_protocol::StreamingTraceParser` which
16//! adapts to the event source type automatically.
17
18use aya::{
19    maps::{perf::PerfEventArray, MapData, RingBuf},
20    programs::{uprobe::UProbeLinkId, ProgramError, UProbe},
21    Ebpf, EbpfLoader, VerifierLogLevel,
22};
23use ghostscope_protocol::{ParsedTraceEvent, StreamingTraceParser, TraceContext};
24use log::log_enabled;
25use log::Level as LogLevel;
26use std::convert::TryInto;
27use std::future::poll_fn;
28use std::os::unix::io::AsRawFd;
29use std::os::unix::io::RawFd;
30use std::path::Path;
31use std::task::Poll;
32use tokio::io::unix::AsyncFd;
33use tokio::io::Interest;
34use tracing::{debug, error, info, warn};
35
36// Export kernel capabilities detection
37mod kernel_caps;
38pub use kernel_caps::{KernelCapabilities, KernelCapabilityError};
39
40// Export error types
41mod error;
42pub use error::{LoaderError, Result};
43
44// Internal uprobe module
45mod uprobe;
46use uprobe::UprobeAttachmentParams;
47
48// Use shared map types from ghostscope-process
49use ghostscope_process::pinned_bpf_maps::{
50    bpffs_mount_hint_for_pin_path, pid_aliases_pin_path, proc_offsets_pin_dir,
51    proc_offsets_pin_path,
52};
53
54/// Event output map type wrapper
55enum EventMap {
56    RingBuf(RingBuf<MapData>),
57    PerfEventArray {
58        _map: PerfEventArray<MapData>,
59        cpu_buffers: Vec<PerfEventCpuBuffer>,
60    },
61}
62
63#[derive(Clone, Copy, Debug)]
64struct PerfBufferFd(RawFd);
65
66impl AsRawFd for PerfBufferFd {
67    fn as_raw_fd(&self) -> RawFd {
68        self.0
69    }
70}
71
72struct PerfEventCpuBuffer {
73    cpu_id: u32,
74    buffer: aya::maps::perf::PerfEventArrayBuffer<MapData>,
75    readiness: AsyncFd<PerfBufferFd>,
76}
77
78/// Compatibility shim that mimics Aya's newer attach location helper so we can keep
79/// a single call-site regardless of which `UProbe::attach` signature we compile against.
80enum UProbeAttachLocation<'a> {
81    AbsoluteOffset(u64),
82    Function(&'a str),
83}
84
85impl<'a> UProbeAttachLocation<'a> {
86    fn attach<T: AsRef<Path>>(
87        self,
88        program: &mut UProbe,
89        target: T,
90        pid: Option<i32>,
91    ) -> std::result::Result<UProbeLinkId, ProgramError> {
92        match self {
93            Self::AbsoluteOffset(offset) => program.attach(None, offset, target, pid),
94            Self::Function(fn_name) => program.attach(Some(fn_name), 0, target, pid),
95        }
96    }
97}
98
99pub fn hello() -> String {
100    format!("Loader: {}", ghostscope_compiler::hello())
101}
102
103/// Main eBPF program loader and manager
104///
105/// Manages the lifecycle of eBPF programs and provides methods for:
106/// - Loading eBPF bytecode
107/// - Attaching/detaching uprobes
108/// - Reading trace events
109/// - Managing BPF maps
110pub struct GhostScopeLoader {
111    /// Loaded eBPF program
112    bpf: Ebpf,
113    /// Event output map (RingBuf or PerfEventArray)
114    event_map: Option<EventMap>,
115    /// Active uprobe link
116    uprobe_link: Option<UProbeLinkId>,
117    /// Stored parameters for re-attaching uprobe
118    attachment_params: Option<UprobeAttachmentParams>,
119    /// Streaming parser for trace events
120    parser: StreamingTraceParser,
121    /// String table and metadata for parsing trace events
122    trace_context: Option<TraceContext>,
123    /// Optional override for PerfEventArray page count (per CPU buffer size in pages)
124    perf_page_count: Option<usize>,
125}
126
127impl std::fmt::Debug for GhostScopeLoader {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        f.debug_struct("GhostScopeLoader")
130            .field("bpf", &"<eBPF object>")
131            .field("event_map", &self.event_map.is_some())
132            .field("uprobe_attached", &self.uprobe_link.is_some())
133            .field("attachment_params", &self.attachment_params.is_some())
134            .finish()
135    }
136}
137
138impl GhostScopeLoader {
139    // ============================================================================
140    // Lifecycle Management
141    // ============================================================================
142
143    /// Create a new loader instance from eBPF bytecode
144    pub fn new(bytecode: &[u8]) -> Result<Self> {
145        info!(
146            "Loading eBPF program from bytecode ({} bytes)",
147            bytecode.len()
148        );
149
150        // Enforce: proc_module_offsets must be provided as a pinned global map by the process layer
151        let pin_path = proc_offsets_pin_path()
152            .map_err(|e| LoaderError::Generic(format!("Failed to resolve pinned map path: {e}")))?;
153        if !pin_path.exists() {
154            let hint = bpffs_mount_hint_for_pin_path(&pin_path)
155                .map(|hint| format!(" {hint}"))
156                .unwrap_or_default();
157            return Err(LoaderError::Generic(format!(
158                "Pinned map '{}' not found. Please call ghostscope-process to create it first.{}",
159                pin_path.display(),
160                hint
161            )));
162        }
163        let alias_pin_path = pid_aliases_pin_path().map_err(|e| {
164            LoaderError::Generic(format!("Failed to resolve pinned alias map path: {e}"))
165        })?;
166        if !alias_pin_path.exists() {
167            let hint = bpffs_mount_hint_for_pin_path(&alias_pin_path)
168                .map(|hint| format!(" {hint}"))
169                .unwrap_or_default();
170            return Err(LoaderError::Generic(format!(
171                "Pinned map '{}' not found. Please call ghostscope-process to create it first.{}",
172                alias_pin_path.display(),
173                hint
174            )));
175        }
176
177        let mut loader = EbpfLoader::new();
178        let use_verbose = cfg!(debug_assertions)
179            || log_enabled!(LogLevel::Trace)
180            || log_enabled!(LogLevel::Debug);
181        if use_verbose {
182            loader.verifier_log_level(VerifierLogLevel::VERBOSE | VerifierLogLevel::STATS);
183            tracing::info!("BPF verifier logs: VERBOSE (debug build/log)");
184        } else {
185            loader.verifier_log_level(VerifierLogLevel::DEBUG | VerifierLogLevel::STATS);
186            tracing::info!("BPF verifier logs: DEBUG (release/info)");
187        }
188        // Configure Aya loader to reuse pinned maps by name under our per-process pin directory.
189        // This makes @proc_module_offsets in the eBPF object bind to the already pinned map
190        // created by ghostscope-process instead of creating a new private map.
191        let pin_dir = proc_offsets_pin_dir().map_err(|e| {
192            LoaderError::Generic(format!("Failed to resolve pinned map directory: {e}"))
193        })?;
194        if pin_dir.exists() {
195            loader.map_pin_path(&pin_dir);
196            tracing::info!(
197                "Configured map pin directory for reuse: {}",
198                pin_dir.display()
199            );
200        }
201        match loader.load(bytecode) {
202            Ok(bpf) => {
203                info!("Successfully loaded eBPF program");
204                Ok(Self {
205                    bpf,
206                    event_map: None,
207                    uprobe_link: None,
208                    attachment_params: None,
209                    parser: StreamingTraceParser::new(),
210                    trace_context: None,
211                    perf_page_count: None,
212                })
213            }
214            Err(e) => {
215                error!("Failed to load BPF program: {:?}", e);
216                // Try to provide more specific error information
217                match &e {
218                    aya::EbpfError::ParseError(parse_err) => {
219                        error!("Parse error details: {:?}", parse_err);
220                    }
221                    aya::EbpfError::BtfError(btf_err) => {
222                        error!("BTF error details: {:?}", btf_err);
223                    }
224                    _ => {
225                        error!("Other BPF error: {:?}", e);
226                    }
227                }
228                Err(LoaderError::Aya(e))
229            }
230        }
231    }
232
233    // ============================================================================
234    // Uprobe Management
235    // ============================================================================
236
237    /// Attach to a uprobe at the specified function offset
238    pub fn attach_uprobe(
239        &mut self,
240        target_binary: &str,
241        function_name: &str,
242        offset: Option<u64>,
243        pid: Option<i32>,
244    ) -> Result<()> {
245        self.attach_uprobe_with_program_name(target_binary, function_name, offset, pid, None)
246    }
247
248    /// Set PerfEventArray page count override (applies when using Perf backend)
249    pub fn set_perf_page_count(&mut self, pages: u32) {
250        self.perf_page_count = Some(pages as usize);
251    }
252
253    /// Attach to a uprobe with a specific eBPF program name
254    pub fn attach_uprobe_with_program_name(
255        &mut self,
256        target_binary: &str,
257        function_name: &str,
258        offset: Option<u64>,
259        pid: Option<i32>,
260        program_name: Option<&str>,
261    ) -> Result<()> {
262        info!("attach_uprobe called with offset: {:?}", offset);
263        if let Some(offset) = offset {
264            info!(
265                "Using offset-based attachment: {} at 0x{:x} ({}) (pid: {:?})",
266                target_binary, offset, function_name, pid
267            );
268        } else {
269            info!(
270                "Using function name-based attachment: {}:{} (pid: {:?})",
271                target_binary, function_name, pid
272            );
273        }
274
275        // Collect all available program names first to avoid borrowing conflicts
276        let available_programs: Vec<String> = self
277            .bpf
278            .programs()
279            .map(|(name, _)| name.to_string())
280            .collect();
281
282        // Debug: Print all available programs
283        info!("Available programs:");
284        for name in &available_programs {
285            info!("  - {}", name);
286        }
287
288        // Get the program from the BPF object
289        let program_name: String = if let Some(name) = program_name {
290            // Use the specified program name
291            info!("Using specified program name: {}", name);
292            if available_programs.contains(&name.to_string()) {
293                name.to_string()
294            } else {
295                return Err(LoaderError::Generic(format!(
296                    "Specified program '{name}' not found in eBPF object"
297                )));
298            }
299        } else {
300            // Try different program names: section name first, then function name, then any program
301            let program_names = ["uprobe", "main"];
302            let mut found_program_name: Option<String> = None;
303
304            for name in &program_names {
305                info!("Checking if program exists: {}", name);
306                if available_programs.contains(&name.to_string()) {
307                    info!("Found program: {}", name);
308                    found_program_name = Some(name.to_string());
309                    break;
310                }
311            }
312
313            // If no standard names found, use the first available program
314            if found_program_name.is_none() {
315                if let Some(first_name) = available_programs.first() {
316                    info!(
317                        "No standard program names found, using first available: {}",
318                        first_name
319                    );
320                    found_program_name = Some(first_name.clone());
321                }
322            }
323
324            found_program_name
325                .ok_or_else(|| LoaderError::Generic("No suitable program found".to_string()))?
326        };
327
328        info!("Attempting to load program: {}", program_name);
329
330        let program_ref = self
331            .bpf
332            .program_mut(&program_name)
333            .ok_or_else(|| LoaderError::Generic(format!("Program '{program_name}' not found")))?;
334
335        info!("Found program, attempting to convert to UProbe");
336        info!("Program type: {:?}", program_ref.prog_type());
337
338        // Check what type of program this actually is
339        match program_ref {
340            aya::programs::Program::UProbe(_) => {
341                info!("Program is correctly recognized as UProbe");
342            }
343            aya::programs::Program::KProbe(_) => {
344                error!("Program is incorrectly recognized as KProbe, should be UProbe");
345            }
346            ref _other => {
347                error!("Program is unexpected type (not UProbe or KProbe)");
348            }
349        }
350
351        let program: &mut UProbe = program_ref.try_into().map_err(|e| {
352            LoaderError::Generic(format!("Program '{program_name}' is not a UProbe: {e:?}"))
353        })?;
354
355        // Load the program
356        info!("About to load eBPF program");
357        match program.load() {
358            Ok(()) => {
359                info!("Program loaded successfully");
360            }
361            Err(e) => {
362                error!("eBPF program load failed: {}", e);
363                error!("This typically indicates eBPF verifier rejection");
364
365                // Check for specific verifier errors
366                if let ProgramError::SyscallError(syscall_error) = &e {
367                    error!(
368                        "Syscall '{}' failed: {}",
369                        syscall_error.call, syscall_error.io_error
370                    );
371
372                    // Check for common error codes
373                    if let Some(errno) = syscall_error.io_error.raw_os_error() {
374                        match errno {
375                            22 => error!(
376                                "EINVAL (22): Invalid argument - likely eBPF verifier rejection"
377                            ),
378                            7 => error!("E2BIG (7): Program too large"),
379                            13 => error!("EACCES (13): Permission denied"),
380                            95 => error!("EOPNOTSUPP (95): Operation not supported"),
381                            _ => error!("Unknown errno: {}", errno),
382                        }
383                    }
384                }
385
386                // Log additional debugging info
387                error!("Program name: {}", program_name);
388                error!("Program type: {:?}", program_ref.prog_type());
389
390                return Err(LoaderError::Program(e));
391            }
392        }
393
394        // Attach the uprobe using Aya API via a compatibility helper
395        // so argument ordering stays explicit regardless of Aya version.
396        let attach_location = match offset {
397            Some(offset) => UProbeAttachLocation::AbsoluteOffset(offset),
398            None => UProbeAttachLocation::Function(function_name),
399        };
400        let attach_result = attach_location.attach(program, target_binary, pid);
401
402        match attach_result {
403            Ok(link) => {
404                if let Some(offset) = offset {
405                    info!(
406                        "Uprobe attached successfully to {} at offset 0x{:x}",
407                        target_binary, offset
408                    );
409                } else {
410                    info!(
411                        "Uprobe attached successfully to {}:{}",
412                        target_binary, function_name
413                    );
414                }
415
416                // Store the link handle and attachment parameters for later use
417                self.uprobe_link = Some(link);
418                self.attachment_params = Some(UprobeAttachmentParams {
419                    target_binary: target_binary.to_string(),
420                    function_name: function_name.to_string(),
421                    offset,
422                    pid,
423                    program_name,
424                });
425            }
426            Err(e) => {
427                if let Some(offset) = offset {
428                    error!(
429                        "Failed to attach uprobe to {} at offset 0x{:x}: {}",
430                        target_binary, offset, e
431                    );
432                    error!("Detailed error: {:#?}", e);
433                } else {
434                    error!(
435                        "Failed to attach uprobe to {}:{}: {}",
436                        target_binary, function_name, e
437                    );
438                    error!("Detailed error: {:#?}", e);
439                }
440
441                // Try to provide more helpful error information
442                if let ProgramError::SyscallError(syscall_error) = &e {
443                    error!(
444                        "Syscall '{}' failed: {}",
445                        syscall_error.call, syscall_error.io_error
446                    );
447                    if let Some(13) = syscall_error.io_error.raw_os_error() {
448                        error!("Permission denied - make sure to run with sudo");
449                    }
450                }
451
452                return Err(LoaderError::Program(e));
453            }
454        }
455
456        // Initialize event map after successful attachment
457        // Try RingBuf first, fall back to PerfEventArray
458        let event_map = if let Some(map) = self.bpf.take_map("ringbuf") {
459            info!("Initializing RingBuf event map");
460            let ringbuf: RingBuf<_> = map
461                .try_into()
462                .map_err(|e| LoaderError::Generic(format!("Failed to convert ringbuf map: {e}")))?;
463            EventMap::RingBuf(ringbuf)
464        } else if let Some(map) = self.bpf.take_map("events") {
465            info!("Initializing PerfEventArray event map");
466            let mut perf_array: PerfEventArray<_> = map.try_into().map_err(|e| {
467                LoaderError::Generic(format!("Failed to convert perf event array map: {e}"))
468            })?;
469
470            // Get online CPUs
471            let online_cpus = aya::util::online_cpus().map_err(|(_, e)| {
472                LoaderError::Generic(format!("Failed to get online CPUs: {e}"))
473            })?;
474
475            info!(
476                "Opening PerfEventArray buffers for {} online CPUs",
477                online_cpus.len()
478            );
479
480            // Open buffers for all online CPUs
481            let mut cpu_buffers = Vec::new();
482
483            for cpu_id in online_cpus {
484                let pages = self.perf_page_count;
485                match perf_array.open(cpu_id, pages) {
486                    Ok(buffer) => {
487                        if let Some(p) = pages {
488                            info!(
489                                "Opened PerfEventArray buffer for CPU {} with {} pages",
490                                cpu_id, p
491                            );
492                        } else {
493                            info!(
494                                "Opened PerfEventArray buffer for CPU {} (default pages)",
495                                cpu_id
496                            );
497                        }
498                        let fd = buffer.as_raw_fd();
499                        let readiness =
500                            AsyncFd::with_interest(PerfBufferFd(fd), Interest::READABLE).map_err(
501                                |err| {
502                                    LoaderError::Generic(format!(
503                                        "Failed to register perf buffer fd for CPU {cpu_id}: {err}"
504                                    ))
505                                },
506                            )?;
507                        cpu_buffers.push(PerfEventCpuBuffer {
508                            cpu_id,
509                            buffer,
510                            readiness,
511                        });
512                    }
513                    Err(e) => {
514                        warn!("Failed to open perf buffer for CPU {}: {}", cpu_id, e);
515                    }
516                }
517            }
518
519            if cpu_buffers.is_empty() {
520                return Err(LoaderError::Generic(
521                    "Failed to open any perf event buffers".to_string(),
522                ));
523            }
524
525            EventMap::PerfEventArray {
526                _map: perf_array,
527                cpu_buffers,
528            }
529        } else {
530            return Err(LoaderError::MapNotFound(
531                "Neither 'ringbuf' nor 'events' map found".to_string(),
532            ));
533        };
534
535        // Set parser event source based on map type
536        let event_source = match &event_map {
537            EventMap::RingBuf(_) => {
538                info!("Using RingBuf mode for parser");
539                ghostscope_protocol::EventSource::RingBuf
540            }
541            EventMap::PerfEventArray { .. } => {
542                info!("Using PerfEventArray mode for parser");
543                ghostscope_protocol::EventSource::PerfEventArray
544            }
545        };
546        self.parser = StreamingTraceParser::with_event_source(event_source);
547
548        self.event_map = Some(event_map);
549        info!("Event map initialized");
550
551        Ok(())
552    }
553
554    /// Detach the uprobe (disable tracing) while keeping eBPF resources loaded
555    /// This allows the trace to be quickly re-enabled later
556    pub fn detach_uprobe(&mut self) -> Result<()> {
557        if let Some(link_id) = self.uprobe_link.take() {
558            if let Some(params) = &self.attachment_params {
559                info!("Detaching uprobe...");
560
561                // Get the program to detach the link
562                let program_ref = self.bpf.program_mut(&params.program_name).ok_or_else(|| {
563                    let program_name = &params.program_name;
564                    LoaderError::Generic(format!("Program '{program_name}' not found"))
565                })?;
566
567                let program: &mut UProbe = program_ref.try_into().map_err(|e| {
568                    let program_name = &params.program_name;
569                    LoaderError::Generic(format!("Program '{program_name}' is not a UProbe: {e:?}"))
570                })?;
571
572                // Detach the uprobe using the link ID
573                program.detach(link_id).map_err(LoaderError::Program)?;
574
575                info!("Uprobe detached successfully");
576                Ok(())
577            } else {
578                error!("No attachment parameters stored");
579                Err(LoaderError::Generic(
580                    "No attachment parameters stored".to_string(),
581                ))
582            }
583        } else {
584            warn!("No uprobe attached, nothing to detach");
585            Ok(())
586        }
587    }
588
589    /// Reattach the uprobe (re-enable tracing) using previously stored parameters
590    /// This requires that attach_uprobe was called previously to store the parameters
591    pub fn reattach_uprobe(&mut self) -> Result<()> {
592        if self.uprobe_link.is_some() {
593            info!("Uprobe already attached");
594            return Ok(());
595        }
596
597        let params = self
598            .attachment_params
599            .as_ref()
600            .ok_or_else(|| {
601                LoaderError::Generic(
602                    "No attachment parameters stored. Call attach_uprobe first.".to_string(),
603                )
604            })?
605            .clone();
606
607        info!("Reattaching uprobe with stored parameters...");
608
609        // Get the program directly (it's already loaded)
610        let program_ref = self.bpf.program_mut(&params.program_name).ok_or_else(|| {
611            LoaderError::Generic(format!("Program '{}' not found", params.program_name))
612        })?;
613
614        let program: &mut UProbe = program_ref.try_into().map_err(|e| {
615            LoaderError::Generic(format!(
616                "Program '{}' is not a UProbe: {:?}",
617                params.program_name, e
618            ))
619        })?;
620
621        // Attach the uprobe directly (don't load - it's already loaded)
622        let attach_location = match params.offset {
623            Some(offset) => UProbeAttachLocation::AbsoluteOffset(offset),
624            None => UProbeAttachLocation::Function(params.function_name.as_str()),
625        };
626        let attach_result = attach_location.attach(program, &params.target_binary, params.pid);
627
628        match attach_result {
629            Ok(link) => {
630                if let Some(offset) = params.offset {
631                    info!(
632                        "Uprobe reattached successfully to {} at offset 0x{:x}",
633                        params.target_binary, offset
634                    );
635                } else {
636                    info!(
637                        "Uprobe reattached successfully to {}:{}",
638                        params.target_binary, params.function_name
639                    );
640                }
641
642                // Store the new link handle
643                self.uprobe_link = Some(link);
644                Ok(())
645            }
646            Err(e) => {
647                error!("Failed to reattach uprobe: {:?}", e);
648                Err(LoaderError::Program(e))
649            }
650        }
651    }
652
653    /// Check if the uprobe is currently attached
654    pub fn is_uprobe_attached(&self) -> bool {
655        self.uprobe_link.is_some()
656    }
657
658    /// Completely destroy this loader and all associated resources
659    /// This detaches any attached uprobes and clears all eBPF resources
660    /// After calling this, the loader cannot be reused
661    pub fn destroy(&mut self) -> Result<()> {
662        info!("Destroying GhostScopeLoader and all associated resources");
663
664        // First detach uprobe if attached
665        if self.uprobe_link.is_some() {
666            if let Err(e) = self.detach_uprobe() {
667                warn!("Failed to detach uprobe during destroy: {}", e);
668                // Continue with destruction even if detach fails
669            }
670        }
671
672        // Clear attachment parameters
673        self.attachment_params = None;
674
675        // Clear event map reference (this doesn't destroy the actual eBPF map,
676        // but removes our handle to it)
677        self.event_map = None;
678
679        // Note: The eBPF programs and maps will be automatically cleaned up
680        // when the `bpf` field is dropped (when this struct is dropped)
681
682        info!("GhostScopeLoader destroyed successfully");
683        Ok(())
684    }
685
686    /// Get current attachment status information
687    pub fn get_attachment_info(&self) -> Option<String> {
688        if let Some(params) = &self.attachment_params {
689            if let Some(offset) = params.offset {
690                Some(format!(
691                    "{}:{} (offset: 0x{:x}, pid: {:?}) - {}",
692                    params.target_binary,
693                    params.function_name,
694                    offset,
695                    params.pid,
696                    if self.is_uprobe_attached() {
697                        "attached"
698                    } else {
699                        "detached"
700                    }
701                ))
702            } else {
703                Some(format!(
704                    "{}:{} (pid: {:?}) - {}",
705                    params.target_binary,
706                    params.function_name,
707                    params.pid,
708                    if self.is_uprobe_attached() {
709                        "attached"
710                    } else {
711                        "detached"
712                    }
713                ))
714            }
715        } else {
716            None
717        }
718    }
719
720    // ============================================================================
721    // Event Reading
722    // ============================================================================
723
724    /// Wait for events asynchronously using AsyncFd
725    pub async fn wait_for_events_async(&mut self) -> Result<Vec<ParsedTraceEvent>> {
726        let trace_context = self.trace_context.as_ref().ok_or_else(|| {
727            LoaderError::Generic(
728                "No trace context available - cannot parse trace events".to_string(),
729            )
730        })?;
731
732        let event_map = self.event_map.as_mut().ok_or_else(|| {
733            LoaderError::Generic("Event map not initialized. Call attach_uprobe first.".to_string())
734        })?;
735
736        let mut events = Vec::new();
737
738        match event_map {
739            EventMap::RingBuf(ringbuf) => {
740                // Create AsyncFd and wait for readable; clear readiness to avoid spin
741                let async_fd = AsyncFd::new(ringbuf.as_raw_fd())
742                    .map_err(|e| LoaderError::Generic(format!("Failed to create AsyncFd: {e}")))?;
743                let mut guard = async_fd
744                    .readable()
745                    .await
746                    .map_err(|e| LoaderError::Generic(format!("AsyncFd error: {e}")))?;
747                guard.clear_ready();
748
749                // Read all available events
750                while let Some(item) = ringbuf.next() {
751                    match self.parser.process_segment(&item, trace_context) {
752                        Ok(Some(parsed_event)) => events.push(parsed_event),
753                        Ok(None) => {}
754                        Err(e) => {
755                            return Err(LoaderError::Generic(format!(
756                                "Fatal: Failed to parse trace event from RingBuf (async): {e}"
757                            )));
758                        }
759                    }
760                }
761            }
762            EventMap::PerfEventArray { cpu_buffers, .. } => {
763                use bytes::BytesMut;
764
765                let parser = &mut self.parser;
766
767                let mut drain_buffer = |entry: &mut PerfEventCpuBuffer| -> Result<bool> {
768                    let mut produced = false;
769                    let mut read_bufs = vec![BytesMut::with_capacity(4096)];
770
771                    match entry.buffer.read_events(&mut read_bufs) {
772                        Ok(result) => {
773                            if result.read > 0 {
774                                produced = true;
775                                info!(
776                                    "Read {} events from CPU {} buffer",
777                                    result.read, entry.cpu_id
778                                );
779                            }
780                            if result.lost > 0 {
781                                warn!(
782                                    "Lost {} events from CPU {} buffer",
783                                    result.lost, entry.cpu_id
784                                );
785                            }
786
787                            for (i, data) in read_bufs.iter().enumerate().take(result.read) {
788                                debug!(
789                                    "PerfEvent {}: {} bytes - {:02x?}",
790                                    i,
791                                    data.len(),
792                                    &data[..data.len().min(32)]
793                                );
794
795                                match parser.process_segment(data, trace_context) {
796                                    Ok(Some(parsed_event)) => events.push(parsed_event),
797                                    Ok(None) => {}
798                                    Err(e) => {
799                                        let cpu = entry.cpu_id;
800                                        return Err(LoaderError::Generic(format!(
801                                            "Fatal: Failed to parse trace event from PerfEventArray CPU {cpu}: {e}"
802                                        )));
803                                    }
804                                }
805                            }
806                        }
807                        Err(e) => {
808                            warn!("Failed to read from CPU {} buffer: {}", entry.cpu_id, e);
809                        }
810                    }
811
812                    Ok(produced)
813                };
814
815                loop {
816                    // Drain any buffers that already report data without waiting.
817                    let mut made_progress = false;
818                    for entry in cpu_buffers.iter_mut() {
819                        if entry.buffer.readable() {
820                            made_progress |= drain_buffer(entry)?;
821                        }
822                    }
823
824                    if made_progress {
825                        break;
826                    }
827
828                    // Wait for at least one buffer to become readable.
829                    let ready_idx = poll_fn(|cx| {
830                        for (idx, entry) in cpu_buffers.iter().enumerate() {
831                            match entry.readiness.poll_read_ready(cx) {
832                                Poll::Ready(Ok(mut guard)) => {
833                                    guard.clear_ready();
834                                    return Poll::Ready(Ok(idx));
835                                }
836                                Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
837                                Poll::Pending => {}
838                            }
839                        }
840                        Poll::Pending
841                    })
842                    .await
843                    .map_err(|e| {
844                        LoaderError::Generic(format!(
845                            "AsyncFd error while waiting for perf events: {e}"
846                        ))
847                    })?;
848
849                    // Drain the buffer that triggered readiness.
850                    made_progress |= drain_buffer(
851                        cpu_buffers
852                            .get_mut(ready_idx)
853                            .expect("ready index should be valid"),
854                    )?;
855
856                    // Drain any other buffers now advertising data.
857                    for (idx, entry) in cpu_buffers.iter_mut().enumerate() {
858                        if idx == ready_idx || !entry.buffer.readable() {
859                            continue;
860                        }
861                        made_progress |= drain_buffer(entry)?;
862                    }
863
864                    if made_progress {
865                        break;
866                    }
867                    // No events were produced despite readiness (eg. lost event markers).
868                    // Loop back and wait again.
869                }
870            }
871        }
872
873        Ok(events)
874    }
875
876    /// Set the trace context for parsing trace events
877    pub fn set_trace_context(&mut self, trace_context: TraceContext) {
878        info!("Setting trace context for trace event parsing");
879        self.trace_context = Some(trace_context);
880    }
881
882    // ============================================================================
883    // Information and Debugging
884    // ============================================================================
885
886    /// Get information about loaded maps
887    pub fn get_map_info(&self) -> Vec<String> {
888        self.bpf
889            .maps()
890            .map(|(name, _map)| format!("Map: {name}"))
891            .collect()
892    }
893
894    /// Get information about loaded programs
895    pub fn get_program_info(&self) -> Vec<String> {
896        self.bpf
897            .programs()
898            .map(|(name, _prog)| format!("Program: {name}"))
899            .collect()
900    }
901}