fxprof_processed_profile/
library_info.rs

1use std::sync::Arc;
2
3use debugid::DebugId;
4use serde::ser::{Serialize, SerializeMap, Serializer};
5
6/// A library ("binary" / "module" / "DSO") which is loaded into a process.
7/// This can be the main executable file or a dynamic library, or any other
8/// mapping of executable memory.
9///
10/// Library information makes after-the-fact symbolication possible: The
11/// profile JSON contains raw code addresses, and then the symbols for these
12/// addresses get resolved later.
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14pub struct LibraryInfo {
15    /// The name of this library that should be displayed in the profiler.
16    /// Usually this is the filename of the binary, but it could also be any other
17    /// name, such as "\[kernel.kallsyms\]" or "\[vdso\]" or "JIT".
18    pub name: String,
19    /// The debug name of this library which should be used when looking up symbols.
20    /// On Windows this is the filename of the PDB file, on other platforms it's
21    /// usually the same as the filename of the binary.
22    pub debug_name: String,
23    /// The absolute path to the binary file.
24    pub path: String,
25    /// The absolute path to the debug file. On Linux and macOS this is the same as
26    /// the path to the binary file. On Windows this is the path to the PDB file.
27    pub debug_path: String,
28    /// The debug ID of the library. This lets symbolication confirm that it's
29    /// getting symbols for the right file, and it can sometimes allow obtaining a
30    /// symbol file from a symbol server.
31    pub debug_id: DebugId,
32    /// The code ID of the library. This lets symbolication confirm that it's
33    /// getting symbols for the right file, and it can sometimes allow obtaining a
34    /// symbol file from a symbol server.
35    pub code_id: Option<String>,
36    /// An optional string with the CPU arch of this library, for example "x86_64",
37    /// "arm64", or "arm64e". This is used for macOS system libraries in the dyld
38    /// shared cache, in order to avoid loading the wrong cache files, as a
39    /// performance optimization. In the past, this was also used to find the
40    /// correct sub-binary in a mach-O fat binary. But we now use the debug_id for that
41    /// purpose.
42    pub arch: Option<String>,
43    /// An optional symbol table, for "pre-symbolicating" stack frames.
44    ///
45    /// Usually, symbolication is something that should happen asynchronously,
46    /// because it can be very slow, so the regular way to use the profiler is to
47    /// store only frame addresses and no symbols in the profile JSON, and perform
48    /// symbolication only once the profile is loaded in the Firefox Profiler UI.
49    ///
50    /// However, sometimes symbols are only available during recording and are not
51    /// easily accessible afterwards. One such example the symbol table of the
52    /// Linux kernel: Users with root privileges can access the symbol table of the
53    /// currently-running kernel via `/proc/kallsyms`, but we don't want to have
54    /// to run the local symbol server with root privileges. So it's easier to
55    /// resolve kernel symbols when generating the profile JSON.
56    ///
57    /// This way of symbolicating does not support file names, line numbers, or
58    /// inline frames. It is intended for relatively "small" symbol tables for which
59    /// an address lookup is fast.
60    pub symbol_table: Option<Arc<SymbolTable>>,
61}
62
63impl Serialize for LibraryInfo {
64    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
65        let breakpad_id = self.debug_id.breakpad().to_string();
66        let code_id = self.code_id.as_ref().map(|cid| cid.to_string());
67        let mut map = serializer.serialize_map(None)?;
68        map.serialize_entry("name", &self.name)?;
69        map.serialize_entry("path", &self.path)?;
70        map.serialize_entry("debugName", &self.debug_name)?;
71        map.serialize_entry("debugPath", &self.debug_path)?;
72        map.serialize_entry("breakpadId", &breakpad_id)?;
73        map.serialize_entry("codeId", &code_id)?;
74        map.serialize_entry("arch", &self.arch)?;
75        map.end()
76    }
77}
78
79/// A symbol table which contains a list of [`Symbol`]s, used in [`LibraryInfo`].
80#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
81pub struct SymbolTable {
82    symbols: Vec<Symbol>,
83}
84
85impl SymbolTable {
86    /// Create a [`SymbolTable`] from a list of [`Symbol`]s.
87    pub fn new(mut symbols: Vec<Symbol>) -> Self {
88        symbols.sort();
89        symbols.dedup_by_key(|symbol| symbol.address);
90        Self { symbols }
91    }
92
93    /// Look up the symbol for an address. This address is relative to the library's base address.
94    pub fn lookup(&self, address: u32) -> Option<&Symbol> {
95        let index = match self
96            .symbols
97            .binary_search_by_key(&address, |symbol| symbol.address)
98        {
99            Ok(i) => i,
100            Err(0) => return None,
101            Err(next_i) => next_i - 1,
102        };
103        let symbol = &self.symbols[index];
104        match symbol.size {
105            Some(size) if address < symbol.address.saturating_add(size) => Some(symbol),
106            Some(_size) => None,
107            None => Some(symbol),
108        }
109    }
110}
111
112/// A single symbol from a [`SymbolTable`].
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
114pub struct Symbol {
115    /// The symbol's address, as a "relative address", i.e. relative to the library's base address.
116    pub address: u32,
117    /// The symbol's size, if known. This is often just set based on the address of the next symbol.
118    pub size: Option<u32>,
119    /// The symbol name.
120    pub name: String,
121}