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