memscope_rs/platform/
symbol_resolver.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3use std::time::{Duration, Instant};
4
5/// Platform-specific symbol resolution
6pub struct PlatformSymbolResolver {
7    /// Resolver configuration
8    config: ResolverConfig,
9    /// Symbol cache
10    symbol_cache: HashMap<usize, CachedSymbol>,
11    /// Module information cache
12    module_cache: HashMap<usize, ModuleInfo>,
13    /// Performance statistics
14    stats: ResolverStats,
15    /// Platform-specific context
16    platform_context: ResolverContext,
17}
18
19/// Configuration for symbol resolution
20#[derive(Debug, Clone)]
21pub struct ResolverConfig {
22    /// Whether to enable symbol caching
23    pub enable_caching: bool,
24    /// Maximum cache size
25    pub max_cache_size: usize,
26    /// Search paths for debug symbols
27    pub symbol_search_paths: Vec<PathBuf>,
28    /// Whether to perform demangling
29    pub enable_demangling: bool,
30    /// Maximum time to spend resolving a symbol
31    pub max_resolve_time: Duration,
32    /// Whether to load symbols eagerly
33    pub eager_loading: bool,
34}
35
36/// Platform-specific resolver context
37#[derive(Debug)]
38struct ResolverContext {
39    /// Whether resolver is initialized
40    initialized: bool,
41
42    #[cfg(target_os = "linux")]
43    linux_context: LinuxResolverContext,
44
45    #[cfg(target_os = "windows")]
46    windows_context: WindowsResolverContext,
47
48    #[cfg(target_os = "macos")]
49    macos_context: MacOSResolverContext,
50}
51
52/// Linux-specific resolver context
53#[cfg(target_os = "linux")]
54#[derive(Debug)]
55struct LinuxResolverContext {
56    /// Whether addr2line is available
57    addr2line_available: bool,
58    /// Whether DWARF debug info is loaded
59    dwarf_loaded: bool,
60    /// Loaded shared libraries
61    #[allow(dead_code)]
62    loaded_libraries: Vec<LibraryInfo>,
63}
64
65/// Windows-specific resolver context
66#[cfg(target_os = "windows")]
67#[derive(Debug)]
68struct WindowsResolverContext {
69    /// Whether symbol APIs are initialized
70    symbols_initialized: bool,
71    /// Whether PDB files are loaded
72    pdb_loaded: bool,
73    /// Symbol search paths
74    #[allow(dead_code)]
75    symbol_paths: Vec<PathBuf>,
76}
77
78/// macOS-specific resolver context
79#[cfg(target_os = "macos")]
80#[derive(Debug)]
81struct MacOSResolverContext {
82    /// Whether atos utility is available
83    atos_available: bool,
84    /// Whether dSYM files are loaded
85    dsym_loaded: bool,
86    /// Loaded frameworks
87    #[allow(dead_code)]
88    loaded_frameworks: Vec<FrameworkInfo>,
89}
90
91/// Information about a loaded library/module
92#[derive(Debug, Clone)]
93#[allow(dead_code)]
94struct LibraryInfo {
95    /// Library name
96    name: String,
97    /// Base address
98    base_address: usize,
99    /// Size of library
100    size: usize,
101    /// Path to library file
102    path: PathBuf,
103}
104
105/// Framework information (macOS)
106#[derive(Debug, Clone)]
107#[allow(dead_code)]
108struct FrameworkInfo {
109    /// Framework name
110    #[allow(dead_code)]
111    name: String,
112    /// Base address
113    #[allow(dead_code)]
114    base_address: usize,
115    /// dSYM path if available
116    #[allow(dead_code)]
117    dsym_path: Option<PathBuf>,
118}
119
120/// Cached symbol information
121#[derive(Debug, Clone)]
122struct CachedSymbol {
123    /// Symbol information
124    symbol: SymbolInfo,
125    /// Cache timestamp
126    #[allow(dead_code)]
127    cached_at: Instant,
128    /// Access count
129    access_count: usize,
130}
131
132/// Detailed symbol information
133#[derive(Debug, Clone)]
134pub struct SymbolInfo {
135    /// Symbol name
136    pub name: String,
137    /// Demangled name if available
138    pub demangled_name: Option<String>,
139    /// Source file path
140    pub file_path: Option<PathBuf>,
141    /// Line number in source
142    pub line_number: Option<u32>,
143    /// Column number
144    pub column_number: Option<u32>,
145    /// Function start address
146    pub function_start: Option<usize>,
147    /// Function size
148    pub function_size: Option<usize>,
149    /// Module/library name
150    pub module_name: Option<String>,
151    /// Compilation unit
152    pub compilation_unit: Option<String>,
153}
154
155/// Module/library information
156#[derive(Debug, Clone)]
157pub struct ModuleInfo {
158    /// Module name
159    pub name: String,
160    /// Base address where module is loaded
161    pub base_address: usize,
162    /// Size of module
163    pub size: usize,
164    /// Path to module file
165    pub file_path: PathBuf,
166    /// Whether debug symbols are available
167    pub has_symbols: bool,
168    /// Symbol file path if separate
169    pub symbol_file: Option<PathBuf>,
170}
171
172/// Performance statistics for symbol resolution
173#[derive(Debug)]
174struct ResolverStats {
175    /// Total resolution attempts
176    total_resolutions: std::sync::atomic::AtomicUsize,
177    /// Successful resolutions
178    successful_resolutions: std::sync::atomic::AtomicUsize,
179    /// Cache hits
180    cache_hits: std::sync::atomic::AtomicUsize,
181    /// Total resolution time
182    total_resolve_time: std::sync::atomic::AtomicU64,
183}
184
185/// Errors that can occur during symbol resolution
186#[derive(Debug, Clone, PartialEq)]
187pub enum ResolveError {
188    /// Platform not supported
189    UnsupportedPlatform,
190    /// Symbol not found
191    SymbolNotFound,
192    /// Debug information not available
193    NoDebugInfo,
194    /// File access error
195    FileAccessError(String),
196    /// Parse error
197    ParseError(String),
198    /// Timeout during resolution
199    Timeout,
200    /// Memory error
201    MemoryError,
202    /// Unknown error
203    Unknown(String),
204}
205
206impl PlatformSymbolResolver {
207    /// Create new symbol resolver
208    pub fn new() -> Self {
209        Self {
210            config: ResolverConfig::default(),
211            symbol_cache: HashMap::new(),
212            module_cache: HashMap::new(),
213            stats: ResolverStats::new(),
214            platform_context: ResolverContext::new(),
215        }
216    }
217
218    /// Initialize symbol resolver for current platform
219    pub fn initialize(&mut self) -> Result<(), ResolveError> {
220        #[cfg(target_os = "linux")]
221        {
222            self.initialize_linux()
223        }
224
225        #[cfg(target_os = "windows")]
226        {
227            self.initialize_windows()
228        }
229
230        #[cfg(target_os = "macos")]
231        {
232            self.initialize_macos()
233        }
234
235        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
236        {
237            Err(ResolveError::UnsupportedPlatform)
238        }
239    }
240
241    /// Resolve symbol for given address
242    pub fn resolve_symbol(&mut self, address: usize) -> Result<SymbolInfo, ResolveError> {
243        let start_time = Instant::now();
244
245        // Check cache first
246        if self.config.enable_caching {
247            if let Some(cached) = self.symbol_cache.get_mut(&address) {
248                cached.access_count += 1;
249                self.stats
250                    .cache_hits
251                    .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
252                return Ok(cached.symbol.clone());
253            }
254        }
255
256        // Perform resolution
257        self.stats
258            .total_resolutions
259            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
260
261        let result = self.perform_resolution(address);
262        let resolve_time = start_time.elapsed();
263
264        // Update statistics
265        self.stats.total_resolve_time.fetch_add(
266            resolve_time.as_nanos() as u64,
267            std::sync::atomic::Ordering::Relaxed,
268        );
269
270        if let Ok(symbol) = &result {
271            self.stats
272                .successful_resolutions
273                .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
274
275            // Cache successful resolution
276            if self.config.enable_caching && self.symbol_cache.len() < self.config.max_cache_size {
277                self.symbol_cache.insert(
278                    address,
279                    CachedSymbol {
280                        symbol: symbol.clone(),
281                        cached_at: Instant::now(),
282                        access_count: 1,
283                    },
284                );
285            }
286        }
287
288        result
289    }
290
291    /// Resolve symbols for multiple addresses in batch
292    pub fn resolve_batch(&mut self, addresses: &[usize]) -> Vec<Result<SymbolInfo, ResolveError>> {
293        addresses
294            .iter()
295            .map(|&addr| self.resolve_symbol(addr))
296            .collect()
297    }
298
299    /// Get module information for address
300    pub fn get_module_info(&mut self, address: usize) -> Option<ModuleInfo> {
301        // Check cache first
302        for (base_addr, module) in &self.module_cache {
303            if address >= *base_addr && address < (*base_addr + module.size) {
304                return Some(module.clone());
305            }
306        }
307
308        // Load module info
309        self.load_module_info(address)
310    }
311
312    /// Get resolver statistics
313    pub fn get_statistics(&self) -> ResolverStatistics {
314        let total = self
315            .stats
316            .total_resolutions
317            .load(std::sync::atomic::Ordering::Relaxed);
318        let successful = self
319            .stats
320            .successful_resolutions
321            .load(std::sync::atomic::Ordering::Relaxed);
322        let cache_hits = self
323            .stats
324            .cache_hits
325            .load(std::sync::atomic::Ordering::Relaxed);
326        let total_time_ns = self
327            .stats
328            .total_resolve_time
329            .load(std::sync::atomic::Ordering::Relaxed);
330
331        ResolverStatistics {
332            total_resolutions: total,
333            successful_resolutions: successful,
334            failed_resolutions: total.saturating_sub(successful),
335            cache_hits,
336            cache_misses: total.saturating_sub(cache_hits),
337            cache_hit_rate: if total > 0 {
338                cache_hits as f64 / total as f64
339            } else {
340                0.0
341            },
342            success_rate: if total > 0 {
343                successful as f64 / total as f64
344            } else {
345                0.0
346            },
347            average_resolve_time: if total > 0 {
348                Duration::from_nanos(total_time_ns / total as u64)
349            } else {
350                Duration::ZERO
351            },
352            current_cache_size: self.symbol_cache.len(),
353        }
354    }
355
356    /// Clear symbol cache
357    pub fn clear_cache(&mut self) {
358        self.symbol_cache.clear();
359    }
360
361    /// Update resolver configuration
362    pub fn update_config(&mut self, config: ResolverConfig) {
363        self.config = config;
364    }
365
366    fn perform_resolution(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
367        if !self.platform_context.initialized {
368            return Err(ResolveError::NoDebugInfo);
369        }
370
371        #[cfg(target_os = "linux")]
372        return self.resolve_linux_symbol(address);
373
374        #[cfg(target_os = "windows")]
375        return self.resolve_windows_symbol(address);
376
377        #[cfg(target_os = "macos")]
378        return self.resolve_macos_symbol(address);
379
380        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
381        Err(ResolveError::UnsupportedPlatform)
382    }
383
384    #[cfg(target_os = "linux")]
385    fn initialize_linux(&mut self) -> Result<(), ResolveError> {
386        // Initialize Linux symbol resolution using addr2line, DWARF, etc.
387        self.platform_context.linux_context.addr2line_available = true; // Simplified
388        self.platform_context.linux_context.dwarf_loaded = true; // Simplified
389        self.platform_context.initialized = true;
390        Ok(())
391    }
392
393    #[cfg(target_os = "windows")]
394    fn initialize_windows(&mut self) -> Result<(), ResolveError> {
395        // Initialize Windows symbol resolution using dbghelp.dll
396        self.platform_context.windows_context.symbols_initialized = true; // Simplified
397        self.platform_context.windows_context.pdb_loaded = true; // Simplified
398        self.platform_context.initialized = true;
399        Ok(())
400    }
401
402    #[cfg(target_os = "macos")]
403    fn initialize_macos(&mut self) -> Result<(), ResolveError> {
404        // Initialize macOS symbol resolution using atos, dSYM files
405        self.platform_context.macos_context.atos_available = true; // Simplified
406        self.platform_context.macos_context.dsym_loaded = true; // Simplified
407        self.platform_context.initialized = true;
408        Ok(())
409    }
410
411    #[cfg(target_os = "linux")]
412    fn resolve_linux_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
413        // Linux symbol resolution using addr2line, objdump, etc.
414        // This is a simplified mock implementation
415        Ok(SymbolInfo {
416            name: format!("linux_function_{:x}", address),
417            demangled_name: Some(format!("namespace::linux_function_{:x}", address)),
418            file_path: Some(PathBuf::from("/usr/src/app/main.rs")),
419            line_number: Some((address % 1000) as u32 + 1),
420            column_number: Some((address % 80) as u32 + 1),
421            function_start: Some(address & !0xfff),
422            function_size: Some(0x1000),
423            module_name: Some("main_executable".to_string()),
424            compilation_unit: Some("main.rs".to_string()),
425        })
426    }
427
428    #[cfg(target_os = "windows")]
429    fn resolve_windows_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
430        // Windows symbol resolution using dbghelp APIs
431        // This is a simplified mock implementation
432        Ok(SymbolInfo {
433            name: format!("windows_function_{:x}", address),
434            demangled_name: None, // Windows doesn't typically mangle C symbols
435            file_path: Some(PathBuf::from("C:\\src\\main.cpp")),
436            line_number: Some((address % 1000) as u32 + 1),
437            column_number: Some((address % 80) as u32 + 1),
438            function_start: Some(address & !0xfff),
439            function_size: Some(0x1000),
440            module_name: Some("main.exe".to_string()),
441            compilation_unit: Some("main.cpp".to_string()),
442        })
443    }
444
445    #[cfg(target_os = "macos")]
446    fn resolve_macos_symbol(&self, address: usize) -> Result<SymbolInfo, ResolveError> {
447        // macOS symbol resolution using atos, dSYM files
448        // This is a simplified mock implementation
449        Ok(SymbolInfo {
450            name: format!("macos_function_{:x}", address),
451            demangled_name: Some(format!("MyClass::macos_function_{:x}", address)),
452            file_path: Some(PathBuf::from("/Users/dev/src/main.mm")),
453            line_number: Some((address % 1000) as u32 + 1),
454            column_number: Some((address % 80) as u32 + 1),
455            function_start: Some(address & !0xfff),
456            function_size: Some(0x1000),
457            module_name: Some("main".to_string()),
458            compilation_unit: Some("main.mm".to_string()),
459        })
460    }
461
462    fn load_module_info(&mut self, address: usize) -> Option<ModuleInfo> {
463        // Platform-specific module loading
464        let module = ModuleInfo {
465            name: "unknown_module".to_string(),
466            base_address: address & !0xfffff, // Align to 1MB boundary
467            size: 0x100000,                   // 1MB default
468            file_path: PathBuf::from("unknown"),
469            has_symbols: true,
470            symbol_file: None,
471        };
472
473        self.module_cache
474            .insert(module.base_address, module.clone());
475        Some(module)
476    }
477}
478
479impl ResolverContext {
480    fn new() -> Self {
481        Self {
482            initialized: false,
483            #[cfg(target_os = "linux")]
484            linux_context: LinuxResolverContext {
485                addr2line_available: false,
486                dwarf_loaded: false,
487                loaded_libraries: Vec::new(),
488            },
489            #[cfg(target_os = "windows")]
490            windows_context: WindowsResolverContext {
491                symbols_initialized: false,
492                pdb_loaded: false,
493                symbol_paths: Vec::new(),
494            },
495            #[cfg(target_os = "macos")]
496            macos_context: MacOSResolverContext {
497                atos_available: false,
498                dsym_loaded: false,
499                loaded_frameworks: Vec::new(),
500            },
501        }
502    }
503}
504
505impl ResolverStats {
506    fn new() -> Self {
507        Self {
508            total_resolutions: std::sync::atomic::AtomicUsize::new(0),
509            successful_resolutions: std::sync::atomic::AtomicUsize::new(0),
510            cache_hits: std::sync::atomic::AtomicUsize::new(0),
511            total_resolve_time: std::sync::atomic::AtomicU64::new(0),
512        }
513    }
514}
515
516/// Statistics about symbol resolution performance
517#[derive(Debug, Clone)]
518pub struct ResolverStatistics {
519    /// Total resolution attempts
520    pub total_resolutions: usize,
521    /// Number of successful resolutions
522    pub successful_resolutions: usize,
523    /// Number of failed resolutions
524    pub failed_resolutions: usize,
525    /// Number of cache hits
526    pub cache_hits: usize,
527    /// Number of cache misses
528    pub cache_misses: usize,
529    /// Cache hit rate (0.0 to 1.0)
530    pub cache_hit_rate: f64,
531    /// Resolution success rate (0.0 to 1.0)
532    pub success_rate: f64,
533    /// Average time per resolution
534    pub average_resolve_time: Duration,
535    /// Current cache size
536    pub current_cache_size: usize,
537}
538
539impl Default for ResolverConfig {
540    fn default() -> Self {
541        Self {
542            enable_caching: true,
543            max_cache_size: 10000,
544            symbol_search_paths: Vec::new(),
545            enable_demangling: true,
546            max_resolve_time: Duration::from_millis(100),
547            eager_loading: false,
548        }
549    }
550}
551
552impl Default for PlatformSymbolResolver {
553    fn default() -> Self {
554        Self::new()
555    }
556}
557
558impl std::fmt::Display for ResolveError {
559    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560        match self {
561            ResolveError::UnsupportedPlatform => {
562                write!(f, "Platform not supported for symbol resolution")
563            }
564            ResolveError::SymbolNotFound => write!(f, "Symbol not found"),
565            ResolveError::NoDebugInfo => write!(f, "Debug information not available"),
566            ResolveError::FileAccessError(msg) => write!(f, "File access error: {}", msg),
567            ResolveError::ParseError(msg) => write!(f, "Parse error: {}", msg),
568            ResolveError::Timeout => write!(f, "Symbol resolution timed out"),
569            ResolveError::MemoryError => write!(f, "Memory error during symbol resolution"),
570            ResolveError::Unknown(msg) => write!(f, "Unknown error: {}", msg),
571        }
572    }
573}
574
575impl std::error::Error for ResolveError {}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn test_symbol_resolver_creation() {
583        let resolver = PlatformSymbolResolver::new();
584        assert!(!resolver.platform_context.initialized);
585        assert!(resolver.symbol_cache.is_empty());
586
587        let stats = resolver.get_statistics();
588        assert_eq!(stats.total_resolutions, 0);
589        assert_eq!(stats.cache_hit_rate, 0.0);
590    }
591
592    #[test]
593    fn test_resolver_initialization() {
594        let mut resolver = PlatformSymbolResolver::new();
595        let result = resolver.initialize();
596
597        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
598        assert!(result.is_ok());
599
600        #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
601        assert_eq!(result, Err(ResolveError::UnsupportedPlatform));
602    }
603
604    #[test]
605    fn test_symbol_resolution() {
606        let mut resolver = PlatformSymbolResolver::new();
607        let _ = resolver.initialize();
608
609        let address = 0x12345678;
610        let result = resolver.resolve_symbol(address);
611
612        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
613        {
614            if resolver.platform_context.initialized {
615                assert!(result.is_ok());
616                let symbol = result.expect("Symbol should resolve");
617                assert!(!symbol.name.is_empty());
618                assert!(symbol.line_number.is_some());
619            }
620        }
621    }
622
623    #[test]
624    fn test_symbol_caching() {
625        let mut resolver = PlatformSymbolResolver::new();
626        let _ = resolver.initialize();
627
628        let address = 0x12345678;
629
630        // First resolution should be cache miss
631        let _ = resolver.resolve_symbol(address);
632        let stats1 = resolver.get_statistics();
633
634        // Second resolution should be cache hit
635        let _ = resolver.resolve_symbol(address);
636        let stats2 = resolver.get_statistics();
637
638        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
639        {
640            if resolver.platform_context.initialized {
641                assert!(stats2.cache_hits > stats1.cache_hits);
642                assert!(stats2.cache_hit_rate > 0.0);
643            }
644        }
645    }
646
647    #[test]
648    fn test_batch_resolution() {
649        let mut resolver = PlatformSymbolResolver::new();
650        let _ = resolver.initialize();
651
652        let addresses = vec![0x12345678, 0x87654321, 0xabcdef00];
653        let results = resolver.resolve_batch(&addresses);
654
655        assert_eq!(results.len(), 3);
656
657        #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
658        {
659            if resolver.platform_context.initialized {
660                for result in results {
661                    assert!(result.is_ok());
662                }
663            }
664        }
665    }
666
667    #[test]
668    fn test_module_info() {
669        let mut resolver = PlatformSymbolResolver::new();
670        let _ = resolver.initialize();
671
672        let address = 0x12345678;
673        let module = resolver.get_module_info(address);
674
675        assert!(module.is_some());
676        let module = module.expect("Module should exist");
677        assert!(!module.name.is_empty());
678        assert!(module.size > 0);
679    }
680}