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 None
168 }
169
170 fn demangle_symbol(&self, _mangled: &str) -> Option<String> {
171 None
174 }
175
176 fn lookup_line_info(&self, _address: usize) -> (Option<String>, Option<u32>, Option<u32>) {
177 (None, None, None)
180 }
181
182 fn lookup_module_name(&self, _address: usize) -> Option<String> {
183 None
186 }
187
188 fn calculate_offset(&self, _address: usize, _symbol_name: &str) -> Option<usize> {
189 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
265 #[test]
266 fn test_resolved_frame_display_no_column() {
267 let frame = ResolvedFrame {
268 instruction_pointer: 0x1234,
269 symbol_name: "test_func".to_string(),
270 demangled_name: Some("test_func".to_string()),
271 filename: Some("lib.rs".to_string()),
272 line_number: Some(10),
273 column: None,
274 module_name: None,
275 offset: None,
276 };
277
278 assert_eq!(frame.display_name(), "test_func at lib.rs:10");
279 }
280
281 #[test]
282 fn test_resolved_frame_display_no_line_info() {
283 let frame = ResolvedFrame {
284 instruction_pointer: 0x5678,
285 symbol_name: "unknown_func".to_string(),
286 demangled_name: None,
287 filename: None,
288 line_number: None,
289 column: None,
290 module_name: None,
291 offset: None,
292 };
293
294 assert_eq!(frame.display_name(), "unknown_func");
295 assert_eq!(frame.short_display(), "unknown_func");
296 assert!(!frame.has_line_info());
297 }
298
299 #[test]
300 fn test_resolved_frame_is_rust_symbol_mangled() {
301 let frame = ResolvedFrame {
302 instruction_pointer: 0x1000,
303 symbol_name: "_ZN4test4main17habcdef123456E".to_string(),
304 demangled_name: None,
305 filename: None,
306 line_number: None,
307 column: None,
308 module_name: None,
309 offset: None,
310 };
311
312 assert!(frame.is_rust_symbol());
313 }
314
315 #[test]
316 fn test_resolved_frame_is_rust_symbol_demangled() {
317 let frame = ResolvedFrame {
318 instruction_pointer: 0x1000,
319 symbol_name: "func".to_string(),
320 demangled_name: Some("std::collections::HashMap::new".to_string()),
321 filename: None,
322 line_number: None,
323 column: None,
324 module_name: None,
325 offset: None,
326 };
327
328 assert!(frame.is_rust_symbol());
329 }
330
331 #[test]
332 fn test_resolved_frame_is_system_symbol_libc() {
333 let frame = ResolvedFrame {
334 instruction_pointer: 0x2000,
335 symbol_name: "malloc".to_string(),
336 demangled_name: None,
337 filename: None,
338 line_number: None,
339 column: None,
340 module_name: Some("libc.so.6".to_string()),
341 offset: None,
342 };
343
344 assert!(frame.is_system_symbol());
345 }
346
347 #[test]
348 fn test_resolved_frame_is_system_symbol_pthread() {
349 let frame = ResolvedFrame {
350 instruction_pointer: 0x3000,
351 symbol_name: "pthread_create".to_string(),
352 demangled_name: None,
353 filename: None,
354 line_number: None,
355 column: None,
356 module_name: Some("libpthread.so.0".to_string()),
357 offset: None,
358 };
359
360 assert!(frame.is_system_symbol());
361 }
362
363 #[test]
364 fn test_resolved_frame_is_system_symbol_ld() {
365 let frame = ResolvedFrame {
366 instruction_pointer: 0x4000,
367 symbol_name: "_start".to_string(),
368 demangled_name: None,
369 filename: None,
370 line_number: None,
371 column: None,
372 module_name: Some("ld-linux-x86-64.so.2".to_string()),
373 offset: None,
374 };
375
376 assert!(frame.is_system_symbol());
377 }
378
379 #[test]
380 fn test_resolved_frame_not_system_symbol() {
381 let frame = ResolvedFrame {
382 instruction_pointer: 0x5000,
383 symbol_name: "my_function".to_string(),
384 demangled_name: None,
385 filename: None,
386 line_number: None,
387 column: None,
388 module_name: Some("my_app".to_string()),
389 offset: None,
390 };
391
392 assert!(!frame.is_system_symbol());
393 }
394
395 #[test]
396 fn test_symbol_resolver_new() {
397 let resolver = SymbolResolver::new();
398 assert!(resolver.symbol_cache.is_empty());
399 assert_eq!(resolver.cache_size(), 0);
400 }
401
402 #[test]
403 fn test_symbol_resolver_default() {
404 let resolver = SymbolResolver::default();
405 assert!(resolver.symbol_cache.is_empty());
406 }
407
408 #[test]
409 fn test_symbol_resolver_with_options() {
410 let resolver = SymbolResolver::with_options(false, false);
411 assert!(!resolver.enable_demangling);
412 assert!(!resolver.enable_line_info);
413 }
414
415 #[test]
416 fn test_symbol_resolver_cache_stats_initial() {
417 let resolver = SymbolResolver::new();
418 let (resolutions, hits, ratio) = resolver.get_cache_stats();
419
420 assert_eq!(resolutions, 0);
421 assert_eq!(hits, 0);
422 assert_eq!(ratio, 0.0);
423 }
424
425 #[test]
426 fn test_symbol_resolver_clear_cache() {
427 let mut resolver = SymbolResolver::new();
428 resolver.resolution_count.store(10, Ordering::Relaxed);
429 resolver.cache_hits.store(5, Ordering::Relaxed);
430
431 resolver.clear_cache();
432
433 let (resolutions, hits, _) = resolver.get_cache_stats();
434 assert_eq!(resolutions, 0);
435 assert_eq!(hits, 0);
436 assert_eq!(resolver.cache_size(), 0);
437 }
438
439 #[test]
440 fn test_symbol_resolver_resolve_frame_no_symbol() {
441 let mut resolver = SymbolResolver::new();
442 let frame = StackFrame::new(0x12345678);
443
444 let result = resolver.resolve_frame(&frame);
445 assert!(result.is_none());
446
447 let (resolutions, hits, _) = resolver.get_cache_stats();
448 assert_eq!(resolutions, 1);
449 assert_eq!(hits, 0);
450 }
451
452 #[test]
453 fn test_symbol_resolver_resolve_batch() {
454 let mut resolver = SymbolResolver::new();
455 let frames = vec![
456 StackFrame::new(0x1000),
457 StackFrame::new(0x2000),
458 StackFrame::new(0x3000),
459 ];
460
461 let results = resolver.resolve_batch(&frames);
462 assert_eq!(results.len(), 3);
463 assert!(results.iter().all(|r| r.is_none()));
464 }
465
466 #[test]
467 fn test_symbol_resolver_resolve_addresses() {
468 let mut resolver = SymbolResolver::new();
469 let addresses = vec![0x1000, 0x2000, 0x3000];
470
471 let results = resolver.resolve_addresses(&addresses);
472 assert_eq!(results.len(), 3);
473 assert!(results.iter().all(|r| r.is_none()));
474 }
475
476 #[test]
477 fn test_symbol_resolver_preload_symbols() {
478 let mut resolver = SymbolResolver::new();
479 let addresses = vec![0x1000, 0x2000];
480
481 resolver.preload_symbols(&addresses);
482 assert_eq!(resolver.cache_size(), 0);
483 }
484
485 #[test]
486 fn test_resolved_frame_clone() {
487 let frame = ResolvedFrame {
488 instruction_pointer: 0x1234,
489 symbol_name: "test".to_string(),
490 demangled_name: Some("test_demangled".to_string()),
491 filename: Some("test.rs".to_string()),
492 line_number: Some(1),
493 column: Some(2),
494 module_name: Some("test_mod".to_string()),
495 offset: Some(3),
496 };
497
498 let cloned = frame.clone();
499 assert_eq!(cloned.instruction_pointer, frame.instruction_pointer);
500 assert_eq!(cloned.symbol_name, frame.symbol_name);
501 }
502
503 #[test]
504 fn test_resolved_frame_debug() {
505 let frame = ResolvedFrame {
506 instruction_pointer: 0x1234,
507 symbol_name: "test".to_string(),
508 demangled_name: None,
509 filename: None,
510 line_number: None,
511 column: None,
512 module_name: None,
513 offset: None,
514 };
515
516 let debug_str = format!("{:?}", frame);
517 assert!(debug_str.contains("ResolvedFrame"));
518 assert!(debug_str.contains("instruction_pointer"));
519 }
520
521 #[test]
522 fn test_resolved_frame_core_symbol() {
523 let frame = ResolvedFrame {
524 instruction_pointer: 0x1000,
525 symbol_name: "core_func".to_string(),
526 demangled_name: Some("core::ptr::drop_in_place".to_string()),
527 filename: None,
528 line_number: None,
529 column: None,
530 module_name: None,
531 offset: None,
532 };
533
534 assert!(frame.is_rust_symbol());
535 }
536}