1use super::StackFrame;
2use std::collections::HashMap;
3use std::sync::atomic::{AtomicUsize, Ordering};
4
5#[derive(Debug, Clone)]
7pub struct ResolvedFrame {
8 pub instruction_pointer: usize,
10 pub symbol_name: String,
12 pub demangled_name: Option<String>,
14 pub filename: Option<String>,
16 pub line_number: Option<u32>,
18 pub column: Option<u32>,
20 pub module_name: Option<String>,
22 pub offset: Option<usize>,
24}
25
26pub struct SymbolResolver {
28 symbol_cache: HashMap<usize, ResolvedFrame>,
30 resolution_count: AtomicUsize,
32 cache_hits: AtomicUsize,
34 enable_demangling: bool,
36 enable_line_info: bool,
38}
39
40impl SymbolResolver {
41 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 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 pub fn resolve_frame(&mut self, frame: &StackFrame) -> Option<ResolvedFrame> {
66 self.resolution_count.fetch_add(1, Ordering::Relaxed);
67
68 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 let resolved = self.perform_resolution(frame.instruction_pointer)?;
76
77 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 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 match address % 7 {
167 0 => Some("_ZN4main17h1234567890abcdefE".to_string()),
168 1 => Some("_ZN9allocator8allocate17h9876543210fedcbaE".to_string()),
169 2 => Some("process_data".to_string()),
170 3 => Some("_ZN3std6thread6spawn17habcdef1234567890E".to_string()),
171 4 => Some("handle_request".to_string()),
172 5 => Some("_ZN4core3ptr8drop_in_place17h1111222233334444E".to_string()),
173 _ => Some(format!("unknown_symbol_{:x}", address)),
174 }
175 }
176
177 fn demangle_symbol(&self, mangled: &str) -> Option<String> {
178 if mangled.starts_with("_ZN") {
180 if mangled.contains("main") {
182 Some("main".to_string())
183 } else if mangled.contains("allocate") {
184 Some("allocator::allocate".to_string())
185 } else if mangled.contains("spawn") {
186 Some("std::thread::spawn".to_string())
187 } else if mangled.contains("drop_in_place") {
188 Some("core::ptr::drop_in_place".to_string())
189 } else {
190 Some(format!("demangled({})", mangled))
191 }
192 } else {
193 None
194 }
195 }
196
197 fn lookup_line_info(&self, address: usize) -> (Option<String>, Option<u32>, Option<u32>) {
198 let file = match address % 4 {
200 0 => Some("src/main.rs".to_string()),
201 1 => Some("src/allocator.rs".to_string()),
202 2 => Some("src/process.rs".to_string()),
203 _ => Some("src/lib.rs".to_string()),
204 };
205
206 let line = Some((address % 500) as u32 + 1);
207 let column = Some((address % 80) as u32 + 1);
208
209 (file, line, column)
210 }
211
212 fn lookup_module_name(&self, address: usize) -> Option<String> {
213 match address % 3 {
215 0 => Some("main_executable".to_string()),
216 1 => Some("libstd.so".to_string()),
217 _ => Some("libcore.so".to_string()),
218 }
219 }
220
221 fn calculate_offset(&self, address: usize, _symbol_name: &str) -> Option<usize> {
222 Some(address % 256)
224 }
225}
226
227impl Default for SymbolResolver {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233impl ResolvedFrame {
234 pub fn display_name(&self) -> String {
235 let name = self.demangled_name.as_ref().unwrap_or(&self.symbol_name);
236
237 if let (Some(file), Some(line)) = (&self.filename, self.line_number) {
238 if let Some(col) = self.column {
239 format!("{} at {}:{}:{}", name, file, line, col)
240 } else {
241 format!("{} at {}:{}", name, file, line)
242 }
243 } else {
244 name.clone()
245 }
246 }
247
248 pub fn short_display(&self) -> String {
249 self.demangled_name
250 .as_ref()
251 .unwrap_or(&self.symbol_name)
252 .clone()
253 }
254
255 pub fn has_line_info(&self) -> bool {
256 self.filename.is_some() && self.line_number.is_some()
257 }
258
259 pub fn is_rust_symbol(&self) -> bool {
260 self.symbol_name.starts_with("_ZN")
261 || self.demangled_name.as_ref().is_some_and(|name| {
262 name.contains("::") || name.starts_with("std::") || name.starts_with("core::")
263 })
264 }
265
266 pub fn is_system_symbol(&self) -> bool {
267 self.module_name.as_ref().is_some_and(|module| {
268 module.contains("libc") || module.contains("libpthread") || module.contains("ld-")
269 })
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_basic_resolution() {
279 let mut resolver = SymbolResolver::new();
280 let frame = StackFrame::new(0x12345678);
281
282 let resolved = resolver.resolve_frame(&frame);
283 assert!(resolved.is_some());
284
285 let resolved = resolved.expect("Should resolve");
286 assert_eq!(resolved.instruction_pointer, 0x12345678);
287 assert!(!resolved.symbol_name.is_empty());
288 }
289
290 #[test]
291 fn test_cache_functionality() {
292 let mut resolver = SymbolResolver::new();
293 let frame = StackFrame::new(0x12345678);
294
295 let resolved1 = resolver.resolve_frame(&frame);
297 assert!(resolved1.is_some());
298
299 let resolved2 = resolver.resolve_frame(&frame);
301 assert!(resolved2.is_some());
302
303 let (resolutions, hits, hit_ratio) = resolver.get_cache_stats();
304 assert_eq!(resolutions, 2);
305 assert_eq!(hits, 1);
306 assert!((hit_ratio - 0.5).abs() < 0.01);
307 }
308
309 #[test]
310 fn test_batch_resolution() {
311 let mut resolver = SymbolResolver::new();
312 let frames = vec![
313 StackFrame::new(0x1000),
314 StackFrame::new(0x2000),
315 StackFrame::new(0x3000),
316 ];
317
318 let resolved = resolver.resolve_batch(&frames);
319 assert_eq!(resolved.len(), 3);
320
321 for result in resolved {
322 assert!(result.is_some());
323 }
324 }
325
326 #[test]
327 fn test_demangling() {
328 let resolver = SymbolResolver::new();
329
330 let demangled = resolver.demangle_symbol("_ZN4main17h1234567890abcdefE");
331 assert_eq!(demangled, Some("main".to_string()));
332
333 let not_mangled = resolver.demangle_symbol("regular_function");
334 assert_eq!(not_mangled, None);
335 }
336
337 #[test]
338 fn test_resolved_frame_display() {
339 let frame = ResolvedFrame {
340 instruction_pointer: 0x1234,
341 symbol_name: "_ZN4main17h1234567890abcdefE".to_string(),
342 demangled_name: Some("main".to_string()),
343 filename: Some("src/main.rs".to_string()),
344 line_number: Some(42),
345 column: Some(10),
346 module_name: Some("main_executable".to_string()),
347 offset: Some(16),
348 };
349
350 assert_eq!(frame.display_name(), "main at src/main.rs:42:10");
351 assert_eq!(frame.short_display(), "main");
352 assert!(frame.has_line_info());
353 assert!(frame.is_rust_symbol());
354 assert!(!frame.is_system_symbol());
355 }
356
357 #[test]
358 fn test_preload_symbols() {
359 let mut resolver = SymbolResolver::new();
360 let addresses = vec![0x1000, 0x2000, 0x3000];
361
362 assert_eq!(resolver.cache_size(), 0);
363
364 resolver.preload_symbols(&addresses);
365 assert_eq!(resolver.cache_size(), 3);
366 }
367
368 #[test]
369 fn test_clear_cache() {
370 let mut resolver = SymbolResolver::new();
371 let frame = StackFrame::new(0x12345678);
372
373 resolver.resolve_frame(&frame);
374 assert!(resolver.cache_size() > 0);
375
376 resolver.clear_cache();
377 assert_eq!(resolver.cache_size(), 0);
378
379 let (resolutions, hits, _) = resolver.get_cache_stats();
380 assert_eq!(resolutions, 0);
381 assert_eq!(hits, 0);
382 }
383}