memscope_rs/platform/
stack_walker.rs

1use std::time::{Duration, Instant};
2
3/// Platform-specific stack walking implementation
4pub struct PlatformStackWalker {
5    /// Walker configuration
6    config: StackWalkConfig,
7    /// Performance statistics
8    stats: WalkStats,
9    /// Platform-specific context
10    platform_context: PlatformContext,
11}
12
13/// Configuration for stack walking
14#[derive(Debug, Clone)]
15pub struct StackWalkConfig {
16    /// Maximum depth to walk
17    pub max_depth: usize,
18    /// Number of frames to skip at top
19    pub skip_frames: usize,
20    /// Whether to use fast unwinding
21    pub fast_unwind: bool,
22    /// Whether to collect frame info
23    pub collect_frame_info: bool,
24    /// Maximum time to spend walking
25    pub max_walk_time: Duration,
26}
27
28/// Platform-specific context for stack walking
29#[derive(Debug)]
30struct PlatformContext {
31    /// Whether unwinder is initialized
32    initialized: bool,
33    /// Platform-specific data
34    #[cfg(target_os = "linux")]
35    linux_context: LinuxContext,
36    #[cfg(target_os = "windows")]
37    windows_context: WindowsContext,
38    #[cfg(target_os = "macos")]
39    macos_context: MacOSContext,
40}
41
42/// Linux-specific context
43#[cfg(target_os = "linux")]
44#[derive(Debug)]
45struct LinuxContext {
46    /// Whether libunwind is available
47    libunwind_available: bool,
48    /// Whether DWARF info is available
49    dwarf_available: bool,
50}
51
52/// Windows-specific context
53#[cfg(target_os = "windows")]
54#[derive(Debug)]
55struct WindowsContext {
56    /// Whether RtlCaptureStackBackTrace is available
57    capture_available: bool,
58    /// Whether symbol APIs are initialized
59    symbols_initialized: bool,
60}
61
62/// macOS-specific context
63#[cfg(target_os = "macos")]
64#[derive(Debug)]
65struct MacOSContext {
66    /// Whether backtrace() is available
67    backtrace_available: bool,
68    /// Whether dSYM files are accessible
69    dsym_available: bool,
70}
71
72/// Statistics for stack walking performance
73#[derive(Debug)]
74struct WalkStats {
75    /// Total walks performed
76    total_walks: std::sync::atomic::AtomicUsize,
77    /// Total frames collected
78    total_frames: std::sync::atomic::AtomicUsize,
79    /// Total time spent walking
80    total_walk_time: std::sync::atomic::AtomicU64,
81    /// Failed walks
82    failed_walks: std::sync::atomic::AtomicUsize,
83}
84
85/// Result of stack walk operation
86#[derive(Debug, Clone)]
87pub struct WalkResult {
88    /// Success status
89    pub success: bool,
90    /// Collected stack frames
91    pub frames: Vec<StackFrame>,
92    /// Time taken for walk
93    pub walk_time: Duration,
94    /// Error if walk failed
95    pub error: Option<WalkError>,
96}
97
98/// Individual stack frame information
99#[derive(Debug, Clone)]
100pub struct StackFrame {
101    /// Instruction pointer
102    pub ip: usize,
103    /// Frame pointer
104    pub fp: Option<usize>,
105    /// Stack pointer
106    pub sp: Option<usize>,
107    /// Module base address
108    pub module_base: Option<usize>,
109    /// Offset within module
110    pub module_offset: Option<usize>,
111    /// Symbol information if available
112    pub symbol_info: Option<FrameSymbolInfo>,
113}
114
115/// Symbol information for a frame
116#[derive(Debug, Clone)]
117pub struct FrameSymbolInfo {
118    /// Symbol name
119    pub name: String,
120    /// Demangled name
121    pub demangled_name: Option<String>,
122    /// File name
123    pub file_name: Option<String>,
124    /// Line number
125    pub line_number: Option<u32>,
126}
127
128/// Errors that can occur during stack walking
129#[derive(Debug, Clone, PartialEq)]
130pub enum WalkError {
131    /// Platform not supported
132    UnsupportedPlatform,
133    /// Unwind library not available
134    UnwindUnavailable,
135    /// Insufficient permissions
136    InsufficientPermissions,
137    /// Corrupted stack
138    CorruptedStack,
139    /// Timeout during walk
140    Timeout,
141    /// Memory access error
142    MemoryError,
143    /// Unknown error
144    Unknown(String),
145}
146
147impl PlatformStackWalker {
148    /// Create new platform stack walker
149    pub fn new() -> Self {
150        Self {
151            config: StackWalkConfig::default(),
152            stats: WalkStats::new(),
153            platform_context: PlatformContext::new(),
154        }
155    }
156
157    /// Initialize stack walker for current platform
158    pub fn initialize(&mut self) -> Result<(), WalkError> {
159        #[cfg(target_os = "linux")]
160        {
161            self.initialize_linux()
162        }
163
164        #[cfg(target_os = "windows")]
165        {
166            self.initialize_windows()
167        }
168
169        #[cfg(target_os = "macos")]
170        {
171            self.initialize_macos()
172        }
173
174        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
175        {
176            Err(WalkError::UnsupportedPlatform)
177        }
178    }
179
180    /// Walk current thread's stack
181    pub fn walk_current_thread(&mut self) -> WalkResult {
182        let start_time = Instant::now();
183
184        if !self.platform_context.initialized {
185            return WalkResult {
186                success: false,
187                frames: Vec::new(),
188                walk_time: start_time.elapsed(),
189                error: Some(WalkError::UnwindUnavailable),
190            };
191        }
192
193        let result = self.perform_stack_walk();
194        let walk_time = start_time.elapsed();
195
196        // Update statistics
197        self.stats
198            .total_walks
199            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
200        if result.success {
201            self.stats
202                .total_frames
203                .fetch_add(result.frames.len(), std::sync::atomic::Ordering::Relaxed);
204        } else {
205            self.stats
206                .failed_walks
207                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
208        }
209        self.stats.total_walk_time.fetch_add(
210            walk_time.as_nanos() as u64,
211            std::sync::atomic::Ordering::Relaxed,
212        );
213
214        WalkResult {
215            success: result.success,
216            frames: result.frames,
217            walk_time,
218            error: result.error,
219        }
220    }
221
222    /// Walk specific thread's stack
223    pub fn walk_thread(&mut self, thread_id: u32) -> WalkResult {
224        // Platform-specific thread stack walking
225        #[cfg(target_os = "linux")]
226        {
227            self.walk_linux_thread(thread_id)
228        }
229
230        #[cfg(target_os = "windows")]
231        {
232            self.walk_windows_thread(thread_id)
233        }
234
235        #[cfg(target_os = "macos")]
236        {
237            self.walk_macos_thread(thread_id)
238        }
239
240        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
241        {
242            WalkResult {
243                success: false,
244                frames: Vec::new(),
245                walk_time: Duration::ZERO,
246                error: Some(WalkError::UnsupportedPlatform),
247            }
248        }
249    }
250
251    /// Get walking statistics
252    pub fn get_statistics(&self) -> WalkStatistics {
253        let total_walks = self
254            .stats
255            .total_walks
256            .load(std::sync::atomic::Ordering::Relaxed);
257        let total_frames = self
258            .stats
259            .total_frames
260            .load(std::sync::atomic::Ordering::Relaxed);
261        let total_time_ns = self
262            .stats
263            .total_walk_time
264            .load(std::sync::atomic::Ordering::Relaxed);
265        let failed_walks = self
266            .stats
267            .failed_walks
268            .load(std::sync::atomic::Ordering::Relaxed);
269
270        WalkStatistics {
271            total_walks,
272            successful_walks: total_walks.saturating_sub(failed_walks),
273            failed_walks,
274            total_frames_collected: total_frames,
275            average_frames_per_walk: if total_walks > 0 {
276                total_frames as f64 / total_walks as f64
277            } else {
278                0.0
279            },
280            average_walk_time: if total_walks > 0 {
281                Duration::from_nanos(total_time_ns / total_walks as u64)
282            } else {
283                Duration::ZERO
284            },
285            success_rate: if total_walks > 0 {
286                (total_walks - failed_walks) as f64 / total_walks as f64
287            } else {
288                0.0
289            },
290        }
291    }
292
293    /// Update walker configuration
294    pub fn update_config(&mut self, config: StackWalkConfig) {
295        self.config = config;
296    }
297
298    fn perform_stack_walk(&self) -> WalkResult {
299        let mut frames = Vec::with_capacity(self.config.max_depth);
300        let start_time = Instant::now();
301
302        #[cfg(target_os = "linux")]
303        let result = self.walk_linux_stack(&mut frames);
304
305        #[cfg(target_os = "windows")]
306        let result = self.walk_windows_stack(&mut frames);
307
308        #[cfg(target_os = "macos")]
309        let result = self.walk_macos_stack(&mut frames);
310
311        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
312        let result = Err(WalkError::UnsupportedPlatform);
313
314        match result {
315            Ok(()) => WalkResult {
316                success: true,
317                frames,
318                walk_time: start_time.elapsed(),
319                error: None,
320            },
321            Err(error) => WalkResult {
322                success: false,
323                frames,
324                walk_time: start_time.elapsed(),
325                error: Some(error),
326            },
327        }
328    }
329
330    #[cfg(target_os = "linux")]
331    fn initialize_linux(&mut self) -> Result<(), WalkError> {
332        // Check for libunwind availability
333        self.platform_context.linux_context.libunwind_available = true; // Simplified
334        self.platform_context.linux_context.dwarf_available = true; // Simplified
335        self.platform_context.initialized = true;
336        Ok(())
337    }
338
339    #[cfg(target_os = "windows")]
340    fn initialize_windows(&mut self) -> Result<(), WalkError> {
341        // Initialize Windows stack walking APIs
342        self.platform_context.windows_context.capture_available = true; // Simplified
343        self.platform_context.windows_context.symbols_initialized = true; // Simplified
344        self.platform_context.initialized = true;
345        Ok(())
346    }
347
348    #[cfg(target_os = "macos")]
349    fn initialize_macos(&mut self) -> Result<(), WalkError> {
350        // Initialize macOS stack walking
351        self.platform_context.macos_context.backtrace_available = true; // Simplified
352        self.platform_context.macos_context.dsym_available = true; // Simplified
353        self.platform_context.initialized = true;
354        Ok(())
355    }
356
357    #[cfg(target_os = "linux")]
358    fn walk_linux_stack(&self, frames: &mut Vec<StackFrame>) -> Result<(), WalkError> {
359        // Linux-specific stack walking using libunwind or similar
360        // This is a simplified implementation
361        for i in 0..self.config.max_depth.min(10) {
362            if i < self.config.skip_frames {
363                continue;
364            }
365
366            frames.push(StackFrame {
367                ip: 0x400000 + i * 0x1000, // Mock addresses
368                fp: Some(0x7fff0000 + i * 0x100),
369                sp: Some(0x7fff0000 + i * 0x100 - 8),
370                module_base: Some(0x400000),
371                module_offset: Some(i * 0x1000),
372                symbol_info: Some(FrameSymbolInfo {
373                    name: format!("function_{}", i),
374                    demangled_name: Some(format!("namespace::function_{}", i)),
375                    file_name: Some("src/main.rs".to_string()),
376                    line_number: Some((i * 10 + 100) as u32),
377                }),
378            });
379        }
380        Ok(())
381    }
382
383    #[cfg(target_os = "windows")]
384    fn walk_windows_stack(&self, frames: &mut Vec<StackFrame>) -> Result<(), WalkError> {
385        // Windows-specific stack walking using StackWalk64 or RtlCaptureStackBackTrace
386        // This is a simplified implementation
387        for i in 0..self.config.max_depth.min(10) {
388            if i < self.config.skip_frames {
389                continue;
390            }
391
392            frames.push(StackFrame {
393                ip: 0x140000000 + i * 0x1000, // Mock addresses for x64
394                fp: Some(0x000000000022f000 + i * 0x100),
395                sp: Some(0x000000000022f000 + i * 0x100 - 8),
396                module_base: Some(0x140000000),
397                module_offset: Some(i * 0x1000),
398                symbol_info: Some(FrameSymbolInfo {
399                    name: format!("function_{}", i),
400                    demangled_name: None,
401                    file_name: Some("main.cpp".to_string()),
402                    line_number: Some((i * 10 + 100) as u32),
403                }),
404            });
405        }
406        Ok(())
407    }
408
409    #[cfg(target_os = "macos")]
410    fn walk_macos_stack(&self, frames: &mut Vec<StackFrame>) -> Result<(), WalkError> {
411        // macOS-specific stack walking using backtrace() or similar
412        // This is a simplified implementation
413        for i in 0..self.config.max_depth.min(10) {
414            if i < self.config.skip_frames {
415                continue;
416            }
417
418            frames.push(StackFrame {
419                ip: 0x100000000 + i * 0x1000, // Mock addresses
420                fp: Some(0x7fff5fc00000 + i * 0x100),
421                sp: Some(0x7fff5fc00000 + i * 0x100 - 8),
422                module_base: Some(0x100000000),
423                module_offset: Some(i * 0x1000),
424                symbol_info: Some(FrameSymbolInfo {
425                    name: format!("function_{}", i),
426                    demangled_name: Some(format!("MyClass::function_{}", i)),
427                    file_name: Some("main.mm".to_string()),
428                    line_number: Some((i * 10 + 100) as u32),
429                }),
430            });
431        }
432        Ok(())
433    }
434
435    #[cfg(target_os = "linux")]
436    fn walk_linux_thread(&self, _thread_id: u32) -> WalkResult {
437        // Linux thread-specific walking would use ptrace or similar
438        WalkResult {
439            success: false,
440            frames: Vec::new(),
441            walk_time: Duration::ZERO,
442            error: Some(WalkError::Unknown(
443                "Thread walking not implemented".to_string(),
444            )),
445        }
446    }
447
448    #[cfg(target_os = "windows")]
449    fn walk_windows_thread(&self, _thread_id: u32) -> WalkResult {
450        // Windows thread-specific walking would use OpenThread + StackWalk64
451        WalkResult {
452            success: false,
453            frames: Vec::new(),
454            walk_time: Duration::ZERO,
455            error: Some(WalkError::Unknown(
456                "Thread walking not implemented".to_string(),
457            )),
458        }
459    }
460
461    #[cfg(target_os = "macos")]
462    fn walk_macos_thread(&self, _thread_id: u32) -> WalkResult {
463        // macOS thread-specific walking would use thread_get_state
464        WalkResult {
465            success: false,
466            frames: Vec::new(),
467            walk_time: Duration::ZERO,
468            error: Some(WalkError::Unknown(
469                "Thread walking not implemented".to_string(),
470            )),
471        }
472    }
473}
474
475impl PlatformContext {
476    fn new() -> Self {
477        Self {
478            initialized: false,
479            #[cfg(target_os = "linux")]
480            linux_context: LinuxContext {
481                libunwind_available: false,
482                dwarf_available: false,
483            },
484            #[cfg(target_os = "windows")]
485            windows_context: WindowsContext {
486                capture_available: false,
487                symbols_initialized: false,
488            },
489            #[cfg(target_os = "macos")]
490            macos_context: MacOSContext {
491                backtrace_available: false,
492                dsym_available: false,
493            },
494        }
495    }
496}
497
498impl WalkStats {
499    fn new() -> Self {
500        Self {
501            total_walks: std::sync::atomic::AtomicUsize::new(0),
502            total_frames: std::sync::atomic::AtomicUsize::new(0),
503            total_walk_time: std::sync::atomic::AtomicU64::new(0),
504            failed_walks: std::sync::atomic::AtomicUsize::new(0),
505        }
506    }
507}
508
509/// Statistics about stack walking performance
510#[derive(Debug, Clone)]
511pub struct WalkStatistics {
512    /// Total number of walks performed
513    pub total_walks: usize,
514    /// Number of successful walks
515    pub successful_walks: usize,
516    /// Number of failed walks
517    pub failed_walks: usize,
518    /// Total frames collected across all walks
519    pub total_frames_collected: usize,
520    /// Average frames per successful walk
521    pub average_frames_per_walk: f64,
522    /// Average time per walk
523    pub average_walk_time: Duration,
524    /// Success rate (0.0 to 1.0)
525    pub success_rate: f64,
526}
527
528impl Default for StackWalkConfig {
529    fn default() -> Self {
530        Self {
531            max_depth: 32,
532            skip_frames: 2,
533            fast_unwind: true,
534            collect_frame_info: true,
535            max_walk_time: Duration::from_millis(10),
536        }
537    }
538}
539
540impl Default for PlatformStackWalker {
541    fn default() -> Self {
542        Self::new()
543    }
544}
545
546impl std::fmt::Display for WalkError {
547    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548        match self {
549            WalkError::UnsupportedPlatform => write!(f, "Platform not supported for stack walking"),
550            WalkError::UnwindUnavailable => write!(f, "Stack unwinding library not available"),
551            WalkError::InsufficientPermissions => {
552                write!(f, "Insufficient permissions for stack walking")
553            }
554            WalkError::CorruptedStack => write!(f, "Stack appears to be corrupted"),
555            WalkError::Timeout => write!(f, "Stack walk timed out"),
556            WalkError::MemoryError => write!(f, "Memory access error during stack walk"),
557            WalkError::Unknown(msg) => write!(f, "Unknown error: {}", msg),
558        }
559    }
560}
561
562impl std::error::Error for WalkError {}
563
564#[cfg(test)]
565mod tests {
566    use super::*;
567
568    #[test]
569    fn test_platform_stack_walker_creation() {
570        let walker = PlatformStackWalker::new();
571        assert!(!walker.platform_context.initialized);
572
573        let stats = walker.get_statistics();
574        assert_eq!(stats.total_walks, 0);
575        assert_eq!(stats.success_rate, 0.0);
576    }
577
578    #[test]
579    fn test_stack_walk_config() {
580        let config = StackWalkConfig::default();
581        assert_eq!(config.max_depth, 32);
582        assert_eq!(config.skip_frames, 2);
583        assert!(config.fast_unwind);
584        assert!(config.collect_frame_info);
585    }
586
587    #[test]
588    fn test_initialization() {
589        let mut walker = PlatformStackWalker::new();
590        let result = walker.initialize();
591
592        // Should succeed on supported platforms
593        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
594        assert!(result.is_ok());
595
596        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
597        assert_eq!(result, Err(WalkError::UnsupportedPlatform));
598    }
599
600    #[test]
601    fn test_current_thread_walk() {
602        let mut walker = PlatformStackWalker::new();
603        let _ = walker.initialize();
604
605        let result = walker.walk_current_thread();
606
607        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
608        {
609            if walker.platform_context.initialized {
610                assert!(result.success);
611                assert!(!result.frames.is_empty());
612                assert!(result.walk_time > Duration::ZERO);
613            }
614        }
615    }
616
617    #[test]
618    fn test_frame_information() {
619        let frame = StackFrame {
620            ip: 0x12345678,
621            fp: Some(0x7fff0000),
622            sp: Some(0x7fff0008),
623            module_base: Some(0x12340000),
624            module_offset: Some(0x5678),
625            symbol_info: Some(FrameSymbolInfo {
626                name: "test_function".to_string(),
627                demangled_name: Some("namespace::test_function".to_string()),
628                file_name: Some("test.rs".to_string()),
629                line_number: Some(42),
630            }),
631        };
632
633        assert_eq!(frame.ip, 0x12345678);
634        assert_eq!(frame.fp, Some(0x7fff0000));
635        assert!(frame.symbol_info.is_some());
636
637        let symbol = frame
638            .symbol_info
639            .as_ref()
640            .expect("Symbol info should exist");
641        assert_eq!(symbol.name, "test_function");
642        assert_eq!(symbol.line_number, Some(42));
643    }
644}