breakpad_symbols/
lib.rs

1// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3
4//! A library for working with [Google Breakpad][breakpad]'s
5//! text-format [symbol files][symbolfiles].
6//!
7//! See the [walker][] module for documentation on CFI evaluation.
8//!
9//! The highest-level API provided by this crate is to use the
10//! [`Symbolizer`][symbolizer] struct.
11//!
12//! [breakpad]: https://chromium.googlesource.com/breakpad/breakpad/+/master/
13//! [symbolfiles]: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
14//! [symbolizer]: struct.Symbolizer.html
15//!
16//! # Examples
17//!
18//! ```
19//! // std::env::set_current_dir::(env!("CARGO_MANIFEST_DIR"));
20//! use breakpad_symbols::{SimpleSymbolSupplier, Symbolizer, SimpleFrame, SimpleModule};
21//! use debugid::DebugId;
22//! use std::path::PathBuf;
23//! use std::str::FromStr;
24//!
25//! #[tokio::main]
26//! async fn main() {
27//!     let paths = vec!(PathBuf::from("../testdata/symbols/"));
28//!     let supplier = SimpleSymbolSupplier::new(paths);
29//!     let symbolizer = Symbolizer::new(supplier);
30//!
31//!     // Simple function name lookup with debug file, debug id, address.
32//!     let debug_id = DebugId::from_str("5A9832E5287241C1838ED98914E9B7FF1").unwrap();
33//!     assert_eq!(symbolizer.get_symbol_at_address("test_app.pdb", debug_id, 0x1010)
34//!         .await
35//!         .unwrap(),
36//!         "vswprintf");
37//! }
38//! ```
39
40use 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// Re-exports for the purposes of the cfi_eval fuzzer. Not public API.
65#[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/// Statistics on the symbols of a module.
73#[derive(Default, Debug, Clone)]
74pub struct SymbolStats {
75    /// If the module's symbols were downloaded, this is the url used.
76    pub symbol_url: Option<String>,
77    /// If the symbols were found and loaded into memory.
78    pub loaded_symbols: bool,
79    /// If we tried to parse the symbols, but failed.
80    pub corrupt_symbols: bool,
81    /// If the module's debug info had to be looked up, this is the debug info used.
82    pub extra_debug_info: Option<DebugInfoResult>,
83}
84
85/// Statistics on pending symbols.
86///
87/// Fetched with [`Symbolizer::pending_stats`].
88#[derive(Default, Debug, Clone)]
89pub struct PendingSymbolStats {
90    /// The number of symbols we have finished processing
91    /// (could be either successful or not, either way is fine).
92    pub symbols_processed: u64,
93    /// The number of symbols we have been asked to process.
94    pub symbols_requested: u64,
95}
96
97/// A `Module` implementation that holds arbitrary data.
98///
99/// This can be useful for getting symbols for a module when you
100/// have a debug id and filename but not an actual minidump. If you have a
101/// minidump, you should be using [`MinidumpModule`][minidumpmodule].
102///
103/// [minidumpmodule]: ../minidump/struct.MinidumpModule.html
104#[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    /// Create a `SimpleModule` with the given `debug_file` and `debug_id`.
117    ///
118    /// Uses `default` for the remaining fields.
119    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    /// Create a `SimpleModule` with `debug_file`, `debug_id`, `code_file`, and `code_identifier`.
128    ///
129    /// Uses `default` for the remaining fields.
130    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
172/// Like `PathBuf::file_name`, but try to work on Windows or POSIX-style paths.
173fn leafname(path: &str) -> &str {
174    path.rsplit(['/', '\\']).next().unwrap_or(path)
175}
176
177/// If `filename` ends with `match_extension`, remove it. Append `new_extension` to the result.
178fn 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/// A lookup we would like to perform for some file (sym, exe, pdb, dll, ...)
192#[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
200/// Get a relative symbol path at which to locate symbols for `module`.
201///
202/// Symbols are generally stored in the layout used by Microsoft's symbol
203/// server and associated tools:
204/// `<debug filename>/<debug identifier>/<debug filename>.sym`. If
205/// `debug filename` ends with *.pdb* the leaf filename will have that
206/// removed.
207///
208/// The debug filename and debug identifier can be found in the
209/// [first line][module_line] of the symbol file output by the dump_syms tool.
210/// You can use [this script][packagesymbols] to run dump_syms and put the
211/// resulting symbol files in the proper directory structure.
212///
213/// [module_line]: https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md#MODULE-records
214/// [packagesymbols]: https://gist.github.com/luser/2ad32d290f224782fcfc#file-packagesymbols-py
215pub 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        // If the debug_file info is empty, fallback to the code_file.
219        // This can be the case on Windows minidumps generated for gcc MingW-w64 builds
220        // as GCC does not support PDB generation there.
221        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
238/// Get a relative symbol path at which to locate symbols for `module` using
239/// the code file and code identifier. This is helpful for Microsoft modules
240/// where we don't have a valid debug filename and debug id to retrieve the
241/// symbol file with and the symbol server supports looking up debug filename
242/// and debug id using the code file and code id.
243///
244/// If `code file` ends with *.dll* the leaf filename will have that removed.
245/// `extension` is the expected extension for the symbol filename, generally
246/// *sym* if Breakpad text format symbols are expected.
247///
248/// `<code file>/<code identifier>/<code file>.sym`
249pub 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
268/// Returns a lookup for this module's extra debuginfo (pdb)
269pub 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
283/// Returns a lookup for this module's binary (exe, dll, so, dylib, ...)
284pub fn binary_lookup(module: &(dyn Module + Sync)) -> Option<FileLookup> {
285    // NOTE: to make dump_syms happy we're currently moving the bin
286    // to be next to the pdb. This changes where we would naively put it,
287    // hence the two different paths!
288
289    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
305/// Mangles a lookup to mozilla's format where the last char is replaced by an underscore
306/// (and the file is wrapped in a CAB, but dump_syms handles that transparently).
307pub 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/// Possible results of locating symbols for a module.
322///
323/// Because symbols may be found from different sources, symbol providers
324/// are usually configured to "cascade" into the next one whenever they report
325/// `NotFound`.
326///
327/// Cascading currently assumes that if any provider finds symbols for
328/// a module, all other providers will find the same symbols (if any).
329/// Therefore cascading will not be applied if a LoadError or ParseError
330/// occurs (because presumably, all the other sources will also fail to
331/// load/parse.)
332///
333/// In theory we could do some interesting things where we attempt to
334/// be more robust and actually merge together the symbols from multiple
335/// sources, but that would make it difficult to cache symbol files, and
336/// would rarely actually improve results.
337///
338/// Since symbol files can be on the order of a gigabyte(!) and downloaded
339/// from the network, aggressive caching is pretty important. The current
340/// approach is a nice balance of simple and effective.
341#[derive(Debug, thiserror::Error)]
342pub enum SymbolError {
343    /// Symbol file could not be found.
344    ///
345    /// In this case other symbol providers may still be able to find it!
346    #[error("symbol file not found")]
347    NotFound,
348    /// The module was lacking either the debug file or debug id, as such the
349    /// path of the symbol could not be generated.
350    #[error("the debug file or id were missing")]
351    MissingDebugFileOrId,
352    /// Symbol file could not be loaded into memory.
353    #[error("couldn't read input stream")]
354    LoadError(#[from] std::io::Error),
355    /// Symbol file was too corrupt to be parsed at all.
356    ///
357    /// Because symbol files are pretty modular, many corruptions/ambiguities
358    /// can be either repaired or discarded at a fairly granular level
359    /// (e.g. a bad STACK WIN line can be discarded without affecting anything
360    /// else). But sometimes we can't make any sense of the symbol file, and
361    /// you find yourself here.
362    #[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/// An error produced by fill_symbol.
373#[derive(Debug)]
374pub struct FillSymbolError {
375    // We don't want to yield a full SymbolError for fill_symbol
376    // as this would involve cloning bulky Error strings every time
377    // someone requested symbols for a missing module.
378    //
379    // As it turns out there's currently no reason to care about *why*
380    // fill_symbol, so for now this is just a dummy type until we have
381    // something to put here.
382    //
383    // The only reason fill_symbol *can* produce an Err is so that
384    // the caller can distinguish between "we had symbols, but this address
385    // didn't map to a function name" and "we had no symbols for that module"
386    // (this is used as a heuristic for stack scanning).
387}
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/// The result of a lookup by code_file/code_identifier against a symbol
401/// server.
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub struct DebugInfoResult {
404    pub debug_file: String,
405    pub debug_identifier: DebugId,
406}
407
408/// The result of locating symbols, with debug info if it had to be looked up.
409#[derive(Debug, PartialEq, Eq)]
410pub struct LocateSymbolsResult {
411    pub symbols: SymbolFile,
412    pub extra_debug_info: Option<DebugInfoResult>,
413}
414
415/// A trait for things that can locate symbols for a given module.
416#[async_trait]
417pub trait SymbolSupplier {
418    /// Locate and load a symbol file for `module`.
419    ///
420    /// Implementations may use any strategy for locating and loading
421    /// symbols.
422    async fn locate_symbols(
423        &self,
424        module: &(dyn Module + Sync),
425    ) -> Result<LocateSymbolsResult, SymbolError>;
426
427    /// Locate a specific file associated with a `module`
428    ///
429    /// Implementations may use any strategy for locating and loading
430    /// symbols.
431    async fn locate_file(
432        &self,
433        module: &(dyn Module + Sync),
434        file_kind: FileKind,
435    ) -> Result<PathBuf, FileError>;
436}
437
438/// An implementation of `SymbolSupplier` that loads Breakpad text-format symbols from local disk
439/// paths.
440///
441/// See [`breakpad_sym_lookup`] for details on how paths are searched.
442pub struct SimpleSymbolSupplier {
443    /// Local disk paths in which to search for symbols.
444    paths: Vec<PathBuf>,
445}
446
447impl SimpleSymbolSupplier {
448    /// Instantiate a new `SimpleSymbolSupplier` that will search in `paths`.
449    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/// A SymbolSupplier that maps module names (code_files) to an in-memory string.
513///
514/// Intended for mocking symbol files in tests.
515#[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    /// Make a new StringSymbolSupplier with no modules.
523    pub fn new(modules: HashMap<String, String>) -> Self {
524        Self {
525            modules,
526            code_info_to_debug_info: HashMap::new(),
527        }
528    }
529
530    /// Perform a code_file/code_identifier lookup for a specific symbol server.
531    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        // StringSymbolSupplier can never find files, is for testing
567        Err(FileError::NotFound)
568    }
569}
570
571/// A trait for setting symbol information on something like a stack frame.
572pub trait FrameSymbolizer {
573    /// Get the program counter value for this frame.
574    fn get_instruction(&self) -> u64;
575    /// Set the name, base address, and parameter size of the function in
576    /// which this frame is executing.
577    fn set_function(&mut self, name: &str, base: u64, parameter_size: u32);
578    /// Set the source file and (1-based) line number this frame represents.
579    fn set_source_file(&mut self, file: &str, line: u32, base: u64);
580    /// Add an inline frame. This method can be called multiple times, in the
581    /// order "outside to inside".
582    fn add_inline_frame(&mut self, _name: &str, _file: Option<&str>, _line: Option<u32>) {}
583}
584
585pub trait FrameWalker {
586    /// Get the instruction address that we're trying to unwind from.
587    fn get_instruction(&self) -> u64;
588    /// Check whether the callee has a callee of its own.
589    fn has_grand_callee(&self) -> bool;
590    /// Get the number of bytes the callee's callee's parameters take up
591    /// on the stack (or 0 if unknown/invalid). This is needed for
592    /// STACK WIN unwinding.
593    fn get_grand_callee_parameter_size(&self) -> u32;
594    /// Get a register-sized value stored at this address.
595    fn get_register_at_address(&self, address: u64) -> Option<u64>;
596    /// Get the value of a register from the callee's frame.
597    fn get_callee_register(&self, name: &str) -> Option<u64>;
598    /// Set the value of a register for the caller's frame.
599    fn set_caller_register(&mut self, name: &str, val: u64) -> Option<()>;
600    /// Explicitly mark one of the caller's registers as invalid.
601    fn clear_caller_register(&mut self, name: &str);
602    /// Set whatever registers in the caller should be set based on the cfa (e.g. rsp).
603    fn set_cfa(&mut self, val: u64) -> Option<()>;
604    /// Set whatever registers in the caller should be set based on the return address (e.g. rip).
605    fn set_ra(&mut self, val: u64) -> Option<()>;
606}
607
608/// A simple implementation of `FrameSymbolizer` that just holds data.
609#[derive(Debug, Default)]
610pub struct SimpleFrame {
611    /// The program counter value for this frame.
612    pub instruction: u64,
613    /// The name of the function in which the current instruction is executing.
614    pub function: Option<String>,
615    /// The offset of the start of `function` from the module base.
616    pub function_base: Option<u64>,
617    /// The size, in bytes, that this function's parameters take up on the stack.
618    pub parameter_size: Option<u32>,
619    /// The name of the source file in which the current instruction is executing.
620    pub source_file: Option<String>,
621    /// The 1-based index of the line number in `source_file` in which the current instruction is
622    /// executing.
623    pub source_line: Option<u32>,
624    /// The offset of the start of `source_line` from the function base.
625    pub source_line_base: Option<u64>,
626}
627
628impl SimpleFrame {
629    /// Instantiate a `SimpleFrame` with instruction pointer `instruction`.
630    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/// A type of file related to a module that you might want downloaded.
655#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
656pub enum FileKind {
657    /// A Breakpad symbol (.sym) file
658    BreakpadSym,
659    /// The native binary of a module ("code file") (.exe/.dll/.so/.dylib...)
660    Binary,
661    /// Extra debuginfo for a module ("debug file") (.pdb/...?)
662    ExtraDebugInfo,
663}
664
665// Can't make Module derive Hash, since then it can't be used as a trait
666// object (because the hash method is generic), so this is a hacky workaround.
667/// A key that uniquely identifies a module:
668///
669/// * code_file
670/// * code_id
671/// * debug_file
672/// * debug_id
673type ModuleKey = (String, Option<String>, Option<String>, Option<String>);
674
675/// Helper for deriving a hash key from a `Module` for `Symbolizer`.
676fn 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
711/// Symbolicate stack frames.
712///
713/// A `Symbolizer` manages loading symbols and looking up symbols in them
714/// including caching so that symbols for a given module are only loaded once.
715///
716/// Call [`Symbolizer::new`][new] to instantiate a `Symbolizer`. A Symbolizer
717/// requires a [`SymbolSupplier`][supplier] to locate symbols. If you have
718/// symbols on disk in the [customary directory layout][breakpad_sym_lookup], a
719/// [`SimpleSymbolSupplier`][simple] will work.
720///
721/// Use [`get_symbol_at_address`][get_symbol] or [`fill_symbol`][fill_symbol] to
722/// do symbol lookup.
723///
724/// [new]: struct.Symbolizer.html#method.new
725/// [supplier]: trait.SymbolSupplier.html
726/// [simple]: struct.SimpleSymbolSupplier.html
727/// [get_symbol]: struct.Symbolizer.html#method.get_symbol_at_address
728/// [fill_symbol]: struct.Symbolizer.html#method.fill_symbol
729pub struct Symbolizer {
730    /// Symbol supplier for locating symbols.
731    supplier: Box<dyn SymbolSupplier + Send + Sync + 'static>,
732    /// Cache of symbol locating results.
733    // TODO?: use lru-cache: https://crates.io/crates/lru-cache/
734    // note that using an lru-cache would mess up the fact that we currently
735    // use this for statistics collection. Splitting out statistics would be
736    // way messier but not impossible.
737    symbols: CacheMap<ModuleKey, CachedAsyncResult<SymbolFile, SymbolError>>,
738    pending_stats: Mutex<PendingSymbolStats>,
739    stats: Mutex<HashMap<String, SymbolStats>>,
740}
741
742impl Symbolizer {
743    /// Create a `Symbolizer` that uses `supplier` to locate symbols.
744    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    /// Helper method for non-minidump-using callers.
754    ///
755    /// Pass `debug_file` and `debug_id` describing a specific module,
756    /// and `address`, a module-relative address, and get back
757    /// a symbol in that module that covers that address, or `None`.
758    ///
759    /// See [the module-level documentation][module] for an example.
760    ///
761    /// [module]: index.html
762    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    /// Fill symbol information in `frame` using the instruction address
775    /// from `frame`, and the module information from `module`. If you're not
776    /// using a minidump module, you can use [`SimpleModule`][simplemodule] and
777    /// [`SimpleFrame`][simpleframe].
778    ///
779    /// An Error indicates that no symbols could be found for the relevant
780    /// module.
781    ///
782    /// # Examples
783    ///
784    /// ```
785    /// // std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
786    /// use std::str::FromStr;
787    /// use debugid::DebugId;
788    /// use breakpad_symbols::{SimpleSymbolSupplier,Symbolizer,SimpleFrame,SimpleModule};
789    ///
790    /// #[tokio::main]
791    /// async fn main() {
792    ///     use std::path::PathBuf;
793    ///     let paths = vec!(PathBuf::from("../testdata/symbols/"));
794    ///     let supplier = SimpleSymbolSupplier::new(paths);
795    ///     let symbolizer = Symbolizer::new(supplier);
796    ///     let debug_id = DebugId::from_str("5A9832E5287241C1838ED98914E9B7FF1").unwrap();
797    ///     let m = SimpleModule::new("test_app.pdb", debug_id);
798    ///     let mut f = SimpleFrame::with_instruction(0x1010);
799    ///     let _ = symbolizer.fill_symbol(&m, &mut f).await;
800    ///     assert_eq!(f.function.unwrap(), "vswprintf");
801    ///     assert_eq!(f.source_file.unwrap(),
802    ///         r"c:\program files\microsoft visual studio 8\vc\include\swprintf.inl");
803    ///     assert_eq!(f.source_line.unwrap(), 51);
804    /// }
805    /// ```
806    ///
807    /// [simplemodule]: struct.SimpleModule.html
808    /// [simpleframe]: struct.SimpleFrame.html
809    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    /// Collect various statistics on the symbols.
824    ///
825    /// Keys are the file name of the module (code_file's file name).
826    pub fn stats(&self) -> HashMap<String, SymbolStats> {
827        self.stats.lock().unwrap().clone()
828    }
829
830    /// Get live symbol stats for interactive updates.
831    pub fn pending_stats(&self) -> PendingSymbolStats {
832        self.pending_stats.lock().unwrap().clone()
833    }
834
835    /// Tries to use CFI to walk the stack frame of the FrameWalker
836    /// using the symbols of the given Module. Output will be written
837    /// using the FrameWalker's `set_caller_*` APIs.
838    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    /// Gets the fully parsed SymbolFile for a given module (or an Error).
855    ///
856    /// This returns a CachedOperation which is guaranteed to already be resolved (lifetime stuff).
857    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    /// Gets the path to a file for a given module (or an Error).
900    ///
901    /// This returns a CachedOperation which is guaranteed to already be resolved (lifetime stuff).
902    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        // Test module with empty debug_file name
1022        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        // Test normal data
1035        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        // Try loading symbols for each of two modules in each of the two
1101        // search paths.
1102        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            // No symbols present yet.
1120            assert_eq!(
1121                supplier.locate_symbols(&m).await,
1122                Err(SymbolError::NotFound)
1123            );
1124            write_good_symbol_file(&path.join(sym));
1125            // Should load OK now that it exists.
1126            assert!(
1127                supplier.locate_symbols(&m).await.is_ok(),
1128                "{}",
1129                format!("Located symbols for {sym}")
1130            );
1131        }
1132
1133        // Write a malformed symbol file, verify that it's found but fails to load.
1134        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        // TODO: This could really use a MockSupplier
1156        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        // No symbols present, should not find anything.
1188        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        // Results should be cached.
1194        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        // This should also use cached results.
1208        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}