Skip to main content

memscope_rs/capture/platform/
symbol_resolver.rs

1#![allow(warnings, unused)]
2
3use std::collections::HashMap;
4use std::path::PathBuf;
5use std::time::{Duration, Instant};
6
7/// Platform-specific symbol resolution
8pub struct PlatformSymbolResolver {
9    /// Resolver configuration
10    config: ResolverConfig,
11    /// Symbol cache
12    symbol_cache: HashMap<usize, CachedSymbol>,
13    /// Module information cache
14    module_cache: HashMap<usize, ModuleInfo>,
15    /// Performance statistics
16    stats: ResolverStats,
17    /// Platform-specific context
18    platform_context: ResolverContext,
19}
20
21/// Configuration for symbol resolution
22#[derive(Debug, Clone)]
23pub struct ResolverConfig {
24    /// Whether to enable symbol caching
25    pub enable_caching: bool,
26    /// Maximum cache size
27    pub max_cache_size: usize,
28    /// Search paths for debug symbols
29    pub symbol_search_paths: Vec<PathBuf>,
30    /// Whether to perform demangling
31    pub enable_demangling: bool,
32    /// Maximum time to spend resolving a symbol
33    pub max_resolve_time: Duration,
34    /// Whether to load symbols eagerly
35    pub eager_loading: bool,
36}
37
38/// Platform-specific resolver context
39#[derive(Debug)]
40struct ResolverContext {
41    /// Whether resolver is initialized
42    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/// Linux-specific resolver context
55#[cfg(target_os = "linux")]
56#[derive(Debug)]
57struct LinuxResolverContext {
58    /// Whether addr2line is available
59    addr2line_available: bool,
60    /// Whether DWARF debug info is loaded
61    dwarf_loaded: bool,
62}
63
64/// Windows-specific resolver context
65#[cfg(target_os = "windows")]
66#[derive(Debug)]
67struct WindowsResolverContext {
68    /// Whether symbol APIs are initialized
69    symbols_initialized: bool,
70    /// Whether PDB files are loaded
71    pdb_loaded: bool,
72    /// Symbol search paths
73    symbol_paths: Vec<PathBuf>,
74}
75
76/// macOS-specific resolver context
77#[cfg(target_os = "macos")]
78#[derive(Debug)]
79struct MacOSResolverContext {
80    /// Whether atos utility is available
81    atos_available: bool,
82    /// Whether dSYM files are loaded
83    dsym_loaded: bool,
84}
85
86/// Cached symbol information
87#[derive(Debug, Clone)]
88struct CachedSymbol {
89    /// Symbol information
90    symbol: SymbolInfo,
91    /// Access count
92    access_count: usize,
93}
94
95/// Detailed symbol information
96#[derive(Debug, Clone)]
97pub struct SymbolInfo {
98    /// Symbol name
99    pub name: String,
100    /// Demangled name if available
101    pub demangled_name: Option<String>,
102    /// Source file path
103    pub file_path: Option<PathBuf>,
104    /// Line number in source
105    pub line_number: Option<u32>,
106    /// Column number
107    pub column_number: Option<u32>,
108    /// Function start address
109    pub function_start: Option<usize>,
110    /// Function size
111    pub function_size: Option<usize>,
112    /// Module/library name
113    pub module_name: Option<String>,
114    /// Compilation unit
115    pub compilation_unit: Option<String>,
116}
117
118/// Module/library information
119#[derive(Debug, Clone)]
120pub struct ModuleInfo {
121    /// Module name
122    pub name: String,
123    /// Base address where module is loaded
124    pub base_address: usize,
125    /// Size of module
126    pub size: usize,
127    /// Path to module file
128    pub file_path: PathBuf,
129    /// Whether debug symbols are available
130    pub has_symbols: bool,
131    /// Symbol file path if separate
132    pub symbol_file: Option<PathBuf>,
133}
134
135/// Performance statistics for symbol resolution
136#[derive(Debug)]
137struct ResolverStats {
138    /// Total resolution attempts
139    total_resolutions: std::sync::atomic::AtomicUsize,
140    /// Successful resolutions
141    successful_resolutions: std::sync::atomic::AtomicUsize,
142    /// Cache hits
143    cache_hits: std::sync::atomic::AtomicUsize,
144    /// Total resolution time
145    total_resolve_time: std::sync::atomic::AtomicU64,
146}
147
148/// Errors that can occur during symbol resolution
149#[derive(Debug, Clone, PartialEq)]
150pub enum ResolveError {
151    /// Platform not supported
152    UnsupportedPlatform,
153    /// Symbol not found
154    SymbolNotFound,
155    /// Debug information not available
156    NoDebugInfo,
157    /// File access error
158    FileAccessError(String),
159    /// Parse error
160    ParseError(String),
161    /// Timeout during resolution
162    Timeout,
163    /// Memory error
164    MemoryError,
165    /// Unknown error
166    Unknown(String),
167}
168
169impl PlatformSymbolResolver {
170    /// Create new symbol resolver
171    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    /// Initialize symbol resolver for current platform
182    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    /// Resolve symbol for given address
205    pub fn resolve_symbol(&mut self, address: usize) -> Result<SymbolInfo, ResolveError> {
206        let start_time = Instant::now();
207
208        // Check cache first
209        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        // Perform resolution
220        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        // Update statistics
228        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            // Cache successful resolution
239            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    /// Resolve symbols for multiple addresses in batch
254    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    /// Get module information for address
262    pub fn get_module_info(&mut self, address: usize) -> Option<ModuleInfo> {
263        // Check cache first
264        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        // Load module info
271        self.load_module_info(address)
272    }
273
274    /// Get resolver statistics
275    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    /// Clear symbol cache
319    pub fn clear_cache(&mut self) {
320        self.symbol_cache.clear();
321    }
322
323    /// Update resolver configuration
324    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        // Initialize Linux symbol resolution using addr2line, DWARF, etc.
349        self.platform_context.linux_context.addr2line_available = true; // Simplified
350        self.platform_context.linux_context.dwarf_loaded = true; // Simplified
351        self.platform_context.initialized = true;
352        Ok(())
353    }
354
355    #[cfg(target_os = "windows")]
356    fn initialize_windows(&mut self) -> Result<(), ResolveError> {
357        // Initialize Windows symbol resolution using dbghelp.dll
358        self.platform_context.windows_context.symbols_initialized = true; // Simplified
359        self.platform_context.windows_context.pdb_loaded = true; // Simplified
360        self.platform_context.initialized = true;
361        Ok(())
362    }
363
364    #[cfg(target_os = "macos")]
365    fn initialize_macos(&mut self) -> Result<(), ResolveError> {
366        // Initialize macOS symbol resolution using atos, dSYM files
367        self.platform_context.macos_context.atos_available = true; // Simplified
368        self.platform_context.macos_context.dsym_loaded = true; // Simplified
369        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        // Linux symbol resolution using dladdr API
376        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            // Extract module_name before moving file_path
405            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), // dladdr returns demangled names on Linux
412                file_path,
413                line_number: None, // dladdr doesn't provide line numbers, would need DWARF parsing
414                column_number: None,
415                function_start: Some(base_address),
416                function_size: None, // Not available from dladdr
417                module_name,
418                compilation_unit: None, // Not available from dladdr
419            })
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                    // Convert i8 to u16 for Windows API
461                    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        // macOS symbol resolution using dladdr API
495        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            // Extract module_name before moving file_path
524            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), // dladdr returns demangled names on macOS
531                file_path: file_path.clone(),
532                line_number: None, // dladdr doesn't provide line numbers, would need dSYM + atos
533                column_number: None,
534                function_start: Some(base_address),
535                function_size: None, // Not available from dladdr
536                module_name,
537                compilation_unit: None, // Not available from dladdr
538            })
539        }
540    }
541
542    fn load_module_info(&mut self, _address: usize) -> Option<ModuleInfo> {
543        // Platform-specific module loading
544        // This feature is not yet implemented - would require:
545        // - Linux: reading /proc/self/maps or dl_iterate_phdr
546        // - Windows: EnumProcessModules or CreateToolhelp32Snapshot
547        // - macOS: _dyld_image_count and _dyld_get_image_name
548        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/// Statistics about symbol resolution performance
588#[derive(Debug, Clone)]
589pub struct ResolverStatistics {
590    /// Total resolution attempts
591    pub total_resolutions: usize,
592    /// Number of successful resolutions
593    pub successful_resolutions: usize,
594    /// Number of failed resolutions
595    pub failed_resolutions: usize,
596    /// Number of cache hits
597    pub cache_hits: usize,
598    /// Number of cache misses
599    pub cache_misses: usize,
600    /// Cache hit rate (0.0 to 1.0)
601    pub cache_hit_rate: f64,
602    /// Resolution success rate (0.0 to 1.0)
603    pub success_rate: f64,
604    /// Average time per resolution
605    pub average_resolve_time: Duration,
606    /// Current cache size
607    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        // Use real function address instead of hardcoded value
681        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                // On Linux, dladdr should find at least some info
688                // Line numbers may not be available from dladdr
689                match result {
690                    Ok(symbol) => {
691                        assert!(!symbol.name.is_empty());
692                        // Line number is optional from dladdr
693                    }
694                    Err(_) => {
695                        // Symbol resolution may fail in some environments
696                        // This is acceptable as long as the error is handled gracefully
697                    }
698                }
699            }
700        }
701    }
702
703    #[test]
704    fn test_symbol_caching() {
705        let mut resolver = PlatformSymbolResolver::new();
706        let _ = resolver.initialize();
707
708        // Use real function address
709        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                // Cache hits should increase after second call
721                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        // Use real function addresses
732        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                // At least some symbols should resolve successfully
745                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        // Use real function address
757        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                // Module info may or may not be available depending on dladdr
764                if let Some(module) = module {
765                    assert!(!module.name.is_empty());
766                    assert!(module.size > 0);
767                }
768                // It's also acceptable if module info is not available
769            }
770        }
771    }
772}