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}