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}