symbolic_symcache/
lookup.rs

1use std::fmt;
2
3use symbolic_common::{Language, Name, NameMangling};
4
5use super::{raw, SymCache};
6
7impl<'data> SymCache<'data> {
8    /// Looks up an instruction address in the SymCache, yielding an iterator of [`SourceLocation`]s
9    /// representing a hierarchy of inlined function calls.
10    pub fn lookup(&self, addr: u64) -> SourceLocations<'data, '_> {
11        let addr = match u32::try_from(addr) {
12            Ok(addr) => addr,
13            Err(_) => {
14                return SourceLocations {
15                    cache: self,
16                    source_location_idx: u32::MAX,
17                }
18            }
19        };
20
21        let source_location_start = (self.source_locations.len() - self.ranges.len()) as u32;
22        let mut source_location_idx = match self.ranges.binary_search_by_key(&addr, |r| r.0) {
23            Ok(idx) => source_location_start + idx as u32,
24            Err(0) => u32::MAX,
25            Err(idx) => source_location_start + idx as u32 - 1,
26        };
27
28        if let Some(source_location) = self.source_locations.get(source_location_idx as usize) {
29            if *source_location == raw::NO_SOURCE_LOCATION {
30                source_location_idx = u32::MAX;
31            }
32        }
33
34        SourceLocations {
35            cache: self,
36            source_location_idx,
37        }
38    }
39
40    pub(crate) fn get_file(&self, file_idx: u32) -> Option<File<'data>> {
41        let raw_file = self.files.get(file_idx as usize)?;
42        Some(File {
43            comp_dir: self.get_string(raw_file.comp_dir_offset),
44            directory: self.get_string(raw_file.directory_offset),
45            name: self.get_string(raw_file.name_offset).unwrap_or_default(),
46        })
47    }
48
49    pub(crate) fn get_function(&self, function_idx: u32) -> Option<Function<'data>> {
50        let raw_function = self.functions.get(function_idx as usize)?;
51        Some(Function {
52            name: self.get_string(raw_function.name_offset).unwrap_or("?"),
53            entry_pc: raw_function.entry_pc,
54            language: Language::from_u32(raw_function.lang),
55        })
56    }
57
58    /// An iterator over the functions in this SymCache.
59    ///
60    /// Only functions with a valid entry pc, i.e., one not equal to `u32::MAX`,
61    /// will be returned.
62    /// Note that functions are *not* returned ordered by name or entry pc,
63    /// but in insertion order, which is essentially random.
64    pub fn functions(&self) -> Functions<'data> {
65        Functions {
66            cache: self.clone(),
67            function_idx: 0,
68        }
69    }
70
71    /// An iterator over the files in this SymCache.
72    ///
73    /// Note that files are *not* returned ordered by name or full path,
74    /// but in insertion order, which is essentially random.
75    pub fn files(&self) -> Files<'data> {
76        Files {
77            cache: self.clone(),
78            file_idx: 0,
79        }
80    }
81}
82
83/// A source File included in the SymCache.
84#[derive(Debug, Clone)]
85pub struct File<'data> {
86    /// The optional compilation directory prefix.
87    comp_dir: Option<&'data str>,
88    /// The optional directory prefix.
89    directory: Option<&'data str>,
90    /// The file path.
91    name: &'data str,
92}
93
94impl File<'_> {
95    /// Returns this file's full path.
96    pub fn full_path(&self) -> String {
97        let comp_dir = self.comp_dir.unwrap_or_default();
98        let directory = self.directory.unwrap_or_default();
99
100        let prefix = symbolic_common::join_path(comp_dir, directory);
101        let full_path = symbolic_common::join_path(&prefix, self.name);
102        let full_path = symbolic_common::clean_path(&full_path).into_owned();
103
104        full_path
105    }
106}
107
108/// A Function definition as included in the SymCache.
109#[derive(Clone, Debug)]
110pub struct Function<'data> {
111    name: &'data str,
112    entry_pc: u32,
113    language: Language,
114}
115
116impl<'data> Function<'data> {
117    /// The possibly mangled name/symbol of this function.
118    pub fn name(&self) -> &'data str {
119        self.name
120    }
121
122    /// The possibly mangled name/symbol of this function, suitable for demangling.
123    pub fn name_for_demangling(&self) -> Name<'data> {
124        Name::new(self.name, NameMangling::Unknown, self.language)
125    }
126
127    /// The entry pc of the function.
128    pub fn entry_pc(&self) -> u32 {
129        self.entry_pc
130    }
131
132    /// The language the function is written in.
133    pub fn language(&self) -> Language {
134        self.language
135    }
136}
137
138impl Default for Function<'_> {
139    fn default() -> Self {
140        Self {
141            name: "?",
142            entry_pc: u32::MAX,
143            language: Language::Unknown,
144        }
145    }
146}
147
148/// A source location as included in the SymCache.
149///
150/// A `SourceLocation` represents source information about a particular instruction.
151/// It always has a `[Function]` associated with it and may also have a `[File]` and a line number.
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct SourceLocation<'data, 'cache> {
154    pub(crate) cache: &'cache SymCache<'data>,
155    pub(crate) source_location: &'data raw::SourceLocation,
156}
157
158impl<'data> SourceLocation<'data, '_> {
159    /// The source line corresponding to the instruction.
160    ///
161    /// 0 denotes an unknown line number.
162    pub fn line(&self) -> u32 {
163        self.source_location.line
164    }
165
166    /// The source file corresponding to the instruction.
167    pub fn file(&self) -> Option<File<'data>> {
168        self.cache.get_file(self.source_location.file_idx)
169    }
170
171    /// The function corresponding to the instruction.
172    pub fn function(&self) -> Function<'data> {
173        self.cache
174            .get_function(self.source_location.function_idx)
175            .unwrap_or_default()
176    }
177
178    // TODO: maybe forward some of the `File` and `Function` accessors, such as:
179    // `function_name` or `full_path` for convenience.
180}
181
182/// An Iterator that yields [`SourceLocation`]s, representing an inlining hierarchy.
183#[derive(Debug, Clone)]
184pub struct SourceLocations<'data, 'cache> {
185    pub(crate) cache: &'cache SymCache<'data>,
186    pub(crate) source_location_idx: u32,
187}
188
189impl<'data, 'cache> Iterator for SourceLocations<'data, 'cache> {
190    type Item = SourceLocation<'data, 'cache>;
191
192    fn next(&mut self) -> Option<Self::Item> {
193        if self.source_location_idx == u32::MAX {
194            return None;
195        }
196        self.cache
197            .source_locations
198            .get(self.source_location_idx as usize)
199            .map(|source_location| {
200                self.source_location_idx = source_location.inlined_into_idx;
201                SourceLocation {
202                    cache: self.cache,
203                    source_location,
204                }
205            })
206    }
207}
208
209/// Iterator returned by [`SymCache::functions`]; see documentation there.
210#[derive(Debug, Clone)]
211pub struct Functions<'data> {
212    cache: SymCache<'data>,
213    function_idx: u32,
214}
215
216impl<'data> Iterator for Functions<'data> {
217    type Item = Function<'data>;
218
219    fn next(&mut self) -> Option<Self::Item> {
220        let mut function = self.cache.get_function(self.function_idx);
221
222        while let Some(ref f) = function {
223            if f.entry_pc == u32::MAX {
224                self.function_idx += 1;
225                function = self.cache.get_function(self.function_idx);
226            } else {
227                break;
228            }
229        }
230
231        if function.is_some() {
232            self.function_idx += 1;
233        }
234
235        function
236    }
237}
238
239/// A helper struct for printing the functions contained in a symcache.
240///
241/// This struct's `Debug` impl prints the entry pcs and names of the
242/// functions returned by [`SymCache::functions`], sorted first by entry pc
243/// and then by name.
244pub struct FunctionsDebug<'a>(pub &'a SymCache<'a>);
245
246impl fmt::Debug for FunctionsDebug<'_> {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        let mut vec: Vec<_> = self.0.functions().collect();
249
250        vec.sort_by_key(|f| (f.entry_pc, f.name));
251        for function in vec {
252            writeln!(f, "{:>16x} {}", &function.entry_pc, &function.name)?;
253        }
254
255        Ok(())
256    }
257}
258
259/// Iterator returned by [`SymCache::files`]; see documentation there.
260#[derive(Debug, Clone)]
261pub struct Files<'data> {
262    cache: SymCache<'data>,
263    file_idx: u32,
264}
265
266impl<'data> Iterator for Files<'data> {
267    type Item = File<'data>;
268
269    fn next(&mut self) -> Option<Self::Item> {
270        let file = self.cache.get_file(self.file_idx);
271        if file.is_some() {
272            self.file_idx += 1;
273        }
274        file
275    }
276}
277
278/// A helper struct for printing the files contained in a symcache.
279///
280/// This struct's `Debug` impl prints the full paths of the
281/// files returned by [`SymCache::files`] in sorted order.
282pub struct FilesDebug<'a>(pub &'a SymCache<'a>);
283
284impl fmt::Debug for FilesDebug<'_> {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        let mut vec: Vec<_> = self.0.files().map(|f| f.full_path()).collect();
287
288        vec.sort();
289        for file in vec {
290            writeln!(f, "{file}")?;
291        }
292
293        Ok(())
294    }
295}