Skip to main content

memscope_rs/metadata/stack_trace/
resolver.rs

1use super::StackFrame;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5/// Fully resolved stack frame with symbol information
6#[derive(Debug, Clone)]
7pub struct ResolvedFrame {
8    /// Original instruction pointer address
9    pub instruction_pointer: usize,
10    /// Raw symbol name from debug info
11    pub symbol_name: String,
12    /// Human-readable demangled symbol name
13    pub demangled_name: Option<String>,
14    /// Source code filename
15    pub filename: Option<String>,
16    /// Line number in source file
17    pub line_number: Option<u32>,
18    /// Column number in source line
19    pub column: Option<u32>,
20    /// Name of module/library containing this symbol
21    pub module_name: Option<String>,
22    /// Offset from symbol start
23    pub offset: Option<usize>,
24}
25
26/// High-performance symbol resolver with caching
27pub struct SymbolResolver {
28    /// Cache of resolved symbols by instruction pointer
29    symbol_cache: HashMap<usize, ResolvedFrame>,
30    /// Total number of resolution attempts
31    resolution_count: AtomicUsize,
32    /// Number of cache hits for performance tracking
33    cache_hits: AtomicUsize,
34    /// Whether to perform symbol demangling
35    enable_demangling: bool,
36    /// Whether to resolve line number information
37    enable_line_info: bool,
38}
39
40impl SymbolResolver {
41    /// Create new symbol resolver with default settings
42    pub fn new() -> Self {
43        Self {
44            symbol_cache: HashMap::new(),
45            resolution_count: AtomicUsize::new(0),
46            cache_hits: AtomicUsize::new(0),
47            enable_demangling: true,
48            enable_line_info: true,
49        }
50    }
51
52    /// Create symbol resolver with custom options
53    pub fn with_options(enable_demangling: bool, enable_line_info: bool) -> Self {
54        Self {
55            symbol_cache: HashMap::new(),
56            resolution_count: AtomicUsize::new(0),
57            cache_hits: AtomicUsize::new(0),
58            enable_demangling,
59            enable_line_info,
60        }
61    }
62
63    /// Resolve single stack frame to symbol information
64    /// Uses cache for performance, falls back to symbol lookup
65    pub fn resolve_frame(&mut self, frame: &StackFrame) -> Option<ResolvedFrame> {
66        self.resolution_count.fetch_add(1, Ordering::Relaxed);
67
68        // Check cache first
69        if let Some(cached) = self.symbol_cache.get(&frame.instruction_pointer) {
70            self.cache_hits.fetch_add(1, Ordering::Relaxed);
71            return Some(cached.clone());
72        }
73
74        // Resolve symbol information
75        let resolved = self.perform_resolution(frame.instruction_pointer)?;
76
77        // Cache the result
78        self.symbol_cache
79            .insert(frame.instruction_pointer, resolved.clone());
80
81        Some(resolved)
82    }
83
84    pub fn resolve_batch(&mut self, frames: &[StackFrame]) -> Vec<Option<ResolvedFrame>> {
85        frames
86            .iter()
87            .map(|frame| self.resolve_frame(frame))
88            .collect()
89    }
90
91    pub fn resolve_addresses(&mut self, addresses: &[usize]) -> Vec<Option<ResolvedFrame>> {
92        addresses
93            .iter()
94            .map(|&addr| {
95                let frame = StackFrame::new(addr);
96                self.resolve_frame(&frame)
97            })
98            .collect()
99    }
100
101    pub fn get_cache_stats(&self) -> (usize, usize, f64) {
102        let resolutions = self.resolution_count.load(Ordering::Relaxed);
103        let hits = self.cache_hits.load(Ordering::Relaxed);
104        let hit_ratio = if resolutions > 0 {
105            hits as f64 / resolutions as f64
106        } else {
107            0.0
108        };
109        (resolutions, hits, hit_ratio)
110    }
111
112    pub fn clear_cache(&mut self) {
113        self.symbol_cache.clear();
114        self.resolution_count.store(0, Ordering::Relaxed);
115        self.cache_hits.store(0, Ordering::Relaxed);
116    }
117
118    pub fn cache_size(&self) -> usize {
119        self.symbol_cache.len()
120    }
121
122    pub fn preload_symbols(&mut self, addresses: &[usize]) {
123        for &addr in addresses {
124            if !self.symbol_cache.contains_key(&addr) {
125                if let Some(resolved) = self.perform_resolution(addr) {
126                    self.symbol_cache.insert(addr, resolved);
127                }
128            }
129        }
130    }
131
132    fn perform_resolution(&self, address: usize) -> Option<ResolvedFrame> {
133        // This would be platform-specific symbol resolution
134        // For now, provide a mock implementation
135
136        let symbol_name = self.lookup_symbol_name(address)?;
137        let demangled_name = if self.enable_demangling {
138            self.demangle_symbol(&symbol_name)
139        } else {
140            None
141        };
142
143        let (filename, line_number, column) = if self.enable_line_info {
144            self.lookup_line_info(address)
145        } else {
146            (None, None, None)
147        };
148
149        let module_name = self.lookup_module_name(address);
150        let offset = self.calculate_offset(address, &symbol_name);
151
152        Some(ResolvedFrame {
153            instruction_pointer: address,
154            symbol_name,
155            demangled_name,
156            filename,
157            line_number,
158            column,
159            module_name,
160            offset,
161        })
162    }
163
164    fn lookup_symbol_name(&self, _address: usize) -> Option<String> {
165        // Real implementation would use debug info (DWARF, PDB, dSYM)
166        // This feature is not yet implemented
167        None
168    }
169
170    fn demangle_symbol(&self, _mangled: &str) -> Option<String> {
171        // Real implementation should use rustc-demangle crate
172        // This feature is not yet implemented
173        None
174    }
175
176    fn lookup_line_info(&self, _address: usize) -> (Option<String>, Option<u32>, Option<u32>) {
177        // Real implementation would use addr2line or debug info
178        // This feature is not yet implemented
179        (None, None, None)
180    }
181
182    fn lookup_module_name(&self, _address: usize) -> Option<String> {
183        // Real implementation would use dladdr or equivalent
184        // This feature is not yet implemented
185        None
186    }
187
188    fn calculate_offset(&self, _address: usize, _symbol_name: &str) -> Option<usize> {
189        // Real implementation would calculate offset within symbol based on debug info
190        // This feature is not yet implemented
191        None
192    }
193}
194
195impl Default for SymbolResolver {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201impl ResolvedFrame {
202    pub fn display_name(&self) -> String {
203        let name = self.demangled_name.as_ref().unwrap_or(&self.symbol_name);
204
205        if let (Some(file), Some(line)) = (&self.filename, self.line_number) {
206            if let Some(col) = self.column {
207                format!("{} at {}:{}:{}", name, file, line, col)
208            } else {
209                format!("{} at {}:{}", name, file, line)
210            }
211        } else {
212            name.clone()
213        }
214    }
215
216    pub fn short_display(&self) -> String {
217        self.demangled_name
218            .as_ref()
219            .unwrap_or(&self.symbol_name)
220            .clone()
221    }
222
223    pub fn has_line_info(&self) -> bool {
224        self.filename.is_some() && self.line_number.is_some()
225    }
226
227    pub fn is_rust_symbol(&self) -> bool {
228        self.symbol_name.starts_with("_ZN")
229            || self.demangled_name.as_ref().is_some_and(|name| {
230                name.contains("::") || name.starts_with("std::") || name.starts_with("core::")
231            })
232    }
233
234    pub fn is_system_symbol(&self) -> bool {
235        self.module_name.as_ref().is_some_and(|module| {
236            module.contains("libc") || module.contains("libpthread") || module.contains("ld-")
237        })
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_resolved_frame_display() {
247        let frame = ResolvedFrame {
248            instruction_pointer: 0x1234,
249            symbol_name: "_ZN4main17h1234567890abcdefE".to_string(),
250            demangled_name: Some("main".to_string()),
251            filename: Some("src/main.rs".to_string()),
252            line_number: Some(42),
253            column: Some(10),
254            module_name: Some("main_executable".to_string()),
255            offset: Some(16),
256        };
257
258        assert_eq!(frame.display_name(), "main at src/main.rs:42:10");
259        assert_eq!(frame.short_display(), "main");
260        assert!(frame.has_line_info());
261        assert!(frame.is_rust_symbol());
262        assert!(!frame.is_system_symbol());
263    }
264}