Skip to main content

gsym/
model.rs

1/// A half-open virtual-address range, `[start, end)`.
2///
3/// Addresses are the unslid virtual addresses of the image the data describes,
4/// so a runtime address must have its load bias removed before it is compared
5/// against a range. See [`docs::symbolication`](crate::docs::symbolication).
6///
7/// An empty range is legal and means a function whose size the producer did not
8/// know. [`Self::contains`] reports `false` for every address in that case, but
9/// address lookup still resolves such a function, for addresses from its start
10/// up to the next function.
11///
12/// ```
13/// use gsym::AddressRange;
14///
15/// let range = AddressRange::new(0x1000, 0x1020);
16/// assert_eq!(range.size(), 0x20);
17/// assert!(range.contains(0x1000));
18/// assert!(!range.contains(0x1020));
19///
20/// // Endpoints are not checked on construction.
21/// let reversed = AddressRange::new(0x20, 0x10);
22/// assert!(!reversed.is_valid());
23/// assert_eq!(reversed.size(), 0);
24/// assert!(reversed.is_empty());
25/// ```
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
27pub struct AddressRange {
28    /// Inclusive start address.
29    pub start: u64,
30    /// Exclusive end address.
31    pub end: u64,
32}
33
34impl AddressRange {
35    /// Creates a range without validating endpoint order.
36    #[must_use]
37    pub const fn new(start: u64, end: u64) -> Self {
38        Self { start, end }
39    }
40
41    /// Returns the range width, or zero for reversed endpoints.
42    #[must_use]
43    pub const fn size(self) -> u64 {
44        self.end.saturating_sub(self.start)
45    }
46
47    /// Returns whether the start is less than or equal to the end.
48    #[must_use]
49    pub const fn is_valid(self) -> bool {
50        self.start <= self.end
51    }
52
53    /// Returns whether the range covers no addresses, including reversed ones.
54    ///
55    /// This is defined as [`Self::size`] being zero, so it is also true for the
56    /// reversed endpoints [`Self::is_valid`] rejects: such a range covers
57    /// nothing, and [`Self::contains`] reports `false` for every address in it.
58    #[must_use]
59    pub const fn is_empty(self) -> bool {
60        self.size() == 0
61    }
62
63    /// Returns whether `address` lies in this valid half-open range.
64    #[must_use]
65    pub const fn contains(self, address: u64) -> bool {
66        self.is_valid() && self.start <= address && address < self.end
67    }
68
69    /// Returns whether this valid range fully contains `other`.
70    #[must_use]
71    pub const fn contains_range(self, other: Self) -> bool {
72        self.is_valid() && other.is_valid() && self.start <= other.start && other.end <= self.end
73    }
74}
75
76/// A source file split into directory and basename, as GSYM stores it.
77///
78/// Neither half is required to be valid UTF-8, and neither is normalized: what
79/// the producer wrote is what a reader gets back.
80///
81/// [`GsymBuilder::add_file`](crate::GsymBuilder::add_file) interns entries and
82/// returns the [`FileIndex`] that line rows and inline nodes refer to.
83#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
84pub struct FileEntry {
85    /// Directory component as raw string-table bytes.
86    pub directory: Vec<u8>,
87    /// Basename component as raw string-table bytes.
88    pub basename: Vec<u8>,
89}
90
91impl FileEntry {
92    /// Creates a source-file entry from raw directory and basename bytes.
93    #[must_use]
94    pub fn new(directory: impl Into<Vec<u8>>, basename: impl Into<Vec<u8>>) -> Self {
95        Self {
96            directory: directory.into(),
97            basename: basename.into(),
98        }
99    }
100}
101
102/// An index into a GSYM file table.
103///
104/// Index [`ZERO`](Self::ZERO) is reserved for the empty file entry, so real
105/// files are numbered from 1. A line row that carries index zero has no known
106/// source file rather than a file named `""`.
107#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
108#[repr(transparent)]
109pub struct FileIndex(u32);
110
111impl FileIndex {
112    /// Reserved index representing the empty file entry.
113    pub const ZERO: Self = Self(0);
114
115    /// Wraps a raw file-table index.
116    #[must_use]
117    pub const fn new(index: u32) -> Self {
118        Self(index)
119    }
120
121    /// Returns the raw file-table index.
122    #[must_use]
123    pub const fn get(self) -> u32 {
124        self.0
125    }
126}
127
128impl From<u32> for FileIndex {
129    fn from(index: u32) -> Self {
130        Self::new(index)
131    }
132}
133
134impl From<FileIndex> for u32 {
135    fn from(index: FileIndex) -> Self {
136        index.get()
137    }
138}
139
140impl From<FileIndex> for u64 {
141    fn from(index: FileIndex) -> Self {
142        Self::from(index.get())
143    }
144}
145
146/// One address-to-source-row mapping.
147///
148/// A row stays in effect from its address until the next row's address, so a
149/// function's rows must be sorted and must start at or after the function's
150/// start address. The writer rejects rows that violate either rule.
151#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
152pub struct LineEntry {
153    /// Unslid virtual address at which this row becomes active.
154    pub address: u64,
155    /// Source file-table index.
156    pub file: FileIndex,
157    /// One-based source line number, or zero when unknown.
158    pub line: u32,
159}
160
161impl LineEntry {
162    /// Creates a source-line row.
163    #[must_use]
164    pub const fn new(address: u64, file: FileIndex, line: u32) -> Self {
165        Self {
166            address,
167            file,
168            line,
169        }
170    }
171}
172
173/// Recursive inline-call information for a function.
174///
175/// The root node covers the function itself, and each child covers the address
176/// ranges occupied by one inlined call inside its parent. A child's ranges must
177/// be contained by its parent's, and sibling ranges must not overlap.
178///
179/// `call_file` and `call_line` describe where the call appears in the *parent*,
180/// not where the inlined body was defined. Address lookup reads them from the
181/// callee to give each outer frame in a [`Lookup`] its source position.
182#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
183pub struct InlineNode {
184    /// Sorted, disjoint address ranges covered by this inline invocation.
185    pub ranges: Vec<AddressRange>,
186    /// Function name as raw string-table bytes.
187    pub name: Vec<u8>,
188    /// File containing the call site in the parent frame.
189    pub call_file: FileIndex,
190    /// Source line containing the call site in the parent frame.
191    pub call_line: u32,
192    /// Nested inline invocations.
193    pub children: Vec<Self>,
194}
195
196/// Metadata for a call instruction's return address.
197///
198/// [`match_regex`](Self::match_regex) names the callees that may return to this
199/// address, so a stack walker can check it against the frame above. This crate
200/// stores and returns the patterns without interpreting them.
201#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
202pub struct CallSite {
203    /// Return-address offset relative to the containing function start.
204    pub return_offset: u64,
205    /// Classification bits retained from the input.
206    pub flags: CallSiteFlags,
207    /// Regular-expression strings describing possible callees.
208    pub match_regex: Vec<Vec<u8>>,
209}
210
211/// Forward-compatible GSYM call-site flag bits.
212///
213/// Bits this crate does not define are kept rather than cleared, so they
214/// survive a decode and re-encode. Use
215/// [`from_bits_retain`](Self::from_bits_retain) to construct a value from a raw
216/// byte and [`bits`](Self::bits) to get it back.
217#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
218#[repr(transparent)]
219pub struct CallSiteFlags(u8);
220
221impl CallSiteFlags {
222    /// Call target is internal to the image.
223    pub const INTERNAL: Self = Self(1 << 0);
224    /// Call target may be external to the image.
225    pub const EXTERNAL: Self = Self(1 << 1);
226
227    /// Wraps all bits, including values unknown to this crate.
228    #[must_use]
229    pub const fn from_bits_retain(bits: u8) -> Self {
230        Self(bits)
231    }
232
233    /// Returns the raw flag byte.
234    #[must_use]
235    pub const fn bits(self) -> u8 {
236        self.0
237    }
238
239    /// Returns whether no bits are set.
240    #[must_use]
241    pub const fn is_empty(self) -> bool {
242        self.0 == 0
243    }
244
245    /// Returns whether every bit in `flag` is present.
246    #[must_use]
247    pub const fn contains(self, flag: Self) -> bool {
248        self.0 & flag.0 == flag.0
249    }
250}
251
252impl std::ops::BitOr for CallSiteFlags {
253    type Output = Self;
254
255    fn bitor(self, rhs: Self) -> Self::Output {
256        Self(self.0 | rhs.0)
257    }
258}
259
260impl std::ops::BitOrAssign for CallSiteFlags {
261    fn bitor_assign(&mut self, rhs: Self) {
262        self.0 |= rhs.0;
263    }
264}
265
266impl From<u8> for CallSiteFlags {
267    fn from(value: u8) -> Self {
268        Self::from_bits_retain(value)
269    }
270}
271
272impl From<CallSiteFlags> for u8 {
273    fn from(value: CallSiteFlags) -> Self {
274        value.bits()
275    }
276}
277
278/// Owned semantic function data accepted by a GSYM builder.
279///
280/// Only [`range`](Self::range) and [`name`](Self::name) are required;
281/// [`Function::new`] fills in the rest as empty. A function with no line rows
282/// and no inline tree still resolves an address to a name and an offset.
283///
284/// ```
285/// use gsym::{AddressRange, Function, LineEntry, FileIndex};
286///
287/// let plain = Function::new(AddressRange::new(0x1000, 0x1010), b"plain");
288/// assert!(plain.lines.is_empty());
289///
290/// let with_lines = Function {
291///     lines: vec![LineEntry::new(0x1000, FileIndex::new(1), 42)],
292///     ..Function::new(AddressRange::new(0x1000, 0x1010), b"detailed")
293/// };
294/// assert_eq!(with_lines.lines.len(), 1);
295/// ```
296///
297/// [`merged`](Self::merged) holds aliases that share this function's address
298/// range, which identical-code folding produces. They are written only when
299/// [`FunctionSetPolicy::MergeEqualRanges`](crate::FunctionSetPolicy) is
300/// enabled.
301#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
302pub struct Function {
303    /// Function address range.
304    pub range: AddressRange,
305    /// Function name as raw string-table bytes.
306    pub name: Vec<u8>,
307    /// Sorted source-line rows belonging to this function.
308    pub lines: Vec<LineEntry>,
309    /// Root of the inline-call tree, when present.
310    pub inline: Option<InlineNode>,
311    /// Equal-range aliases stored as merged `FunctionInfo` records.
312    pub merged: Vec<Self>,
313    /// Call-site metadata for this function.
314    pub call_sites: Vec<CallSite>,
315}
316
317impl Function {
318    /// Creates a function with no optional line, inline, merged, or call-site data.
319    #[must_use]
320    pub fn new(range: AddressRange, name: impl Into<Vec<u8>>) -> Self {
321        Self {
322            range,
323            name: name.into(),
324            ..Self::default()
325        }
326    }
327}
328
329/// One source frame returned by address lookup.
330///
331/// Every field borrows from the GSYM input, so a frame cannot outlive the
332/// reader it came from. Names and paths are raw bytes and are not checked for
333/// UTF-8.
334///
335/// [`line`](Self::line) and the two path fields describe this frame's own
336/// position. For the innermost frame that is the line row covering the looked-up
337/// address; for an outer frame it is the call site recorded by the frame nested
338/// inside it. A zero line and empty paths mean the file has no line information
339/// for the address, or that the caller disabled it in
340/// [`LookupOptions`](crate::LookupOptions).
341#[derive(Clone, Copy, Debug, Eq, PartialEq)]
342#[non_exhaustive]
343pub struct LookupFrame<'data> {
344    /// Function name borrowed from the string table.
345    pub name: &'data [u8],
346    /// Source directory borrowed from the string table.
347    pub directory: &'data [u8],
348    /// Source basename borrowed from the string table.
349    pub basename: &'data [u8],
350    /// Source line, or zero when unavailable.
351    pub line: u32,
352    /// Address offset from the beginning of this frame's function or inline range.
353    pub offset: u64,
354    /// Whether this frame represents an inline invocation.
355    pub inlined: bool,
356}
357
358/// Borrowed symbolication result for one address.
359///
360/// [`frames`](Self::frames) is ordered innermost first and always holds at
361/// least one frame. Without inlining that is the only frame. With inlining,
362/// frame 0 is the deepest inlined body containing the address, the last frame
363/// is the function the linker emitted, and the frames between them are the
364/// inlined calls, so printing them in order gives the call stack.
365///
366/// [`call_site_patterns`](Self::call_site_patterns) is non-empty only when the
367/// looked-up address is exactly a recorded return address and call-site records
368/// were requested.
369///
370/// ```
371/// use gsym::{AddressRange, Function, Gsym, GsymBuilder};
372///
373/// let mut builder = GsymBuilder::new();
374/// builder.add_function(Function::new(AddressRange::new(0x1000, 0x1010), b"f"))?;
375/// let bytes = builder.to_bytes()?;
376/// let gsym = Gsym::parse(&bytes)?;
377///
378/// let hit = gsym.lookup(0x1008)?.expect("covered address");
379/// assert_eq!(hit.address, 0x1008);
380/// assert_eq!(hit.function, AddressRange::new(0x1000, 0x1010));
381/// assert_eq!(hit.frames().last().unwrap().name, b"f");
382/// # Ok::<(), gsym::Error>(())
383/// ```
384#[derive(Debug, Eq, PartialEq)]
385#[non_exhaustive]
386pub struct Lookup<'data> {
387    /// Address supplied by the caller.
388    pub address: u64,
389    /// Address range of the selected top-level function.
390    pub function: AddressRange,
391    frames: Box<[LookupFrame<'data>]>,
392    call_site_patterns: Box<[&'data [u8]]>,
393}
394
395impl<'data> Lookup<'data> {
396    pub(crate) const fn new(
397        address: u64,
398        function: AddressRange,
399        frames: Box<[LookupFrame<'data>]>,
400        call_site_patterns: Box<[&'data [u8]]>,
401    ) -> Self {
402        Self {
403            address,
404            function,
405            frames,
406            call_site_patterns,
407        }
408    }
409
410    /// Returns resolved frames ordered from innermost to outermost.
411    #[must_use]
412    pub fn frames(&self) -> &[LookupFrame<'data>] {
413        &self.frames
414    }
415
416    /// Returns call-site callee patterns for an exactly matching return address.
417    #[must_use]
418    pub fn call_site_patterns(&self) -> &[&'data [u8]] {
419        &self.call_site_patterns
420    }
421}