1use std::time::{Duration, Instant};
2
3pub struct PlatformStackWalker {
5 config: StackWalkConfig,
7 stats: WalkStats,
9 platform_context: PlatformContext,
11}
12
13#[derive(Debug, Clone)]
15pub struct StackWalkConfig {
16 pub max_depth: usize,
18 pub skip_frames: usize,
20 pub fast_unwind: bool,
22 pub collect_frame_info: bool,
24 pub max_walk_time: Duration,
26}
27
28#[derive(Debug)]
30struct PlatformContext {
31 initialized: bool,
33 #[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#[cfg(target_os = "linux")]
44#[derive(Debug)]
45struct LinuxContext {
46 libunwind_available: bool,
48 dwarf_available: bool,
50}
51
52#[cfg(target_os = "windows")]
54#[derive(Debug)]
55struct WindowsContext {
56 capture_available: bool,
58 symbols_initialized: bool,
60}
61
62#[cfg(target_os = "macos")]
64#[derive(Debug)]
65struct MacOSContext {
66 backtrace_available: bool,
68 dsym_available: bool,
70}
71
72#[derive(Debug)]
74struct WalkStats {
75 total_walks: std::sync::atomic::AtomicUsize,
77 total_frames: std::sync::atomic::AtomicUsize,
79 total_walk_time: std::sync::atomic::AtomicU64,
81 failed_walks: std::sync::atomic::AtomicUsize,
83}
84
85#[derive(Debug, Clone)]
87pub struct WalkResult {
88 pub success: bool,
90 pub frames: Vec<StackFrame>,
92 pub walk_time: Duration,
94 pub error: Option<WalkError>,
96}
97
98#[derive(Debug, Clone)]
100pub struct StackFrame {
101 pub ip: usize,
103 pub fp: Option<usize>,
105 pub sp: Option<usize>,
107 pub module_base: Option<usize>,
109 pub module_offset: Option<usize>,
111 pub symbol_info: Option<FrameSymbolInfo>,
113}
114
115#[derive(Debug, Clone)]
117pub struct FrameSymbolInfo {
118 pub name: String,
120 pub demangled_name: Option<String>,
122 pub file_name: Option<String>,
124 pub line_number: Option<u32>,
126}
127
128#[derive(Debug, Clone, PartialEq)]
130pub enum WalkError {
131 UnsupportedPlatform,
133 UnwindUnavailable,
135 InsufficientPermissions,
137 CorruptedStack,
139 Timeout,
141 MemoryError,
143 Unknown(String),
145}
146
147impl PlatformStackWalker {
148 pub fn new() -> Self {
150 Self {
151 config: StackWalkConfig::default(),
152 stats: WalkStats::new(),
153 platform_context: PlatformContext::new(),
154 }
155 }
156
157 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 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 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 pub fn walk_thread(&mut self, thread_id: u32) -> WalkResult {
224 #[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 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 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 self.platform_context.linux_context.libunwind_available = true; self.platform_context.linux_context.dwarf_available = true; self.platform_context.initialized = true;
336 Ok(())
337 }
338
339 #[cfg(target_os = "windows")]
340 fn initialize_windows(&mut self) -> Result<(), WalkError> {
341 self.platform_context.windows_context.capture_available = true; self.platform_context.windows_context.symbols_initialized = true; self.platform_context.initialized = true;
345 Ok(())
346 }
347
348 #[cfg(target_os = "macos")]
349 fn initialize_macos(&mut self) -> Result<(), WalkError> {
350 self.platform_context.macos_context.backtrace_available = true; self.platform_context.macos_context.dsym_available = true; 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 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, 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 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, 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 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, 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 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 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 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#[derive(Debug, Clone)]
511pub struct WalkStatistics {
512 pub total_walks: usize,
514 pub successful_walks: usize,
516 pub failed_walks: usize,
518 pub total_frames_collected: usize,
520 pub average_frames_per_walk: f64,
522 pub average_walk_time: Duration,
524 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 #[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}