1use async_trait::async_trait;
41use cachemap2::CacheMap;
42use debugid::{CodeId, DebugId};
43use futures_util::lock::Mutex as FutMutex;
44use tracing::trace;
45
46use std::collections::HashMap;
47use std::fs;
48use std::path::PathBuf;
49use std::sync::Mutex;
50use std::{borrow::Cow, sync::Arc};
51
52pub use minidump_common::{traits::Module, utils::basename};
53pub use sym_file::walker;
54
55pub use crate::sym_file::{CfiRules, SymbolFile};
56
57#[cfg(feature = "http")]
58pub mod http;
59mod sym_file;
60
61#[cfg(feature = "http")]
62pub use http::*;
63
64#[doc(hidden)]
66#[cfg(feature = "fuzz")]
67pub mod fuzzing_private_exports {
68 pub use crate::sym_file::walker::{eval_win_expr_for_fuzzer, walk_with_stack_cfi};
69 pub use crate::sym_file::{StackInfoWin, WinStackThing};
70}
71
72#[derive(Default, Debug, Clone)]
74pub struct SymbolStats {
75 pub symbol_url: Option<String>,
77 pub loaded_symbols: bool,
79 pub corrupt_symbols: bool,
81 pub extra_debug_info: Option<DebugInfoResult>,
83}
84
85#[derive(Default, Debug, Clone)]
89pub struct PendingSymbolStats {
90 pub symbols_processed: u64,
93 pub symbols_requested: u64,
95}
96
97#[derive(Default)]
105pub struct SimpleModule {
106 pub base_address: Option<u64>,
107 pub size: Option<u64>,
108 pub code_file: Option<String>,
109 pub code_identifier: Option<CodeId>,
110 pub debug_file: Option<String>,
111 pub debug_id: Option<DebugId>,
112 pub version: Option<String>,
113}
114
115impl SimpleModule {
116 pub fn new(debug_file: &str, debug_id: DebugId) -> SimpleModule {
120 SimpleModule {
121 debug_file: Some(String::from(debug_file)),
122 debug_id: Some(debug_id),
123 ..SimpleModule::default()
124 }
125 }
126
127 pub fn from_basic_info(
131 debug_file: Option<String>,
132 debug_id: Option<DebugId>,
133 code_file: Option<String>,
134 code_identifier: Option<CodeId>,
135 ) -> SimpleModule {
136 SimpleModule {
137 debug_file,
138 debug_id,
139 code_file,
140 code_identifier,
141 ..SimpleModule::default()
142 }
143 }
144}
145
146impl Module for SimpleModule {
147 fn base_address(&self) -> u64 {
148 self.base_address.unwrap_or(0)
149 }
150 fn size(&self) -> u64 {
151 self.size.unwrap_or(0)
152 }
153 fn code_file(&self) -> Cow<'_, str> {
154 self.code_file
155 .as_ref()
156 .map_or(Cow::from(""), |s| Cow::Borrowed(&s[..]))
157 }
158 fn code_identifier(&self) -> Option<CodeId> {
159 self.code_identifier.as_ref().cloned()
160 }
161 fn debug_file(&self) -> Option<Cow<'_, str>> {
162 self.debug_file.as_ref().map(|s| Cow::Borrowed(&s[..]))
163 }
164 fn debug_identifier(&self) -> Option<DebugId> {
165 self.debug_id
166 }
167 fn version(&self) -> Option<Cow<'_, str>> {
168 self.version.as_ref().map(|s| Cow::Borrowed(&s[..]))
169 }
170}
171
172fn leafname(path: &str) -> &str {
174 path.rsplit(['/', '\\']).next().unwrap_or(path)
175}
176
177fn replace_or_add_extension(filename: &str, match_extension: &str, new_extension: &str) -> String {
179 let mut bits = filename.split('.').collect::<Vec<_>>();
180 if bits.len() > 1
181 && bits
182 .last()
183 .is_some_and(|e| e.to_lowercase() == match_extension)
184 {
185 bits.pop();
186 }
187 bits.push(new_extension);
188 bits.join(".")
189}
190
191#[derive(Debug, Clone)]
193pub struct FileLookup {
194 pub debug_id: String,
195 pub debug_file: String,
196 pub cache_rel: String,
197 pub server_rel: String,
198}
199
200pub fn breakpad_sym_lookup(module: &(dyn Module + Sync)) -> Option<FileLookup> {
216 let debug_file = module.debug_file()?;
217 let debug_file = if debug_file.is_empty() {
218 module.code_file()
222 } else {
223 debug_file
224 };
225 let debug_id = module.debug_identifier()?;
226
227 let leaf = leafname(&debug_file);
228 let filename = replace_or_add_extension(leaf, "pdb", "sym");
229 let rel_path = [leaf, &debug_id.breakpad().to_string(), &filename[..]].join("/");
230 Some(FileLookup {
231 cache_rel: rel_path.clone(),
232 server_rel: rel_path,
233 debug_id: debug_id.breakpad().to_string(),
234 debug_file: filename,
235 })
236}
237
238pub fn code_info_breakpad_sym_lookup(module: &(dyn Module + Sync)) -> Option<String> {
250 let code_file = module.code_file();
251 let code_identifier = module.code_identifier()?;
252
253 if code_file.is_empty() {
254 return None;
255 }
256 let leaf = leafname(&code_file);
257 let filename = replace_or_add_extension(leaf, "dll", "sym");
258 let rel_path = [
259 leaf,
260 &code_identifier.to_string().to_uppercase(),
261 &filename[..],
262 ]
263 .join("/");
264
265 Some(rel_path)
266}
267
268pub fn extra_debuginfo_lookup(module: &(dyn Module + Sync)) -> Option<FileLookup> {
270 let debug_file = module.debug_file()?;
271 let debug_id = module.debug_identifier()?;
272
273 let leaf = leafname(&debug_file);
274 let rel_path = [leaf, &debug_id.breakpad().to_string(), leaf].join("/");
275 Some(FileLookup {
276 cache_rel: rel_path.clone(),
277 server_rel: rel_path,
278 debug_id: debug_id.to_string(),
279 debug_file: leaf.to_string(),
280 })
281}
282
283pub fn binary_lookup(module: &(dyn Module + Sync)) -> Option<FileLookup> {
285 let code_file = module.code_file();
290 let code_id = module.code_identifier()?;
291 let debug_file = module.debug_file()?;
292 let debug_id = module.debug_identifier()?;
293
294 let bin_leaf = leafname(&code_file);
295 let debug_leaf = leafname(&debug_file);
296
297 Some(FileLookup {
298 cache_rel: [debug_leaf, &debug_id.breakpad().to_string(), bin_leaf].join("/"),
299 server_rel: [bin_leaf, code_id.as_ref(), bin_leaf].join("/"),
300 debug_id: debug_id.to_string(),
301 debug_file: debug_file.to_string(),
302 })
303}
304
305pub fn moz_lookup(mut lookup: FileLookup) -> FileLookup {
308 lookup.server_rel.pop().unwrap();
309 lookup.server_rel.push('_');
310 lookup
311}
312
313pub fn lookup(module: &(dyn Module + Sync), file_kind: FileKind) -> Option<FileLookup> {
314 match file_kind {
315 FileKind::BreakpadSym => breakpad_sym_lookup(module),
316 FileKind::Binary => binary_lookup(module),
317 FileKind::ExtraDebugInfo => extra_debuginfo_lookup(module),
318 }
319}
320
321#[derive(Debug, thiserror::Error)]
342pub enum SymbolError {
343 #[error("symbol file not found")]
347 NotFound,
348 #[error("the debug file or id were missing")]
351 MissingDebugFileOrId,
352 #[error("couldn't read input stream")]
354 LoadError(#[from] std::io::Error),
355 #[error("parse error: {0} at line {1}")]
363 ParseError(&'static str, u64),
364}
365
366#[derive(Clone, Debug, thiserror::Error)]
367pub enum FileError {
368 #[error("file not found")]
369 NotFound,
370}
371
372#[derive(Debug)]
374pub struct FillSymbolError {
375 }
388
389impl PartialEq for SymbolError {
390 fn eq(&self, other: &SymbolError) -> bool {
391 matches!(
392 (self, other),
393 (SymbolError::NotFound, SymbolError::NotFound)
394 | (SymbolError::LoadError(_), SymbolError::LoadError(_))
395 | (SymbolError::ParseError(..), SymbolError::ParseError(..))
396 )
397 }
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct DebugInfoResult {
404 pub debug_file: String,
405 pub debug_identifier: DebugId,
406}
407
408#[derive(Debug, PartialEq, Eq)]
410pub struct LocateSymbolsResult {
411 pub symbols: SymbolFile,
412 pub extra_debug_info: Option<DebugInfoResult>,
413}
414
415#[async_trait]
417pub trait SymbolSupplier {
418 async fn locate_symbols(
423 &self,
424 module: &(dyn Module + Sync),
425 ) -> Result<LocateSymbolsResult, SymbolError>;
426
427 async fn locate_file(
432 &self,
433 module: &(dyn Module + Sync),
434 file_kind: FileKind,
435 ) -> Result<PathBuf, FileError>;
436}
437
438pub struct SimpleSymbolSupplier {
443 paths: Vec<PathBuf>,
445}
446
447impl SimpleSymbolSupplier {
448 pub fn new(paths: Vec<PathBuf>) -> SimpleSymbolSupplier {
450 SimpleSymbolSupplier { paths }
451 }
452}
453
454#[async_trait]
455impl SymbolSupplier for SimpleSymbolSupplier {
456 #[tracing::instrument(name = "symbols", level = "trace", skip_all, fields(module = crate::basename(&module.code_file())))]
457 async fn locate_symbols(
458 &self,
459 module: &(dyn Module + Sync),
460 ) -> Result<LocateSymbolsResult, SymbolError> {
461 let file_path = self
462 .locate_file(module, FileKind::BreakpadSym)
463 .await
464 .map_err(|_| SymbolError::NotFound)?;
465 let symbols = SymbolFile::from_file(&file_path).map_err(|e| {
466 trace!("SimpleSymbolSupplier failed: {}", e);
467 e
468 })?;
469 trace!("SimpleSymbolSupplier parsed file!");
470 Ok(LocateSymbolsResult {
471 symbols,
472 extra_debug_info: None,
473 })
474 }
475
476 #[tracing::instrument(level = "trace", skip(self, module), fields(module = crate::basename(&module.code_file())))]
477 async fn locate_file(
478 &self,
479 module: &(dyn Module + Sync),
480 file_kind: FileKind,
481 ) -> Result<PathBuf, FileError> {
482 trace!("SimpleSymbolSupplier search");
483 if let Some(lookup) = lookup(module, file_kind) {
484 for path in self.paths.iter() {
485 trace!("SimpleSymbolSupplier looking for {}", path.display());
486 if path.is_file() && file_kind == FileKind::BreakpadSym {
487 if let Ok(sf) = SymbolFile::from_file(path) {
488 if sf.module_id == lookup.debug_id {
489 trace!("SimpleSymbolSupplier found file {}", path.display());
490 return Ok(path.to_path_buf());
491 }
492 }
493 } else if path.is_dir() {
494 let test_path = path.join(lookup.cache_rel.clone());
495 trace!(
496 "SimpleSymbolSupplier looking for file {}",
497 test_path.display()
498 );
499 if fs::metadata(&test_path).ok().is_some_and(|m| m.is_file()) {
500 trace!("SimpleSymbolSupplier found file {}", test_path.display());
501 return Ok(test_path);
502 }
503 }
504 }
505 } else {
506 trace!("SimpleSymbolSupplier could not build symbol_path");
507 }
508 Err(FileError::NotFound)
509 }
510}
511
512#[derive(Default, Debug, Clone)]
516pub struct StringSymbolSupplier {
517 modules: HashMap<String, String>,
518 code_info_to_debug_info: HashMap<String, DebugInfoResult>,
519}
520
521impl StringSymbolSupplier {
522 pub fn new(modules: HashMap<String, String>) -> Self {
524 Self {
525 modules,
526 code_info_to_debug_info: HashMap::new(),
527 }
528 }
529
530 async fn lookup_debug_info_by_code_info(
532 &self,
533 module: &(dyn Module + Sync),
534 ) -> Option<DebugInfoResult> {
535 let lookup_path = code_info_breakpad_sym_lookup(module)?;
536 self.code_info_to_debug_info.get(&lookup_path).cloned()
537 }
538}
539
540#[async_trait]
541impl SymbolSupplier for StringSymbolSupplier {
542 #[tracing::instrument(name = "symbols", level = "trace", skip_all, fields(file = crate::basename(&module.code_file())))]
543 async fn locate_symbols(
544 &self,
545 module: &(dyn Module + Sync),
546 ) -> Result<LocateSymbolsResult, SymbolError> {
547 trace!("StringSymbolSupplier search");
548 if let Some(symbols) = self.modules.get(&*module.code_file()) {
549 trace!("StringSymbolSupplier found file");
550 let file = SymbolFile::from_bytes(symbols.as_bytes())?;
551 trace!("StringSymbolSupplier parsed file!");
552 return Ok(LocateSymbolsResult {
553 symbols: file,
554 extra_debug_info: self.lookup_debug_info_by_code_info(module).await,
555 });
556 }
557 trace!("StringSymbolSupplier could not find file");
558 Err(SymbolError::NotFound)
559 }
560
561 async fn locate_file(
562 &self,
563 _module: &(dyn Module + Sync),
564 _file_kind: FileKind,
565 ) -> Result<PathBuf, FileError> {
566 Err(FileError::NotFound)
568 }
569}
570
571pub trait FrameSymbolizer {
573 fn get_instruction(&self) -> u64;
575 fn set_function(&mut self, name: &str, base: u64, parameter_size: u32);
578 fn set_source_file(&mut self, file: &str, line: u32, base: u64);
580 fn add_inline_frame(&mut self, _name: &str, _file: Option<&str>, _line: Option<u32>) {}
583}
584
585pub trait FrameWalker {
586 fn get_instruction(&self) -> u64;
588 fn has_grand_callee(&self) -> bool;
590 fn get_grand_callee_parameter_size(&self) -> u32;
594 fn get_register_at_address(&self, address: u64) -> Option<u64>;
596 fn get_callee_register(&self, name: &str) -> Option<u64>;
598 fn set_caller_register(&mut self, name: &str, val: u64) -> Option<()>;
600 fn clear_caller_register(&mut self, name: &str);
602 fn set_cfa(&mut self, val: u64) -> Option<()>;
604 fn set_ra(&mut self, val: u64) -> Option<()>;
606}
607
608#[derive(Debug, Default)]
610pub struct SimpleFrame {
611 pub instruction: u64,
613 pub function: Option<String>,
615 pub function_base: Option<u64>,
617 pub parameter_size: Option<u32>,
619 pub source_file: Option<String>,
621 pub source_line: Option<u32>,
624 pub source_line_base: Option<u64>,
626}
627
628impl SimpleFrame {
629 pub fn with_instruction(instruction: u64) -> SimpleFrame {
631 SimpleFrame {
632 instruction,
633 ..SimpleFrame::default()
634 }
635 }
636}
637
638impl FrameSymbolizer for SimpleFrame {
639 fn get_instruction(&self) -> u64 {
640 self.instruction
641 }
642 fn set_function(&mut self, name: &str, base: u64, parameter_size: u32) {
643 self.function = Some(String::from(name));
644 self.function_base = Some(base);
645 self.parameter_size = Some(parameter_size);
646 }
647 fn set_source_file(&mut self, file: &str, line: u32, base: u64) {
648 self.source_file = Some(String::from(file));
649 self.source_line = Some(line);
650 self.source_line_base = Some(base);
651 }
652}
653
654#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
656pub enum FileKind {
657 BreakpadSym,
659 Binary,
661 ExtraDebugInfo,
663}
664
665type ModuleKey = (String, Option<String>, Option<String>, Option<String>);
674
675fn module_key(module: &(dyn Module + Sync)) -> ModuleKey {
677 (
678 module.code_file().to_string(),
679 module.code_identifier().map(|s| s.to_string()),
680 module.debug_file().map(|s| s.to_string()),
681 module.debug_identifier().map(|s| s.to_string()),
682 )
683}
684
685struct CachedAsyncResult<T, E> {
686 inner: FutMutex<Option<Arc<Result<T, E>>>>,
687}
688
689impl<T, E> Default for CachedAsyncResult<T, E> {
690 fn default() -> Self {
691 CachedAsyncResult {
692 inner: FutMutex::new(None),
693 }
694 }
695}
696
697impl<T, E> CachedAsyncResult<T, E> {
698 pub async fn get<'a, F, Fut>(&self, f: F) -> Arc<Result<T, E>>
699 where
700 F: FnOnce() -> Fut + 'a,
701 Fut: std::future::Future<Output = Result<T, E>> + 'a,
702 {
703 let mut guard = self.inner.lock().await;
704 if guard.is_none() {
705 *guard = Some(Arc::new(f().await));
706 }
707 guard.as_ref().unwrap().clone()
708 }
709}
710
711pub struct Symbolizer {
730 supplier: Box<dyn SymbolSupplier + Send + Sync + 'static>,
732 symbols: CacheMap<ModuleKey, CachedAsyncResult<SymbolFile, SymbolError>>,
738 pending_stats: Mutex<PendingSymbolStats>,
739 stats: Mutex<HashMap<String, SymbolStats>>,
740}
741
742impl Symbolizer {
743 pub fn new<T: SymbolSupplier + Send + Sync + 'static>(supplier: T) -> Symbolizer {
745 Symbolizer {
746 supplier: Box::new(supplier),
747 symbols: CacheMap::default(),
748 pending_stats: Mutex::default(),
749 stats: Mutex::default(),
750 }
751 }
752
753 pub async fn get_symbol_at_address(
763 &self,
764 debug_file: &str,
765 debug_id: DebugId,
766 address: u64,
767 ) -> Option<String> {
768 let k = (debug_file, debug_id);
769 let mut frame = SimpleFrame::with_instruction(address);
770 self.fill_symbol(&k, &mut frame).await.ok()?;
771 frame.function
772 }
773
774 pub async fn fill_symbol(
810 &self,
811 module: &(dyn Module + Sync),
812 frame: &mut (dyn FrameSymbolizer + Send),
813 ) -> Result<(), FillSymbolError> {
814 let cached_sym = self.get_symbols(module).await;
815 let sym = cached_sym
816 .as_ref()
817 .as_ref()
818 .map_err(|_| FillSymbolError {})?;
819 sym.fill_symbol(module, frame);
820 Ok(())
821 }
822
823 pub fn stats(&self) -> HashMap<String, SymbolStats> {
827 self.stats.lock().unwrap().clone()
828 }
829
830 pub fn pending_stats(&self) -> PendingSymbolStats {
832 self.pending_stats.lock().unwrap().clone()
833 }
834
835 pub async fn walk_frame(
839 &self,
840 module: &(dyn Module + Sync),
841 walker: &mut (dyn FrameWalker + Send),
842 ) -> Option<()> {
843 let cached_sym = self.get_symbols(module).await;
844 let sym = cached_sym.as_ref();
845 if let Ok(sym) = sym {
846 trace!("found symbols for address, searching for cfi entries");
847 sym.walk_frame(module, walker)
848 } else {
849 trace!("couldn't find symbols for address, cannot use cfi");
850 None
851 }
852 }
853
854 async fn get_symbols(
858 &self,
859 module: &(dyn Module + Sync),
860 ) -> Arc<Result<SymbolFile, SymbolError>> {
861 self.symbols
862 .cache_default(module_key(module))
863 .get(|| async {
864 trace!("locating symbols for module {}", module.code_file());
865 self.pending_stats.lock().unwrap().symbols_requested += 1;
866 let result = self.supplier.locate_symbols(module).await;
867 self.pending_stats.lock().unwrap().symbols_processed += 1;
868
869 let mut stats = SymbolStats::default();
870 match &result {
871 Ok(res) => {
872 stats.symbol_url.clone_from(&res.symbols.url);
873 stats.loaded_symbols = true;
874 stats.corrupt_symbols = false;
875 stats.extra_debug_info.clone_from(&res.extra_debug_info);
876 }
877 Err(SymbolError::NotFound) => {
878 stats.loaded_symbols = false;
879 }
880 Err(SymbolError::MissingDebugFileOrId) => {
881 stats.loaded_symbols = false;
882 }
883 Err(SymbolError::LoadError(_)) => {
884 stats.loaded_symbols = false;
885 }
886 Err(SymbolError::ParseError(..)) => {
887 stats.loaded_symbols = true;
888 stats.corrupt_symbols = true;
889 }
890 }
891 let key = leafname(module.code_file().as_ref()).to_string();
892 self.stats.lock().unwrap().insert(key, stats);
893
894 result.map(|r| r.symbols)
895 })
896 .await
897 }
898
899 pub async fn get_file_path(
903 &self,
904 module: &(dyn Module + Sync),
905 file_kind: FileKind,
906 ) -> Result<PathBuf, FileError> {
907 self.supplier.locate_file(module, file_kind).await
908 }
909}
910
911#[test]
912fn test_leafname() {
913 assert_eq!(leafname("c:\\foo\\bar\\test.pdb"), "test.pdb");
914 assert_eq!(leafname("c:/foo/bar/test.pdb"), "test.pdb");
915 assert_eq!(leafname("test.pdb"), "test.pdb");
916 assert_eq!(leafname("test"), "test");
917 assert_eq!(leafname("/path/to/test"), "test");
918}
919
920#[test]
921fn test_replace_or_add_extension() {
922 assert_eq!(
923 replace_or_add_extension("test.pdb", "pdb", "sym"),
924 "test.sym"
925 );
926 assert_eq!(
927 replace_or_add_extension("TEST.PDB", "pdb", "sym"),
928 "TEST.sym"
929 );
930 assert_eq!(replace_or_add_extension("test", "pdb", "sym"), "test.sym");
931 assert_eq!(
932 replace_or_add_extension("test.x", "pdb", "sym"),
933 "test.x.sym"
934 );
935 assert_eq!(replace_or_add_extension("", "pdb", "sym"), ".sym");
936 assert_eq!(replace_or_add_extension("test.x", "x", "y"), "test.y");
937}
938
939#[cfg(test)]
940mod test {
941
942 use super::*;
943 use std::fs::File;
944 use std::io::Write;
945 use std::path::Path;
946 use std::str::FromStr;
947
948 #[tokio::test]
949 async fn test_relative_symbol_path() {
950 let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
951 let m = SimpleModule::new("foo.pdb", debug_id);
952 assert_eq!(
953 &breakpad_sym_lookup(&m).unwrap().cache_rel,
954 "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
955 );
956
957 let m2 = SimpleModule::new("foo.pdb", debug_id);
958 assert_eq!(
959 &breakpad_sym_lookup(&m2).unwrap().cache_rel,
960 "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
961 );
962
963 let m3 = SimpleModule::new("foo.xyz", debug_id);
964 assert_eq!(
965 &breakpad_sym_lookup(&m3).unwrap().cache_rel,
966 "foo.xyz/ABCD1234ABCD1234ABCDABCD12345678a/foo.xyz.sym"
967 );
968
969 let m4 = SimpleModule::new("foo.xyz", debug_id);
970 assert_eq!(
971 &breakpad_sym_lookup(&m4).unwrap().cache_rel,
972 "foo.xyz/ABCD1234ABCD1234ABCDABCD12345678a/foo.xyz.sym"
973 );
974
975 let bad = SimpleModule::default();
976 assert!(breakpad_sym_lookup(&bad).is_none());
977
978 let bad2 = SimpleModule {
979 debug_file: Some("foo".to_string()),
980 ..SimpleModule::default()
981 };
982 assert!(breakpad_sym_lookup(&bad2).is_none());
983
984 let bad3 = SimpleModule {
985 debug_id: Some(debug_id),
986 ..SimpleModule::default()
987 };
988 assert!(breakpad_sym_lookup(&bad3).is_none());
989 }
990
991 #[tokio::test]
992 async fn test_relative_symbol_path_abs_paths() {
993 let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
994 {
995 let m = SimpleModule::new("/path/to/foo.bin", debug_id);
996 assert_eq!(
997 &breakpad_sym_lookup(&m).unwrap().cache_rel,
998 "foo.bin/ABCD1234ABCD1234ABCDABCD12345678a/foo.bin.sym"
999 );
1000 }
1001
1002 {
1003 let m = SimpleModule::new("c:/path/to/foo.pdb", debug_id);
1004 assert_eq!(
1005 &breakpad_sym_lookup(&m).unwrap().cache_rel,
1006 "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
1007 );
1008 }
1009
1010 {
1011 let m = SimpleModule::new("c:\\path\\to\\foo.pdb", debug_id);
1012 assert_eq!(
1013 &breakpad_sym_lookup(&m).unwrap().cache_rel,
1014 "foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"
1015 );
1016 }
1017 }
1018
1019 #[tokio::test]
1020 async fn test_empty_debug_file_breakpad_sym_lookup() {
1021 let m = SimpleModule {
1023 debug_file: Some("".to_string()),
1024 code_file: Some("foo.dll".to_string()),
1025 debug_id: DebugId::from_str("abcd1234-0000-0000-0000-abcd12345678-a").ok(),
1026 ..SimpleModule::default()
1027 };
1028
1029 assert_eq!(&breakpad_sym_lookup(&m).unwrap().debug_file, "foo.dll.sym");
1030 }
1031
1032 #[tokio::test]
1033 async fn test_code_info_breakpad_sym_lookup() {
1034 let m = SimpleModule {
1036 code_file: Some("foo.dll".to_string()),
1037 code_identifier: Some(CodeId::from_str("64E782C570C4000").unwrap()),
1038 ..SimpleModule::default()
1039 };
1040 assert_eq!(
1041 &code_info_breakpad_sym_lookup(&m).unwrap(),
1042 "foo.dll/64E782C570C4000/foo.sym"
1043 );
1044
1045 let bad = SimpleModule::default();
1046 assert!(code_info_breakpad_sym_lookup(&bad).is_none());
1047
1048 let bad2 = SimpleModule {
1049 code_file: Some("foo".to_string()),
1050 ..SimpleModule::default()
1051 };
1052 assert!(code_info_breakpad_sym_lookup(&bad2).is_none());
1053
1054 let bad3 = SimpleModule {
1055 code_identifier: Some(CodeId::from_str("64E782C570C4000").unwrap()),
1056 ..SimpleModule::default()
1057 };
1058 assert!(code_info_breakpad_sym_lookup(&bad3).is_none());
1059 }
1060
1061 fn mksubdirs(path: &Path, dirs: &[&str]) -> Vec<PathBuf> {
1062 dirs.iter()
1063 .map(|dir| {
1064 let new_path = path.join(dir);
1065 fs::create_dir(&new_path).unwrap();
1066 new_path
1067 })
1068 .collect()
1069 }
1070
1071 fn write_symbol_file(path: &Path, contents: &[u8]) {
1072 let dir = path.parent().unwrap();
1073 if !fs::metadata(dir).ok().is_some_and(|m| m.is_dir()) {
1074 fs::create_dir_all(dir).unwrap();
1075 }
1076 let mut f = File::create(path).unwrap();
1077 f.write_all(contents).unwrap();
1078 }
1079
1080 fn write_good_symbol_file(path: &Path) {
1081 write_symbol_file(path, b"MODULE Linux x86 abcd1234 foo\n");
1082 }
1083
1084 fn write_bad_symbol_file(path: &Path) {
1085 write_symbol_file(path, b"this is not a symbol file\n");
1086 }
1087
1088 #[tokio::test]
1089 async fn test_simple_symbol_supplier() {
1090 let t = tempfile::tempdir().unwrap();
1091 let paths = mksubdirs(t.path(), &["one", "two"]);
1092
1093 let supplier = SimpleSymbolSupplier::new(paths.clone());
1094 let bad = SimpleModule::default();
1095 assert_eq!(
1096 supplier.locate_symbols(&bad).await,
1097 Err(SymbolError::NotFound)
1098 );
1099
1100 for &(path, file, id, sym) in [
1103 (
1104 &paths[0],
1105 "foo.pdb",
1106 DebugId::from_str("abcd1234-0000-0000-0000-abcd12345678-a").unwrap(),
1107 "foo.pdb/ABCD1234000000000000ABCD12345678a/foo.sym",
1108 ),
1109 (
1110 &paths[1],
1111 "bar.xyz",
1112 DebugId::from_str("ff990000-0000-0000-0000-abcd12345678-a").unwrap(),
1113 "bar.xyz/FF990000000000000000ABCD12345678a/bar.xyz.sym",
1114 ),
1115 ]
1116 .iter()
1117 {
1118 let m = SimpleModule::new(file, id);
1119 assert_eq!(
1121 supplier.locate_symbols(&m).await,
1122 Err(SymbolError::NotFound)
1123 );
1124 write_good_symbol_file(&path.join(sym));
1125 assert!(
1127 supplier.locate_symbols(&m).await.is_ok(),
1128 "{}",
1129 format!("Located symbols for {sym}")
1130 );
1131 }
1132
1133 let debug_id = DebugId::from_str("ffff0000-0000-0000-0000-abcd12345678-a").unwrap();
1135 let mal = SimpleModule::new("baz.pdb", debug_id);
1136 let sym = "baz.pdb/FFFF0000000000000000ABCD12345678a/baz.sym";
1137 assert_eq!(
1138 supplier.locate_symbols(&mal).await,
1139 Err(SymbolError::NotFound)
1140 );
1141 write_bad_symbol_file(&paths[0].join(sym));
1142 let res = supplier.locate_symbols(&mal).await;
1143 assert!(
1144 matches!(res, Err(SymbolError::ParseError(..))),
1145 "{}",
1146 format!("Correctly failed to parse {sym}, result: {res:?}")
1147 );
1148 }
1149
1150 #[tokio::test]
1151 async fn test_symbolizer() {
1152 let t = tempfile::tempdir().unwrap();
1153 let path = t.path();
1154
1155 let supplier = SimpleSymbolSupplier::new(vec![PathBuf::from(path)]);
1157 let symbolizer = Symbolizer::new(supplier);
1158 let debug_id = DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap();
1159 let m1 = SimpleModule::new("foo.pdb", debug_id);
1160 write_symbol_file(
1161 &path.join("foo.pdb/ABCD1234ABCD1234ABCDABCD12345678a/foo.sym"),
1162 b"MODULE Linux x86 ABCD1234ABCD1234ABCDABCD12345678a foo
1163FILE 1 foo.c
1164FUNC 1000 30 10 some func
11651000 30 100 1
1166",
1167 );
1168 let mut f1 = SimpleFrame::with_instruction(0x1010);
1169 symbolizer.fill_symbol(&m1, &mut f1).await.unwrap();
1170 assert_eq!(f1.function.unwrap(), "some func");
1171 assert_eq!(f1.function_base.unwrap(), 0x1000);
1172 assert_eq!(f1.source_file.unwrap(), "foo.c");
1173 assert_eq!(f1.source_line.unwrap(), 100);
1174 assert_eq!(f1.source_line_base.unwrap(), 0x1000);
1175
1176 assert_eq!(
1177 symbolizer
1178 .get_symbol_at_address("foo.pdb", debug_id, 0x1010)
1179 .await
1180 .unwrap(),
1181 "some func"
1182 );
1183
1184 let debug_id = DebugId::from_str("ffff0000-0000-0000-0000-abcd12345678-a").unwrap();
1185 let m2 = SimpleModule::new("bar.pdb", debug_id);
1186 let mut f2 = SimpleFrame::with_instruction(0x1010);
1187 assert!(symbolizer.fill_symbol(&m2, &mut f2).await.is_err());
1189 assert!(f2.function.is_none());
1190 assert!(f2.function_base.is_none());
1191 assert!(f2.source_file.is_none());
1192 assert!(f2.source_line.is_none());
1193 write_symbol_file(
1195 &path.join("bar.pdb/ffff0000000000000000ABCD12345678a/bar.sym"),
1196 b"MODULE Linux x86 ffff0000000000000000ABCD12345678a bar
1197FILE 53 bar.c
1198FUNC 1000 30 10 another func
11991000 30 7 53
1200",
1201 );
1202 assert!(symbolizer.fill_symbol(&m2, &mut f2).await.is_err());
1203 assert!(f2.function.is_none());
1204 assert!(f2.function_base.is_none());
1205 assert!(f2.source_file.is_none());
1206 assert!(f2.source_line.is_none());
1207 assert!(symbolizer
1209 .get_symbol_at_address("bar.pdb", debug_id, 0x1010)
1210 .await
1211 .is_none());
1212 }
1213
1214 #[tokio::test]
1215 async fn test_extra_debug_info() {
1216 let debug_info = DebugInfoResult {
1217 debug_file: String::from_str("foo.pdb").unwrap(),
1218 debug_identifier: DebugId::from_str("abcd1234-abcd-1234-abcd-abcd12345678-a").unwrap(),
1219 };
1220
1221 let mut supplier = StringSymbolSupplier {
1222 modules: HashMap::new(),
1223 code_info_to_debug_info: HashMap::new(),
1224 };
1225 supplier.modules.insert(
1226 String::from_str("foo.pdb").unwrap(),
1227 String::from_str(
1228 "MODULE Linux x86 ABCD1234ABCD1234ABCDABCD12345678a foo
1229FILE 1 foo.c
1230FUNC 1000 30 10 some func
12311000 30 100 1
1232",
1233 )
1234 .unwrap(),
1235 );
1236 supplier.code_info_to_debug_info.insert(
1237 String::from_str("foo.pdb/64E782C570C4000/foo.pdb.sym").unwrap(),
1238 debug_info.clone(),
1239 );
1240
1241 let symbolizer = Symbolizer::new(supplier);
1242 let module = SimpleModule::from_basic_info(
1243 None,
1244 None,
1245 Some(String::from_str("foo.pdb").unwrap()),
1246 Some(CodeId::from_str("64E782C570C4000").unwrap()),
1247 );
1248
1249 let mut f1 = SimpleFrame::with_instruction(0x1010);
1250 symbolizer.fill_symbol(&module, &mut f1).await.unwrap();
1251 assert_eq!(f1.function.unwrap(), "some func");
1252 assert_eq!(f1.function_base.unwrap(), 0x1000);
1253 assert_eq!(f1.source_file.unwrap(), "foo.c");
1254 assert_eq!(f1.source_line.unwrap(), 100);
1255 assert_eq!(f1.source_line_base.unwrap(), 0x1000);
1256
1257 let sym_stats = symbolizer.stats();
1258 let stats = sym_stats.get("foo.pdb").unwrap();
1259 assert_eq!(stats.extra_debug_info, Some(debug_info));
1260 }
1261}