1#![allow(warnings, unused)]
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::time::{Duration, Instant};
6
7pub struct PlatformSymbolResolver {
9 config: ResolverConfig,
11 symbol_cache: HashMap<usize, CachedSymbol>,
13 module_cache: HashMap<usize, ModuleInfo>,
15 stats: ResolverStats,
17 platform_context: ResolverContext,
19}
20
21#[derive(Debug, Clone)]
23pub struct ResolverConfig {
24 pub enable_caching: bool,
26 pub max_cache_size: usize,
28 pub symbol_search_paths: Vec<PathBuf>,
30 pub enable_demangling: bool,
32 pub max_resolve_time: Duration,
34 pub eager_loading: bool,
36}
37
38#[derive(Debug)]
40struct ResolverContext {
41 initialized: bool,
43
44 #[cfg(target_os = "linux")]
45 linux_context: LinuxResolverContext,
46
47 #[cfg(target_os = "windows")]
48 windows_context: WindowsResolverContext,
49
50 #[cfg(target_os = "macos")]
51 macos_context: MacOSResolverContext,
52}
53
54#[cfg(target_os = "linux")]
56#[derive(Debug)]
57struct LinuxResolverContext {
58 addr2line_available: bool,
60 dwarf_loaded: bool,
62}
63
64#[cfg(target_os = "windows")]
66#[derive(Debug)]
67struct WindowsResolverContext {
68 symbols_initialized: bool,
70 pdb_loaded: bool,
72 symbol_paths: Vec<PathBuf>,
74}
75
76#[cfg(target_os = "macos")]
78#[derive(Debug)]
79struct MacOSResolverContext {
80 atos_available: bool,
82 dsym_loaded: bool,
84}
85
86#[derive(Debug, Clone)]
88struct CachedSymbol {
89 symbol: SymbolInfo,
91 access_count: usize,
93}
94
95#[derive(Debug, Clone)]
97pub struct SymbolInfo {
98 pub name: String,
100 pub demangled_name: Option<String>,
102 pub file_path: Option<PathBuf>,
104 pub line_number: Option<u32>,
106 pub column_number: Option<u32>,
108 pub function_start: Option<usize>,
110 pub function_size: Option<usize>,
112 pub module_name: Option<String>,
114 pub compilation_unit: Option<String>,
116}
117
118#[derive(Debug, Clone)]
120pub struct ModuleInfo {
121 pub name: String,
123 pub base_address: usize,
125 pub size: usize,
127 pub file_path: PathBuf,
129 pub has_symbols: bool,
131 pub symbol_file: Option<PathBuf>,
133}
134
135#[derive(Debug)]
137struct ResolverStats {
138 total_resolutions: std::sync::atomic::AtomicUsize,
140 successful_resolutions: std::sync::atomic::AtomicUsize,
142 cache_hits: std::sync::atomic::AtomicUsize,
144 total_resolve_time: std::sync::atomic::AtomicU64,
146}
147
148#[derive(Debug, Clone, PartialEq)]
150pub enum ResolveError {
151 UnsupportedPlatform,
153 SymbolNotFound,
155 NoDebugInfo,
157 FileAccessError(String),
159 ParseError(String),
161 Timeout,
163 MemoryError,
165 Unknown(String),
167}
168
169impl PlatformSymbolResolver {
170 pub fn new() -> Self {
172 Self {
173 config: ResolverConfig::default(),
174 symbol_cache: HashMap::new(),
175 module_cache: HashMap::new(),
176 stats: ResolverStats::new(),
177 platform_context: ResolverContext::new(),
178 }
179 }
180
181 pub fn initialize(&mut self) -> Result<(), ResolveError> {
183 #[cfg(target_os = "linux")]
184 {
185 self.initialize_linux()
186 }
187
188 #[cfg(target_os = "windows")]
189 {
190 self.initialize_windows()
191 }
192
193 #[cfg(target_os = "macos")]
194 {
195 self.initialize_macos()
196 }
197
198 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
199 {
200 Err(ResolveError::UnsupportedPlatform)
201 }
202 }
203
204 pub fn resolve_symbol(&mut self, address: usize) -> Result<SymbolInfo, ResolveError> {
206 let start_time = Instant::now();
207
208 if self.config.enable_caching {
210 if let Some(cached) = self.symbol_cache.get_mut(&address) {
211 cached.access_count += 1;
212 self.stats
213 .cache_hits
214 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
215 return Ok(cached.symbol.clone());
216 }
217 }
218
219 self.stats
221 .total_resolutions
222 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
223
224 let result = self.perform_resolution(address);
225 let resolve_time = start_time.elapsed();
226
227 self.stats.total_resolve_time.fetch_add(
229 resolve_time.as_nanos() as u64,
230 std::sync::atomic::Ordering::Relaxed,
231 );
232
233 if let Ok(symbol) = &result {
234 self.stats
235 .successful_resolutions
236 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
237
238 if self.config.enable_caching && self.symbol_cache.len() < self.config.max_cache_size {
240 self.symbol_cache.insert(
241 address,
242 CachedSymbol {
243 symbol: symbol.clone(),
244 access_count: 1,
245 },
246 );
247 }
248 }
249
250 result
251 }
252
253 pub fn resolve_batch(&mut self, addresses: &[usize]) -> Vec<Result<SymbolInfo, ResolveError>> {
255 addresses
256 .iter()
257 .map(|&addr| self.resolve_symbol(addr))
258 .collect()
259 }
260
261 pub fn get_module_info(&mut self, address: usize) -> Option<ModuleInfo> {
263 for (base_addr, module) in &self.module_cache {
265 if address >= *base_addr && address < (*base_addr + module.size) {
266 return Some(module.clone());
267 }
268 }
269
270 self.load_module_info(address)
272 }
273
274 pub fn get_statistics(&self) -> ResolverStatistics {
276 let total = self
277 .stats
278 .total_resolutions
279 .load(std::sync::atomic::Ordering::Relaxed);
280 let successful = self
281 .stats
282 .successful_resolutions
283 .load(std::sync::atomic::Ordering::Relaxed);
284 let cache_hits = self
285 .stats
286 .cache_hits
287 .load(std::sync::atomic::Ordering::Relaxed);
288 let total_time_ns = self
289 .stats
290 .total_resolve_time
291 .load(std::sync::atomic::Ordering::Relaxed);
292
293 ResolverStatistics {
294 total_resolutions: total,
295 successful_resolutions: successful,
296 failed_resolutions: total.saturating_sub(successful),
297 cache_hits,
298 cache_misses: total.saturating_sub(cache_hits),
299 cache_hit_rate: if total > 0 {
300 cache_hits as f64 / total as f64
301 } else {
302 0.0
303 },
304 success_rate: if total > 0 {
305 successful as f64 / total as f64
306 } else {
307 0.0
308 },
309 average_resolve_time: if total > 0 {
310 Duration::from_nanos(total_time_ns / total as u64)
311 } else {
312 Duration::ZERO
313 },
314 current_cache_size: self.symbol_cache.len(),
315 }
316 }
317
318 pub fn clear_cache(&mut self) {
320 self.symbol_cache.clear();
321 }
322
323 pub fn update_config(&mut self, config: ResolverConfig) {
325 self.config = config;
326 }
327
328 fn perform_resolution(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
329 if !self.platform_context.initialized {
330 return Err(ResolveError::NoDebugInfo);
331 }
332
333 #[cfg(target_os = "linux")]
334 return self.resolve_linux_symbol(address);
335
336 #[cfg(target_os = "windows")]
337 return self.resolve_windows_symbol(address);
338
339 #[cfg(target_os = "macos")]
340 return self.resolve_macos_symbol(address);
341
342 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
343 Err(ResolveError::UnsupportedPlatform)
344 }
345
346 #[cfg(target_os = "linux")]
347 fn initialize_linux(&mut self) -> Result<(), ResolveError> {
348 self.platform_context.linux_context.addr2line_available = true; self.platform_context.linux_context.dwarf_loaded = true; self.platform_context.initialized = true;
352 Ok(())
353 }
354
355 #[cfg(target_os = "windows")]
356 fn initialize_windows(&mut self) -> Result<(), ResolveError> {
357 self.platform_context.windows_context.symbols_initialized = true; self.platform_context.windows_context.pdb_loaded = true; self.platform_context.initialized = true;
361 Ok(())
362 }
363
364 #[cfg(target_os = "macos")]
365 fn initialize_macos(&mut self) -> Result<(), ResolveError> {
366 self.platform_context.macos_context.atos_available = true; self.platform_context.macos_context.dsym_loaded = true; self.platform_context.initialized = true;
370 Ok(())
371 }
372
373 #[cfg(target_os = "linux")]
374 fn resolve_linux_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
375 use std::ffi::CStr;
377
378 let addr = address as *const libc::c_void;
379
380 unsafe {
381 let mut info: libc::Dl_info = std::mem::zeroed();
382
383 if libc::dladdr(addr, &mut info) == 0 {
384 return Err(ResolveError::SymbolNotFound);
385 }
386
387 let name = if !info.dli_sname.is_null() {
388 CStr::from_ptr(info.dli_sname).to_string_lossy().to_string()
389 } else {
390 format!("unknown_0x{:x}", address)
391 };
392
393 let file_path = if !info.dli_fname.is_null() {
394 Some(PathBuf::from(
395 CStr::from_ptr(info.dli_fname).to_string_lossy().to_string(),
396 ))
397 } else {
398 None
399 };
400
401 let base_address = info.dli_fbase as usize;
402 let _offset = address.saturating_sub(base_address);
403
404 let module_name = file_path
406 .as_ref()
407 .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()));
408
409 Ok(SymbolInfo {
410 name: name.clone(),
411 demangled_name: Some(name), file_path,
413 line_number: None, column_number: None,
415 function_start: Some(base_address),
416 function_size: None, module_name,
418 compilation_unit: None, })
420 }
421 }
422
423 #[cfg(target_os = "windows")]
424 fn resolve_windows_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
425 use windows_sys::Win32::Foundation::GetLastError;
426 use windows_sys::Win32::System::Diagnostics::Debug::{
427 SymCleanup, SymFromAddrW, SymGetModuleBase64, SymInitializeW, SymSetContext,
428 SYMBOL_INFO,
429 };
430 use windows_sys::Win32::System::ProcessStatus::EnumProcessModules;
431
432 let process: windows_sys::Win32::Foundation::HANDLE = -1isize as _;
433
434 unsafe {
435 if SymInitializeW(process, std::ptr::null(), 1) == 0 {
436 return Err(ResolveError::Unknown(format!(
437 "SymInitializeW failed: {}",
438 GetLastError()
439 )));
440 }
441
442 let mut symbol_info: SYMBOL_INFO = std::mem::zeroed();
443 symbol_info.SizeOfStruct = std::mem::size_of::<SYMBOL_INFO>() as u32;
444 symbol_info.MaxNameLen = 256;
445
446 let mut displacement = 0u64;
447
448 let result = if SymFromAddrW(
449 process,
450 address as u64,
451 &mut displacement,
452 &mut symbol_info as *mut SYMBOL_INFO as *mut _,
453 ) == 0
454 {
455 Err(ResolveError::SymbolNotFound)
456 } else {
457 let name = if symbol_info.Name[0] != 0 {
458 let name_len = symbol_info.NameLen as usize;
459 let name_bytes = &symbol_info.Name[..name_len.min(symbol_info.Name.len())];
460 let name_u16: Vec<u16> = name_bytes.iter().map(|&b| b as u16).collect();
462 String::from_utf16_lossy(&name_u16)
463 } else {
464 format!("unknown_0x{:x}", address)
465 };
466
467 let module_base = SymGetModuleBase64(process, address as u64);
468
469 Ok(SymbolInfo {
470 name: name.clone(),
471 demangled_name: Some(name),
472 file_path: None,
473 line_number: None,
474 column_number: None,
475 function_start: Some(symbol_info.Address as usize),
476 function_size: None,
477 module_name: if module_base != 0 {
478 Some(format!("module_0x{:x}", module_base))
479 } else {
480 None
481 },
482 compilation_unit: None,
483 })
484 };
485
486 SymCleanup(process);
487
488 result
489 }
490 }
491
492 #[cfg(target_os = "macos")]
493 fn resolve_macos_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
494 use std::ffi::CStr;
496
497 let addr = address as *const libc::c_void;
498
499 unsafe {
500 let mut info: libc::Dl_info = std::mem::zeroed();
501
502 if libc::dladdr(addr, &mut info) == 0 {
503 return Err(ResolveError::SymbolNotFound);
504 }
505
506 let name = if !info.dli_sname.is_null() {
507 CStr::from_ptr(info.dli_sname).to_string_lossy().to_string()
508 } else {
509 format!("unknown_0x{:x}", address)
510 };
511
512 let file_path = if !info.dli_fname.is_null() {
513 Some(PathBuf::from(
514 CStr::from_ptr(info.dli_fname).to_string_lossy().to_string(),
515 ))
516 } else {
517 None
518 };
519
520 let base_address = info.dli_fbase as usize;
521 let _offset = address.saturating_sub(base_address);
522
523 let module_name = file_path
525 .as_ref()
526 .and_then(|p| p.file_name().map(|n| n.to_string_lossy().to_string()));
527
528 Ok(SymbolInfo {
529 name: name.clone(),
530 demangled_name: Some(name), file_path: file_path.clone(),
532 line_number: None, column_number: None,
534 function_start: Some(base_address),
535 function_size: None, module_name,
537 compilation_unit: None, })
539 }
540 }
541
542 fn load_module_info(&mut self, _address: usize) -> Option<ModuleInfo> {
543 None
549 }
550}
551
552impl ResolverContext {
553 fn new() -> Self {
554 Self {
555 initialized: false,
556 #[cfg(target_os = "linux")]
557 linux_context: LinuxResolverContext {
558 addr2line_available: false,
559 dwarf_loaded: false,
560 },
561 #[cfg(target_os = "windows")]
562 windows_context: WindowsResolverContext {
563 symbols_initialized: false,
564 pdb_loaded: false,
565 symbol_paths: Vec::new(),
566 },
567 #[cfg(target_os = "macos")]
568 macos_context: MacOSResolverContext {
569 atos_available: false,
570 dsym_loaded: false,
571 },
572 }
573 }
574}
575
576impl ResolverStats {
577 fn new() -> Self {
578 Self {
579 total_resolutions: std::sync::atomic::AtomicUsize::new(0),
580 successful_resolutions: std::sync::atomic::AtomicUsize::new(0),
581 cache_hits: std::sync::atomic::AtomicUsize::new(0),
582 total_resolve_time: std::sync::atomic::AtomicU64::new(0),
583 }
584 }
585}
586
587#[derive(Debug, Clone)]
589pub struct ResolverStatistics {
590 pub total_resolutions: usize,
592 pub successful_resolutions: usize,
594 pub failed_resolutions: usize,
596 pub cache_hits: usize,
598 pub cache_misses: usize,
600 pub cache_hit_rate: f64,
602 pub success_rate: f64,
604 pub average_resolve_time: Duration,
606 pub current_cache_size: usize,
608}
609
610impl Default for ResolverConfig {
611 fn default() -> Self {
612 Self {
613 enable_caching: true,
614 max_cache_size: 10000,
615 symbol_search_paths: Vec::new(),
616 enable_demangling: true,
617 max_resolve_time: Duration::from_millis(100),
618 eager_loading: false,
619 }
620 }
621}
622
623impl Default for PlatformSymbolResolver {
624 fn default() -> Self {
625 Self::new()
626 }
627}
628
629impl std::fmt::Display for ResolveError {
630 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
631 match self {
632 ResolveError::UnsupportedPlatform => {
633 write!(f, "Platform not supported for symbol resolution")
634 }
635 ResolveError::SymbolNotFound => write!(f, "Symbol not found"),
636 ResolveError::NoDebugInfo => write!(f, "Debug information not available"),
637 ResolveError::FileAccessError(msg) => write!(f, "File access error: {}", msg),
638 ResolveError::ParseError(msg) => write!(f, "Parse error: {}", msg),
639 ResolveError::Timeout => write!(f, "Symbol resolution timed out"),
640 ResolveError::MemoryError => write!(f, "Memory error during symbol resolution"),
641 ResolveError::Unknown(msg) => write!(f, "Unknown error: {}", msg),
642 }
643 }
644}
645
646impl std::error::Error for ResolveError {}
647
648#[cfg(test)]
649mod tests {
650 use super::*;
651
652 #[test]
653 fn test_symbol_resolver_creation() {
654 let resolver = PlatformSymbolResolver::new();
655 assert!(!resolver.platform_context.initialized);
656 assert!(resolver.symbol_cache.is_empty());
657
658 let stats = resolver.get_statistics();
659 assert_eq!(stats.total_resolutions, 0);
660 assert_eq!(stats.cache_hit_rate, 0.0);
661 }
662
663 #[test]
664 fn test_resolver_initialization() {
665 let mut resolver = PlatformSymbolResolver::new();
666 let result = resolver.initialize();
667
668 #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
669 assert!(result.is_ok());
670
671 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
672 assert_eq!(result, Err(ResolveError::UnsupportedPlatform));
673 }
674
675 #[test]
676 fn test_symbol_resolution() {
677 let mut resolver = PlatformSymbolResolver::new();
678 let _ = resolver.initialize();
679
680 let address = test_symbol_resolution as *const () as usize;
682 let result = resolver.resolve_symbol(address);
683
684 #[cfg(target_os = "linux")]
685 {
686 if resolver.platform_context.initialized {
687 match result {
690 Ok(symbol) => {
691 assert!(!symbol.name.is_empty());
692 }
694 Err(_) => {
695 }
698 }
699 }
700 }
701 }
702
703 #[test]
704 fn test_symbol_caching() {
705 let mut resolver = PlatformSymbolResolver::new();
706 let _ = resolver.initialize();
707
708 let address = test_symbol_caching as *const () as usize;
710
711 let _ = resolver.resolve_symbol(address);
712 let stats1 = resolver.get_statistics();
713
714 let _ = resolver.resolve_symbol(address);
715 let stats2 = resolver.get_statistics();
716
717 #[cfg(target_os = "linux")]
718 {
719 if resolver.platform_context.initialized {
720 assert!(stats2.cache_hits >= stats1.cache_hits);
722 }
723 }
724 }
725
726 #[test]
727 fn test_batch_resolution() {
728 let mut resolver = PlatformSymbolResolver::new();
729 let _ = resolver.initialize();
730
731 let addresses = vec![
733 test_batch_resolution as *const () as usize,
734 test_symbol_caching as *const () as usize,
735 test_module_info as *const () as usize,
736 ];
737 let results = resolver.resolve_batch(&addresses);
738
739 assert_eq!(results.len(), 3);
740
741 #[cfg(target_os = "linux")]
742 {
743 if resolver.platform_context.initialized {
744 let success_count = results.iter().filter(|r| r.is_ok()).count();
746 assert!(success_count > 0, "At least one symbol should resolve");
747 }
748 }
749 }
750
751 #[test]
752 fn test_module_info() {
753 let mut resolver = PlatformSymbolResolver::new();
754 let _ = resolver.initialize();
755
756 let address = test_module_info as *const () as usize;
758 let module = resolver.get_module_info(address);
759
760 #[cfg(target_os = "linux")]
761 {
762 if resolver.platform_context.initialized {
763 if let Some(module) = module {
765 assert!(!module.name.is_empty());
766 assert!(module.size > 0);
767 }
768 }
770 }
771 }
772}