1mod function;
2mod layout;
3mod lookup;
4mod owned;
5
6use std::fmt;
7use std::path::Path;
8
9use smallvec::SmallVec;
10use zerocopy::byteorder::{BigEndian, LittleEndian, U16, U32, U64};
11use zerocopy::{FromBytes, Immutable, KnownLayout};
12
13use crate::GsymVersion;
14use crate::endian::{Cursor, Endian};
15use crate::error::{Error, Result};
16use crate::format::function::EncodedFunction;
17use crate::model::{AddressRange, FileIndex};
18
19pub use function::{FunctionRef, Functions};
20use function::{RawFunction, file_at, string_at};
21pub(crate) use layout::ParsedLayout;
22use layout::VersionLayout;
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31#[non_exhaustive]
32pub struct Header<'data> {
33 pub version: GsymVersion,
35 pub endian: Endian,
37 pub address_offset_size: u8,
39 pub base_address: u64,
41 pub address_count: u32,
43 pub build_id: &'data [u8],
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
65pub struct LookupOptions {
66 pub line_information: bool,
68 pub inline_frames: bool,
70 pub call_sites: bool,
72}
73
74impl Default for LookupOptions {
75 fn default() -> Self {
76 Self {
77 line_information: true,
78 inline_frames: true,
79 call_sites: true,
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
90pub struct FrameLookupOptions {
91 pub line_information: bool,
93 pub inline_frames: bool,
95}
96
97impl Default for FrameLookupOptions {
98 fn default() -> Self {
99 Self {
100 line_information: true,
101 inline_frames: true,
102 }
103 }
104}
105
106impl From<FrameLookupOptions> for LookupOptions {
107 fn from(options: FrameLookupOptions) -> Self {
108 Self {
109 line_information: options.line_information,
110 inline_frames: options.inline_frames,
111 call_sites: false,
112 }
113 }
114}
115
116#[derive(Default)]
123pub struct LookupScratch {
124 inline_frames: SmallVec<[RawInlineFrame; 4]>,
125}
126
127impl fmt::Debug for LookupScratch {
128 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
129 formatter
130 .debug_struct("LookupScratch")
131 .finish_non_exhaustive()
132 }
133}
134
135impl LookupScratch {
136 #[must_use]
138 pub fn with_capacity(inline_depth: usize) -> Self {
139 Self {
140 inline_frames: SmallVec::with_capacity(inline_depth),
141 }
142 }
143
144 fn clear(&mut self) {
145 self.inline_frames.clear();
146 }
147}
148
149#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
154#[non_exhaustive]
155pub struct VerifyReport {
156 pub functions: usize,
158 pub files: usize,
160 pub strings: usize,
162 pub function_info_bytes: usize,
164}
165
166#[derive(Clone, Copy, Debug)]
167struct RawInlineFrame {
168 name: u64,
169 call_file: FileIndex,
170 call_line: u32,
171 start: u64,
172}
173
174pub struct Gsym<D> {
208 pub(super) data: D,
209 pub(super) layout: ParsedLayout,
210}
211
212impl<D: AsRef<[u8]>> fmt::Debug for Gsym<D> {
213 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
214 formatter
215 .debug_struct("Gsym")
216 .field("byte_len", &self.data.as_ref().len())
217 .field("version", &self.header().version)
218 .field("endian", &self.layout.endian)
219 .field("base_address", &self.layout.base_address)
220 .field("function_count", &self.layout.address_count)
221 .field("build_id_len", &self.layout.build_id.len())
222 .finish_non_exhaustive()
223 }
224}
225
226impl Gsym<Vec<u8>> {
227 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
246 let path = path.as_ref();
247 let data = std::fs::read(path).map_err(|source| Error::IoAtPath {
248 operation: "read GSYM file",
249 path: path.to_path_buf(),
250 source,
251 })?;
252 Self::parse(data)
253 }
254}
255
256impl<D: AsRef<[u8]>> Gsym<D> {
257 pub fn parse(data: D) -> Result<Self> {
264 let layout = layout::parse(data.as_ref())?;
265 Ok(Self { data, layout })
266 }
267
268 #[must_use]
270 pub fn into_inner(self) -> D {
271 self.data
272 }
273
274 #[must_use]
276 pub fn header(&self) -> Header<'_> {
277 Header {
278 version: match self.layout.version {
279 VersionLayout::V1 => GsymVersion::V1,
280 VersionLayout::V2 => GsymVersion::V2,
281 },
282 endian: self.layout.endian,
283 address_offset_size: self.layout.address_offset_size,
284 base_address: self.layout.base_address,
285 address_count: self.layout.address_count,
286 build_id: self.build_id(),
287 }
288 }
289
290 #[must_use]
292 pub fn build_id(&self) -> &[u8] {
293 self.data
294 .as_ref()
295 .get(self.layout.build_id.clone())
296 .unwrap_or_default()
297 }
298
299 #[must_use]
301 pub const fn functions(&self) -> Functions<'_, D> {
302 Functions {
303 gsym: self,
304 next: 0,
305 }
306 }
307
308 pub fn function(&self, index: usize) -> Result<FunctionRef<'_>> {
315 self.get_function(index)?
316 .ok_or(Error::FunctionIndexOutOfBounds {
317 index,
318 count: self.layout.address_count as usize,
319 })
320 }
321
322 pub fn get_function(&self, index: usize) -> Result<Option<FunctionRef<'_>>> {
331 let Some(raw) = self.raw_function(index)? else {
332 return Ok(None);
333 };
334 Ok(Some(FunctionRef {
335 index,
336 name: self.string(raw.name)?,
337 all_data: self.data.as_ref(),
338 raw,
339 endian: self.layout.endian,
340 string_offset_size: self.layout.string_offset_size,
341 string_table: self.layout.string_table.clone(),
342 file_table: self.layout.file_table.clone(),
343 file_count: self.layout.file_count,
344 }))
345 }
346
347 pub(in crate::reader) fn raw_function(&self, index: usize) -> Result<Option<RawFunction<'_>>> {
348 if index >= self.layout.address_count as usize {
349 return Ok(None);
350 }
351 let start = self.address(index)?;
352 self.raw_function_at(index, start).map(Some)
353 }
354
355 #[inline]
362 pub(in crate::reader) fn raw_function_at(
363 &self,
364 index: usize,
365 start: u64,
366 ) -> Result<RawFunction<'_>> {
367 let offset = self.function_offset(index)?;
368 let section_end = self.layout.function_info.end;
369 if offset < self.layout.function_info.start || offset >= section_end {
370 return Err(Error::InvalidOffset {
371 offset: offset as u64,
372 input_len: self.data.as_ref().len(),
373 });
374 }
375 let data =
376 self.data
377 .as_ref()
378 .get(offset..section_end)
379 .ok_or_else(|| Error::InvalidOffset {
380 offset: offset as u64,
381 input_len: self.data.as_ref().len(),
382 })?;
383 let mut header = Cursor::new(data, self.layout.endian);
384 let size = header.read_u32()?;
385 let name_offset = header.read_uint(self.layout.string_offset_size)?;
386 if name_offset == 0 {
387 return Err(Error::ZeroNameOffset);
388 }
389 let end = start
390 .checked_add(u64::from(size))
391 .ok_or(Error::Overflow("function range"))?;
392 let raw = RawFunction {
393 range: AddressRange::new(start, end),
394 name: name_offset,
395 data,
396 records: data.get(header.position()..).ok_or(Error::InvalidFormat(
397 "function record header overruns its record",
398 ))?,
399 };
400 Ok(raw)
401 }
402
403 pub fn string(&self, offset: u64) -> Result<&[u8]> {
409 string_at(self.data.as_ref(), &self.layout.string_table, offset)
410 }
411
412 pub fn file(&self, index: impl Into<FileIndex>) -> Result<(&[u8], &[u8])> {
418 file_at(
419 self.data.as_ref(),
420 self.layout.endian,
421 self.layout.string_offset_size,
422 &self.layout.file_table,
423 self.layout.file_count,
424 &self.layout.string_table,
425 index.into(),
426 )
427 }
428
429 pub fn verify(&self) -> Result<VerifyReport> {
456 self.verify_with(|_, _| Ok(()))
457 }
458
459 pub(crate) fn decode_all_verified(&self) -> Result<(VerifyReport, Vec<crate::Function>)> {
460 let mut functions = Vec::with_capacity(self.layout.address_count as usize);
461 let report = self.verify_with(|reference, encoded| {
462 functions.push(owned::decode(reference, encoded)?);
463 Ok(())
464 })?;
465 Ok((report, functions))
466 }
467
468 fn verify_with(
469 &self,
470 mut visitor: impl FnMut(&FunctionRef<'_>, EncodedFunction) -> Result<()>,
471 ) -> Result<VerifyReport> {
472 if self
473 .data
474 .as_ref()
475 .get(self.layout.string_table.start)
476 .copied()
477 != Some(0)
478 {
479 return Err(Error::InvalidFormat(
480 "GSYM string table does not begin with an empty string",
481 ));
482 }
483 let mut previous = None;
484 for index in 0..self.layout.address_count as usize {
485 let address = self.address(index)?;
486 if previous.is_some_and(|value| address < value) {
487 return Err(Error::InvalidFormat("address table is not sorted"));
488 }
489 previous = Some(address);
490 let function = self.function(index)?;
491 let decoded = function.decode_encoded()?;
492 owned::validate(self, &decoded)?;
493 visitor(&function, decoded)?;
494 }
495 for index in 0..self.layout.file_count {
496 let _ = self.file(index)?;
497 }
498 Ok(VerifyReport {
499 functions: self.layout.address_count as usize,
500 files: self.layout.file_count as usize,
501 strings: self
502 .data
503 .as_ref()
504 .get(self.layout.string_table.clone())
505 .unwrap_or_default()
506 .iter()
507 .fold(0_usize, |total, byte| {
508 total.saturating_add(usize::from(*byte == 0))
509 }),
510 function_info_bytes: self.layout.function_info.len(),
511 })
512 }
513
514 pub(super) fn address(&self, index: usize) -> Result<u64> {
519 let width = usize::from(self.layout.address_offset_size);
520 let offset = self
521 .layout
522 .address_offsets
523 .start
524 .checked_add(
525 index
526 .checked_mul(width)
527 .ok_or(Error::Overflow("address table index"))?,
528 )
529 .ok_or(Error::Overflow("address table offset"))?;
530 let mut cursor = Cursor::at(self.data.as_ref(), self.layout.endian, offset)?;
531 self.layout
532 .base_address
533 .checked_add(cursor.read_uint(self.layout.address_offset_size)?)
534 .ok_or(Error::Overflow("function address"))
535 }
536
537 fn function_offset(&self, index: usize) -> Result<usize> {
538 let width: u8 = match self.layout.version {
539 VersionLayout::V1 => 4,
540 VersionLayout::V2 => 8,
541 };
542 let offset = self
543 .layout
544 .address_info_offsets
545 .start
546 .checked_add(
547 index
548 .checked_mul(usize::from(width))
549 .ok_or(Error::Overflow("address-info table index"))?,
550 )
551 .ok_or(Error::Overflow("address-info table offset"))?;
552 let mut cursor = Cursor::at(self.data.as_ref(), self.layout.endian, offset)?;
553 let relative = cursor.read_uint(width)?;
554 let absolute = match self.layout.version {
555 VersionLayout::V1 => relative,
556 VersionLayout::V2 => relative
557 .checked_add(self.layout.function_info.start as u64)
558 .ok_or(Error::Overflow("FunctionInfo offset"))?,
559 };
560 usize::try_from(absolute).map_err(|_| Error::Overflow("FunctionInfo offset conversion"))
561 }
562
563 pub(super) fn find_address_index(&self, address: u64) -> Result<Option<usize>> {
564 if address < self.layout.base_address || self.layout.address_count == 0 {
565 return Ok(None);
566 }
567 let count = self.layout.address_count as usize;
568 let relative = address.saturating_sub(self.layout.base_address);
571 let entries = self
572 .data
573 .as_ref()
574 .get(self.layout.address_offsets.clone())
575 .ok_or_else(|| Error::InvalidOffset {
576 offset: self.layout.address_offsets.start as u64,
577 input_len: self.data.as_ref().len(),
578 })?;
579
580 let low = match (self.layout.address_offset_size, self.layout.endian) {
581 (1, _) => partition_point::<1>(entries, relative, |entry| u64::from(entry[0])),
582 (2, Endian::Little) => {
583 typed_partition_point::<U16<LittleEndian>>(entries, relative, |entry| {
584 u64::from(entry.get())
585 })?
586 }
587 (2, Endian::Big) => {
588 typed_partition_point::<U16<BigEndian>>(entries, relative, |entry| {
589 u64::from(entry.get())
590 })?
591 }
592 (4, Endian::Little) => {
593 typed_partition_point::<U32<LittleEndian>>(entries, relative, |entry| {
594 u64::from(entry.get())
595 })?
596 }
597 (4, Endian::Big) => {
598 typed_partition_point::<U32<BigEndian>>(entries, relative, |entry| {
599 u64::from(entry.get())
600 })?
601 }
602 (8, Endian::Little) => {
603 typed_partition_point::<U64<LittleEndian>>(entries, relative, |entry| entry.get())?
604 }
605 (8, Endian::Big) => {
606 typed_partition_point::<U64<BigEndian>>(entries, relative, |entry| entry.get())?
607 }
608 _ => {
609 let mut low = 0usize;
610 let mut high = count;
611 while low < high {
612 let middle = low.saturating_add(high.saturating_sub(low) / 2);
613 if self.address(middle)? <= address {
614 low = middle.saturating_add(1);
615 } else {
616 high = middle;
617 }
618 }
619 low
620 }
621 };
622 Ok(low.checked_sub(1))
623 }
624}
625
626#[inline]
627fn partition_point<const N: usize>(
628 entries: &[u8],
629 probe: u64,
630 decode: impl Fn([u8; N]) -> u64,
631) -> usize {
632 let mut low = 0usize;
633 let mut high = entries.len().checked_div(N).unwrap_or(0);
634 while low < high {
635 let middle = low.saturating_add(high.saturating_sub(low) / 2);
636 let start = middle.saturating_mul(N);
637 let value = entries
640 .get(start..)
641 .and_then(<[u8]>::first_chunk::<N>)
642 .map_or(u64::MAX, |entry| decode(*entry));
643 if value <= probe {
644 low = middle.saturating_add(1);
645 } else {
646 high = middle;
647 }
648 }
649 low
650}
651
652#[inline]
653fn typed_partition_point<T>(entries: &[u8], probe: u64, decode: impl Fn(&T) -> u64) -> Result<usize>
654where
655 [T]: FromBytes + KnownLayout + Immutable,
656{
657 let entries = <[T]>::ref_from_bytes(entries)
658 .map_err(|_| Error::InvalidFormat("address table has an invalid typed layout"))?;
659 Ok(entries.partition_point(|entry| decode(entry) <= probe))
660}
661
662impl<D: AsRef<[u8]>> AsRef<[u8]> for Gsym<D> {
663 fn as_ref(&self) -> &[u8] {
664 self.data.as_ref()
665 }
666}