samply_symbols/lib.rs
1//! This crate allows obtaining symbol information from binaries and compilation artifacts.
2//!
3//! You probably want to be using the [`wholesym` crate](https://docs.rs/wholesym/) instead.
4//! `wholesym` has a much more ergonomic API; it is a wrapper around `samply-symbols`.
5//!
6//! More specifically, `samply-symbols` provides the low-level implementation of `wholesym`,
7//! while satisfying both native and WebAssembly consumers, whereas `wholesym` only cares about
8//! native consumers.
9//!
10//! The main entry point of this crate is the `SymbolManager` struct and its async `load_symbol_map` method.
11//! With a `SymbolMap`, you can resolve raw code addresses to function name strings, and, if available,
12//! to file name + line number information and inline stacks.
13//!
14//! # Design constraints
15//!
16//! This crate operates under the following design constraints:
17//!
18//!   - Must be usable from JavaScript / WebAssembly: The Firefox profiler runs this code in a
19//!     WebAssembly environment, invoked from a privileged piece of JavaScript code inside Firefox itself.
20//!     This setup allows us to download a wasm bundle on demand, rather than shipping
21//!     it with Firefox, which would increase the Firefox download size for a piece of functionality
22//!     that the vast majority of Firefox users don't need.
23//!   - Performance: We want to be able to obtain symbol data from a fresh build of a locally compiled
24//!     Firefox instance as quickly as possible, without an expensive preprocessing step. The time between
25//!     "finished compilation" and "returned symbol data" should be minimized. This means that symbol
26//!     data needs to be obtained directly from the compilation artifacts rather than from, say, a
27//!     dSYM bundle or a Breakpad .sym file.
28//!   - Must scale to large inputs: This applies to both the size of the API request and the size of the
29//!     object files that need to be parsed: The Firefox profiler will supply anywhere between tens of
30//!     thousands and hundreds of thousands of different code addresses in a single symbolication request.
31//!     Firefox build artifacts such as libxul.so can be multiple gigabytes big, and contain around 300000
32//!     function symbols. We want to serve such requests within a few seconds or less.
33//!   - "Best effort" basis: If only limited symbol information is available, for example from system
34//!     libraries, we want to return whatever limited information we have.
35//!
36//! The WebAssembly requirement means that this crate cannot contain any direct file access.
37//! Instead, all file access is mediated through a `FileAndPathHelper` trait which has to be implemented
38//! by the caller. We cannot even use the `std::path::Path` / `PathBuf` types to represent paths,
39//! because the WASM bundle can run on Windows, and the `Path` / `PathBuf` types have! Unix path
40//! semantics in Rust-compiled-to-WebAssembly.
41//!
42//! Furthermore, the caller needs to be able to find the right symbol files based on a subset
43//! of information about a library, for example just based on its debug name and debug ID. This
44//! is used when `SymbolManager::load_symbol_map` is called with such a subset of information.
45//! More concretely, this ability is used by `samply-api` when processing a JSON symbolication
46//! API call, which only comes with the debug name and debug ID for a library.
47//!
48//! # Supported formats and data
49//!
50//! This crate supports obtaining symbol data from PE binaries (Windows), PDB files (Windows),
51//! mach-o binaries (including fat binaries) (macOS & iOS), and ELF binaries (Linux, Android, etc.).
52//! For mach-o files it also supports finding debug information in external objects, by following
53//! OSO stabs entries.
54//! It supports gathering both basic symbol information (function name strings) as well as information
55//! based on debug data, i.e. inline callstacks where each frame has a function name, a file name,
56//! and a line number.
57//! For debug data we support both DWARF debug data (inside mach-o and ELF binaries) and PDB debug data.
58//!
59//! # Example
60//!
61//! ```rust
62//! use samply_symbols::debugid::DebugId;
63//! use samply_symbols::{
64//!     CandidatePathInfo, FileAndPathHelper, FileAndPathHelperResult, FileLocation,
65//!     FramesLookupResult, LibraryInfo, LookupAddress, OptionallySendFuture, SymbolManager,
66//! };
67//!
68//! async fn run_query() {
69//!     let this_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
70//!     let helper = ExampleHelper {
71//!         artifact_directory: this_dir.join("..").join("fixtures").join("win64-ci"),
72//!     };
73//!
74//!     let symbol_manager = SymbolManager::with_helper(helper);
75//!
76//!     let library_info = LibraryInfo {
77//!         debug_name: Some("firefox.pdb".to_string()),
78//!         debug_id: DebugId::from_breakpad("AA152DEB2D9B76084C4C44205044422E1").ok(),
79//!         ..Default::default()
80//!     };
81//!     let symbol_map = match symbol_manager.load_symbol_map(&library_info).await {
82//!         Ok(symbol_map) => symbol_map,
83//!         Err(e) => {
84//!             println!("Error while loading the symbol map: {:?}", e);
85//!             return;
86//!         }
87//!     };
88//!
89//!     // Look up the symbol for an address.
90//!     let lookup_result = symbol_map.lookup(LookupAddress::Relative(0x1f98f)).await;
91//!
92//!     match lookup_result {
93//!         Some(address_info) => {
94//!             // Print the symbol name for this address:
95//!             println!("0x1f98f: {}", address_info.symbol.name);
96//!
97//!             // See if we have debug info (file name + line, and inlined frames):
98//!             if let Some(frames) = address_info.frames {
99//!                 println!("Debug info:");
100//!                 for frame in frames {
101//!                     println!(
102//!                         " - {:?} ({:?}:{:?})",
103//!                         frame.function, frame.file_path, frame.line_number
104//!                     );
105//!                 }
106//!             }
107//!         }
108//!         None => {
109//!             println!("No symbol was found for address 0x1f98f.")
110//!         }
111//!     }
112//! }
113//!
114//! struct ExampleHelper {
115//!     artifact_directory: std::path::PathBuf,
116//! }
117//!
118//! impl FileAndPathHelper for ExampleHelper {
119//!     type F = Vec<u8>;
120//!     type FL = ExampleFileLocation;
121//!
122//!     fn get_candidate_paths_for_debug_file(
123//!         &self,
124//!         library_info: &LibraryInfo,
125//!     ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
126//!         if let Some(debug_name) = library_info.debug_name.as_deref() {
127//!             Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
128//!                 self.artifact_directory.join(debug_name),
129//!             ))])
130//!         } else {
131//!             Ok(vec![])
132//!         }
133//!     }
134//!
135//!     fn get_candidate_paths_for_binary(
136//!         &self,
137//!         library_info: &LibraryInfo,
138//!     ) -> FileAndPathHelperResult<Vec<CandidatePathInfo<ExampleFileLocation>>> {
139//!         if let Some(name) = library_info.name.as_deref() {
140//!             Ok(vec![CandidatePathInfo::SingleFile(ExampleFileLocation(
141//!                 self.artifact_directory.join(name),
142//!             ))])
143//!         } else {
144//!             Ok(vec![])
145//!         }
146//!     }
147//!
148//!    fn get_dyld_shared_cache_paths(
149//!        &self,
150//!        _arch: Option<&str>,
151//!    ) -> FileAndPathHelperResult<Vec<ExampleFileLocation>> {
152//!        Ok(vec![])
153//!    }
154//!
155//!     fn load_file(
156//!         &self,
157//!         location: ExampleFileLocation,
158//!     ) -> std::pin::Pin<Box<dyn OptionallySendFuture<Output = FileAndPathHelperResult<Self::F>> + '_>> {
159//!         Box::pin(async move { Ok(std::fs::read(&location.0)?) })
160//!     }
161//! }
162//!
163//! #[derive(Clone, Debug)]
164//! struct ExampleFileLocation(std::path::PathBuf);
165//!
166//! impl std::fmt::Display for ExampleFileLocation {
167//!     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168//!         self.0.to_string_lossy().fmt(f)
169//!     }
170//! }
171//!
172//! impl FileLocation for ExampleFileLocation {
173//!     fn location_for_dyld_subcache(&self, suffix: &str) -> Option<Self> {
174//!         let mut filename = self.0.file_name().unwrap().to_owned();
175//!         filename.push(suffix);
176//!         Some(Self(self.0.with_file_name(filename)))
177//!     }
178//!
179//!     fn location_for_external_object_file(&self, object_file: &str) -> Option<Self> {
180//!         Some(Self(object_file.into()))
181//!     }
182//!
183//!     fn location_for_pdb_from_binary(&self, pdb_path_in_binary: &str) -> Option<Self> {
184//!         Some(Self(pdb_path_in_binary.into()))
185//!     }
186//!
187//!     fn location_for_source_file(&self, source_file_path: &str) -> Option<Self> {
188//!         Some(Self(source_file_path.into()))
189//!     }
190//!
191//!     fn location_for_breakpad_symindex(&self) -> Option<Self> {
192//!         Some(Self(self.0.with_extension("symindex")))
193//!     }
194//!
195//!     fn location_for_dwo(&self, _comp_dir: &str, path: &str) -> Option<Self> {
196//!         Some(Self(path.into()))
197//!     }
198//!
199//!     fn location_for_dwp(&self) -> Option<Self> {
200//!         let mut s = self.0.as_os_str().to_os_string();
201//!         s.push(".dwp");
202//!         Some(Self(s.into()))
203//!     }
204//! }
205//! ```
206
207use std::sync::Arc;
208
209use binary_image::BinaryImageInner;
210use jitdump::JitDumpIndex;
211use linux_perf_data::jitdump::JitDumpReader;
212use object::read::FileKind;
213pub use pdb_addr2line::pdb;
214use shared::FileContentsCursor;
215pub use {debugid, object};
216
217mod binary_image;
218mod breakpad;
219mod cache;
220mod chunked_read_buffer_manager;
221mod compact_symbol_table;
222mod debugid_util;
223mod demangle;
224mod demangle_ocaml;
225mod dwarf;
226mod elf;
227mod error;
228mod external_file;
229mod jitdump;
230mod macho;
231mod mapped_path;
232mod path_mapper;
233mod shared;
234mod symbol_map;
235mod symbol_map_object;
236mod windows;
237
238pub use crate::binary_image::{BinaryImage, CodeByteReadingError};
239pub use crate::breakpad::{
240    BreakpadIndex, BreakpadIndexParser, BreakpadParseError, BreakpadSymindexParseError,
241};
242pub use crate::cache::{FileByteSource, FileContentsWithChunkedCaching};
243pub use crate::compact_symbol_table::CompactSymbolTable;
244pub use crate::debugid_util::{debug_id_for_object, DebugIdExt};
245pub use crate::demangle::demangle_any;
246pub use crate::error::Error;
247pub use crate::external_file::{load_external_file, ExternalFileSymbolMap};
248pub use crate::jitdump::debug_id_and_code_id_for_jitdump;
249pub use crate::macho::FatArchiveMember;
250pub use crate::mapped_path::MappedPath;
251pub use crate::shared::{
252    relative_address_base, AddressInfo, CandidatePathInfo, CodeId, ElfBuildId,
253    ExternalFileAddressInFileRef, ExternalFileAddressRef, ExternalFileRef, FileAndPathHelper,
254    FileAndPathHelperError, FileAndPathHelperResult, FileContents, FileContentsWrapper,
255    FileLocation, FrameDebugInfo, FramesLookupResult, LibraryInfo, LookupAddress,
256    MultiArchDisambiguator, OptionallySendFuture, PeCodeId, SourceFilePath, SymbolInfo,
257    SyncAddressInfo,
258};
259pub use crate::symbol_map::{SymbolMap, SymbolMapTrait};
260
261pub struct SymbolManager<H: FileAndPathHelper> {
262    helper: Arc<H>,
263}
264
265impl<H, F, FL> SymbolManager<H>
266where
267    H: FileAndPathHelper<F = F, FL = FL>,
268    F: FileContents + 'static,
269    FL: FileLocation,
270{
271    // Create a new `SymbolManager`.
272    pub fn with_helper(helper: H) -> Self {
273        Self {
274            helper: Arc::new(helper),
275        }
276    }
277
278    /// Exposes the helper.
279    pub fn helper(&self) -> Arc<H> {
280        self.helper.clone()
281    }
282
283    pub async fn load_source_file(
284        &self,
285        debug_file_location: &H::FL,
286        source_file_path: &SourceFilePath,
287    ) -> Result<String, Error> {
288        let source_file_location = debug_file_location
289            .location_for_source_file(source_file_path.raw_path())
290            .ok_or(Error::FileLocationRefusedSourceFileLocation)?;
291        let file_contents = self
292            .helper
293            .load_file(source_file_location.clone())
294            .await
295            .map_err(|e| Error::HelperErrorDuringOpenFile(source_file_location.to_string(), e))?;
296        let file_contents = file_contents
297            .read_bytes_at(0, file_contents.len())
298            .map_err(|e| {
299                Error::HelperErrorDuringFileReading(source_file_location.to_string(), e)
300            })?;
301        Ok(String::from_utf8_lossy(file_contents).to_string())
302    }
303
304    /// Obtain a symbol map for the library, given the (partial) `LibraryInfo`.
305    /// At least the debug_id has to be given.
306    pub async fn load_symbol_map(&self, library_info: &LibraryInfo) -> Result<SymbolMap<H>, Error> {
307        if let Some((fl, symbol_map)) = self
308            .helper()
309            .as_ref()
310            .get_symbol_map_for_library(library_info)
311        {
312            return Ok(SymbolMap::with_symbol_map_trait(fl, symbol_map));
313        }
314
315        let debug_id = match library_info.debug_id {
316            Some(debug_id) => debug_id,
317            None => return Err(Error::NotEnoughInformationToIdentifySymbolMap),
318        };
319
320        let candidate_paths = self
321            .helper
322            .get_candidate_paths_for_debug_file(library_info)
323            .map_err(|e| {
324                Error::HelperErrorDuringGetCandidatePathsForDebugFile(
325                    Box::new(library_info.clone()),
326                    e,
327                )
328            })?;
329
330        let mut all_errors = Vec::new();
331        for candidate_info in candidate_paths {
332            let symbol_map = match candidate_info {
333                CandidatePathInfo::SingleFile(file_location) => {
334                    self.load_symbol_map_from_location(
335                        file_location,
336                        Some(MultiArchDisambiguator::DebugId(debug_id)),
337                    )
338                    .await
339                }
340                CandidatePathInfo::InDyldCache {
341                    dyld_cache_path,
342                    dylib_path,
343                } => {
344                    macho::load_symbol_map_for_dyld_cache(
345                        dyld_cache_path,
346                        dylib_path,
347                        &*self.helper,
348                    )
349                    .await
350                }
351            };
352
353            match symbol_map {
354                Ok(symbol_map) if symbol_map.debug_id() == debug_id => return Ok(symbol_map),
355                Ok(symbol_map) => {
356                    all_errors.push(Error::UnmatchedDebugId(symbol_map.debug_id(), debug_id));
357                }
358                Err(e) => {
359                    all_errors.push(e);
360                }
361            }
362        }
363        let err = match all_errors.len() {
364            0 => Error::NoCandidatePathForDebugFile(Box::new(library_info.clone())),
365            1 => all_errors.pop().unwrap(),
366            _ => Error::NoSuccessfulCandidate(all_errors),
367        };
368        Err(err)
369    }
370
371    /// Load and return an external file which may contain additional debug info.
372    ///
373    /// This is used on macOS: When linking multiple `.o` files together into a library or
374    /// an executable, the linker does not copy the dwarf sections into the linked output.
375    /// Instead, it stores the paths to those original `.o` files, using OSO stabs entries.
376    ///
377    /// A `SymbolMap` for such a linked file will not find debug info, and will return
378    /// `FramesLookupResult::External` from the lookups. Then the address needs to be
379    /// looked up in the external file.
380    ///
381    /// Also see `SymbolMap::lookup_external`.
382    pub async fn load_external_file(
383        &self,
384        debug_file_location: &H::FL,
385        external_file_path: &str,
386    ) -> Result<ExternalFileSymbolMap<H::F>, Error> {
387        let external_file_location = debug_file_location
388            .location_for_external_object_file(external_file_path)
389            .ok_or(Error::FileLocationRefusedExternalObjectLocation)?;
390        external_file::load_external_file(&*self.helper, external_file_location, external_file_path)
391            .await
392    }
393
394    async fn load_binary_from_dyld_cache(
395        &self,
396        dyld_cache_path: FL,
397        dylib_path: String,
398    ) -> Result<BinaryImage<F>, Error> {
399        macho::load_binary_from_dyld_cache(dyld_cache_path, dylib_path, &*self.helper).await
400    }
401
402    /// Returns the binary for the given (partial) [`LibraryInfo`].
403    ///
404    /// This consults the helper to get candidate paths to the binary.
405    pub async fn load_binary(&self, info: &LibraryInfo) -> Result<BinaryImage<F>, Error> {
406        // Require at least either the code ID or a (debug_name, debug_id) pair.
407        if info.code_id.is_none() && (info.debug_name.is_none() || info.debug_id.is_none()) {
408            return Err(Error::NotEnoughInformationToIdentifyBinary);
409        }
410
411        let candidate_paths_for_binary = self
412            .helper
413            .get_candidate_paths_for_binary(info)
414            .map_err(Error::HelperErrorDuringGetCandidatePathsForBinary)?;
415
416        let disambiguator = match (&info.debug_id, &info.arch) {
417            (Some(debug_id), _) => Some(MultiArchDisambiguator::DebugId(*debug_id)),
418            (None, Some(arch)) => Some(MultiArchDisambiguator::Arch(arch.clone())),
419            (None, None) => None,
420        };
421
422        let mut last_err = None;
423        for candidate_info in candidate_paths_for_binary {
424            let image = match candidate_info {
425                CandidatePathInfo::SingleFile(file_location) => {
426                    self.load_binary_at_location(
427                        file_location,
428                        info.name.clone(),
429                        None,
430                        disambiguator.clone(),
431                    )
432                    .await
433                }
434                CandidatePathInfo::InDyldCache {
435                    dyld_cache_path,
436                    dylib_path,
437                } => {
438                    self.load_binary_from_dyld_cache(dyld_cache_path, dylib_path)
439                        .await
440                }
441            };
442
443            match image {
444                Ok(image) => {
445                    let e = if let Some(expected_debug_id) = info.debug_id {
446                        if image.debug_id() == Some(expected_debug_id) {
447                            return Ok(image);
448                        }
449                        Error::UnmatchedDebugIdOptional(expected_debug_id, image.debug_id())
450                    } else if let Some(expected_code_id) = info.code_id.as_ref() {
451                        if image.code_id().as_ref() == Some(expected_code_id) {
452                            return Ok(image);
453                        }
454                        Error::UnmatchedCodeId(expected_code_id.clone(), image.code_id())
455                    } else {
456                        panic!(
457                            "We checked earlier that we have at least one of debug_id / code_id."
458                        )
459                    };
460                    last_err = Some(e);
461                }
462                Err(e) => {
463                    last_err = Some(e);
464                }
465            }
466        }
467        Err(last_err.unwrap_or_else(|| {
468            Error::NoCandidatePathForBinary(info.debug_name.clone(), info.debug_id)
469        }))
470    }
471
472    pub async fn load_binary_for_dyld_cache_image(
473        &self,
474        dylib_path: &str,
475        multi_arch_disambiguator: Option<MultiArchDisambiguator>,
476    ) -> Result<BinaryImage<F>, Error> {
477        let arch = match &multi_arch_disambiguator {
478            Some(MultiArchDisambiguator::Arch(arch)) => Some(arch.as_str()),
479            _ => None,
480        };
481        let dyld_shared_cache_paths = self
482            .helper
483            .get_dyld_shared_cache_paths(arch)
484            .map_err(Error::HelperErrorDuringGetDyldSharedCachePaths)?;
485
486        let mut err = None;
487        for dyld_cache_path in dyld_shared_cache_paths {
488            let binary_res = self
489                .load_binary_from_dyld_cache(dyld_cache_path, dylib_path.to_owned())
490                .await;
491            match (&multi_arch_disambiguator, binary_res) {
492                (Some(MultiArchDisambiguator::DebugId(expected_debug_id)), Ok(binary)) => {
493                    if binary.debug_id().as_ref() == Some(expected_debug_id) {
494                        return Ok(binary);
495                    }
496                    err = Some(Error::UnmatchedDebugIdOptional(
497                        *expected_debug_id,
498                        binary.debug_id(),
499                    ));
500                }
501                (_, Ok(binary)) => return Ok(binary),
502                (_, Err(e)) => err = Some(e),
503            }
504        }
505        Err(err.unwrap_or(Error::NoCandidatePathForDyldCache))
506    }
507
508    pub async fn load_symbol_map_for_dyld_cache_image(
509        &self,
510        dylib_path: &str,
511        multi_arch_disambiguator: Option<MultiArchDisambiguator>,
512    ) -> Result<SymbolMap<H>, Error> {
513        let arch = match &multi_arch_disambiguator {
514            Some(MultiArchDisambiguator::Arch(arch)) => Some(arch.as_str()),
515            _ => None,
516        };
517        let dyld_shared_cache_paths = self
518            .helper
519            .get_dyld_shared_cache_paths(arch)
520            .map_err(Error::HelperErrorDuringGetDyldSharedCachePaths)?;
521
522        let mut err = None;
523        for dyld_cache_path in dyld_shared_cache_paths {
524            let symbol_map_res = macho::load_symbol_map_for_dyld_cache(
525                dyld_cache_path,
526                dylib_path.to_owned(),
527                &*self.helper,
528            )
529            .await;
530            match (&multi_arch_disambiguator, symbol_map_res) {
531                (Some(MultiArchDisambiguator::DebugId(expected_debug_id)), Ok(symbol_map)) => {
532                    if &symbol_map.debug_id() == expected_debug_id {
533                        return Ok(symbol_map);
534                    }
535                    err = Some(Error::UnmatchedDebugId(
536                        symbol_map.debug_id(),
537                        *expected_debug_id,
538                    ));
539                }
540                (_, Ok(symbol_map)) => return Ok(symbol_map),
541                (_, Err(e)) => err = Some(e),
542            }
543        }
544        Err(err.unwrap_or(Error::NoCandidatePathForDyldCache))
545    }
546
547    pub async fn load_symbol_map_from_location(
548        &self,
549        file_location: FL,
550        multi_arch_disambiguator: Option<MultiArchDisambiguator>,
551    ) -> Result<SymbolMap<H>, Error> {
552        let file_contents = self
553            .helper
554            .load_file(file_location.clone())
555            .await
556            .map_err(|e| Error::HelperErrorDuringOpenFile(file_location.to_string(), e))?;
557
558        let file_contents = FileContentsWrapper::new(file_contents);
559
560        if let Ok(file_kind) = FileKind::parse(&file_contents) {
561            match file_kind {
562                FileKind::Elf32 | FileKind::Elf64 => {
563                    elf::load_symbol_map_for_elf(
564                        file_location,
565                        file_contents,
566                        file_kind,
567                        self.helper(),
568                    )
569                    .await
570                }
571                FileKind::MachOFat32 | FileKind::MachOFat64 => {
572                    let member = macho::get_fat_archive_member(
573                        &file_contents,
574                        file_kind,
575                        multi_arch_disambiguator,
576                    )?;
577                    macho::get_symbol_map_for_fat_archive_member(
578                        file_location,
579                        file_contents,
580                        member,
581                        self.helper(),
582                    )
583                }
584                FileKind::MachO32 | FileKind::MachO64 => {
585                    macho::get_symbol_map_for_macho(file_location, file_contents, self.helper())
586                }
587                FileKind::Pe32 | FileKind::Pe64 => {
588                    match windows::load_symbol_map_for_pdb_corresponding_to_binary(
589                        file_kind,
590                        &file_contents,
591                        file_location.clone(),
592                        &*self.helper,
593                    )
594                    .await
595                    {
596                        Ok(symbol_map) => Ok(symbol_map),
597                        Err(_) => windows::get_symbol_map_for_pe(
598                            file_contents,
599                            file_kind,
600                            file_location,
601                            self.helper(),
602                        ),
603                    }
604                }
605                _ => Err(Error::InvalidInputError(
606                    "Input was Archive, Coff or Wasm format, which are unsupported for now",
607                )),
608            }
609        } else if windows::is_pdb_file(&file_contents) {
610            windows::get_symbol_map_for_pdb(file_contents, file_location)
611        } else if breakpad::is_breakpad_file(&file_contents) {
612            let index_file_contents =
613                if let Some(index_file_location) = file_location.location_for_breakpad_symindex() {
614                    self.helper
615                        .load_file(index_file_location)
616                        .await
617                        .ok()
618                        .map(FileContentsWrapper::new)
619                } else {
620                    None
621                };
622            let symbol_map =
623                breakpad::get_symbol_map_for_breakpad_sym(file_contents, index_file_contents)?;
624            Ok(SymbolMap::new_plain(file_location, Box::new(symbol_map)))
625        } else if jitdump::is_jitdump_file(&file_contents) {
626            jitdump::get_symbol_map_for_jitdump(file_contents, file_location)
627        } else {
628            Err(Error::InvalidInputError(
629            "The file does not have a known format; PDB::open was not able to parse it and object::FileKind::parse was not able to detect the format.",
630        ))
631        }
632    }
633
634    pub async fn load_binary_at_location(
635        &self,
636        file_location: H::FL,
637        name: Option<String>,
638        path: Option<String>,
639        multi_arch_disambiguator: Option<MultiArchDisambiguator>,
640    ) -> Result<BinaryImage<F>, Error> {
641        let file_contents = self
642            .helper
643            .load_file(file_location.clone())
644            .await
645            .map_err(|e| Error::HelperErrorDuringOpenFile(file_location.to_string(), e))?;
646
647        let file_contents = FileContentsWrapper::new(file_contents);
648
649        let file_kind = match FileKind::parse(&file_contents) {
650            Ok(file_kind) => file_kind,
651            Err(_) if jitdump::is_jitdump_file(&file_contents) => {
652                let cursor = FileContentsCursor::new(&file_contents);
653                let reader = JitDumpReader::new(cursor)?;
654                let index = JitDumpIndex::from_reader(reader).map_err(Error::JitDumpFileReading)?;
655                let inner = BinaryImageInner::JitDump(file_contents, index);
656                return BinaryImage::new(inner, name, path);
657            }
658            Err(_) => {
659                return Err(Error::InvalidInputError("Unrecognized file"));
660            }
661        };
662        let inner = match file_kind {
663            FileKind::Elf32
664            | FileKind::Elf64
665            | FileKind::MachO32
666            | FileKind::MachO64
667            | FileKind::Pe32
668            | FileKind::Pe64 => BinaryImageInner::Normal(file_contents, file_kind),
669            FileKind::MachOFat32 | FileKind::MachOFat64 => {
670                let member = macho::get_fat_archive_member(
671                    &file_contents,
672                    file_kind,
673                    multi_arch_disambiguator,
674                )?;
675                let (offset, size) = member.offset_and_size;
676                let arch = member.arch;
677                let data = macho::MachOFatArchiveMemberData::new(file_contents, offset, size, arch);
678                BinaryImageInner::MemberOfFatArchive(data, file_kind)
679            }
680            _ => {
681                return Err(Error::InvalidInputError(
682                    "Input was Archive, Coff or Wasm format, which are unsupported for now",
683                ))
684            }
685        };
686        BinaryImage::new(inner, name, path)
687    }
688}