symbolic_ppdb/cache/
mod.rs

1//! Provides PortablePdbCache support.
2//!
3//! This includes a reader and writer for the binary format.
4//!
5//! # Structure of a PortablePdbCache
6//!
7//! A PortablePdbCache(version 1) contains the following primary kinds of data, written in the following
8//! order:
9//!
10//! 1. Files
11//! 2. Source Locations
12//! 3. Address Ranges
13//! 4. String Data
14//!
15//! The format uses `u32`s to represent line numbers, referencecs, IL offsets, languages, and string offsets.
16//! Line numbers use `0` to represent an unknown or invalid value. References and string
17//! offsets instead use `u32::MAX`.
18//!
19//! Strings are saved in one contiguous section with each individual string prefixed by
20//! its length in LEB-128 encoding. Source locations refer to strings by an offset into this string section,
21//! hence "string offset".
22//!
23//! ## Files
24//!
25//! A file contains a string offset for its name and a language.
26//!
27//! ## Address Ranges
28//!
29//! Ranges are saved as a contiguous list of pairs of `u32`s, the first representing the index of the function
30//! the range belongs to and the second representing the range's starting IL offset. Ranges are ordered
31//! by function index and then by starting offset.
32//!
33//! ## Source Locations
34//!
35//! A source location in a PortablePDBCache represents a line in a source file.
36//! It contains a line number and a reference to a file.
37//!
38//! ## Mapping From Ranges To Source Locations
39//!
40//! The mapping from ranges to source locations is one-to-one: the `i`th range in the cache corresponds to the `i`th source location.
41//!
42//! # Lookups
43//!
44//! To look up an IL offset `offset` for the `i`th function in a PortablePdbCache:
45//!
46//! 1. Find the range belonging to the `i`th function that covers `offset` via binary search.
47//! 2. Find the source location belonging to this range.
48//! 3. Find the file referenced by the source location.
49
50pub(crate) mod lookup;
51pub(crate) mod raw;
52pub(crate) mod writer;
53
54use symbolic_common::{AsSelf, DebugId};
55use thiserror::Error;
56use watto::{align_to, Pod};
57
58const PPDBCACHE_VERSION: u32 = 1;
59
60/// The kind of a [`CacheError`].
61#[derive(Debug, Clone, Copy, Error)]
62#[non_exhaustive]
63pub enum CacheErrorKind {
64    /// The cache header could not be read.
65    #[error("could not read header")]
66    InvalidHeader,
67    /// The cache file's endianness does not match the system's endianness.
68    #[error("wrong endianness")]
69    WrongEndianness,
70    /// The cache file header does not contain the correct magic bytes.
71    #[error("invalid magic: {0}")]
72    InvalidMagic(u32),
73    /// The cache file header contains an invalid version.
74    #[error("wrong version: {0}")]
75    WrongVersion(u32),
76    /// Range data could not be parsed from the cache file.
77    #[error("could not read ranges")]
78    InvalidRanges,
79    /// Source location data could not be parsed from the cache file.
80    #[error("could not read source locations")]
81    InvalidSourceLocations,
82    /// File data could not be parsed from the cache file.
83    #[error("could not read files")]
84    InvalidFiles,
85    /// The header claimed an incorrect number of string bytes.
86    #[error("expected {expected} string bytes, found {found}")]
87    UnexpectedStringBytes {
88        /// Expected number of string bytes.
89        expected: usize,
90        /// Number of string bytes actually found in the cache file.
91        found: usize,
92    },
93    /// An error resulting from Portable PDB file processing.
94    #[error("error processing portable pdb file")]
95    PortablePdb,
96}
97
98/// An error encountered during [`PortablePdbCache`] creation or parsing.
99#[derive(Debug, Error)]
100#[error("{kind}")]
101pub struct CacheError {
102    pub(crate) kind: CacheErrorKind,
103    #[source]
104    pub(crate) source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
105}
106
107impl CacheError {
108    /// Creates a new CacheError from a known kind of error as well as an
109    /// arbitrary error payload.
110    pub(crate) fn new<E>(kind: CacheErrorKind, source: E) -> Self
111    where
112        E: Into<Box<dyn std::error::Error + Send + Sync>>,
113    {
114        let source = Some(source.into());
115        Self { kind, source }
116    }
117
118    /// Returns the corresponding [`CacheErrorKind`] for this error.
119    pub fn kind(&self) -> CacheErrorKind {
120        self.kind
121    }
122}
123
124impl From<CacheErrorKind> for CacheError {
125    fn from(kind: CacheErrorKind) -> Self {
126        Self { kind, source: None }
127    }
128}
129
130impl From<crate::format::FormatError> for CacheError {
131    fn from(e: crate::format::FormatError) -> Self {
132        Self::new(CacheErrorKind::PortablePdb, e)
133    }
134}
135
136/// The serialized PortablePdbCache binary format.
137///
138/// This can be parsed from a binary buffer via [`PortablePdbCache::parse`] and lookups on it can be performed
139/// via the [`PortablePdbCache::lookup`] method.
140#[derive(Clone, PartialEq, Eq)]
141pub struct PortablePdbCache<'data> {
142    header: &'data raw::Header,
143    files: &'data [raw::File],
144    source_locations: &'data [raw::SourceLocation],
145    ranges: &'data [raw::Range],
146    string_bytes: &'data [u8],
147}
148
149impl<'data> PortablePdbCache<'data> {
150    /// Parses the given buffer into a `PortablePdbCache`.
151    pub fn parse(buf: &'data [u8]) -> Result<Self, CacheError> {
152        let (header, rest) =
153            raw::Header::ref_from_prefix(buf).ok_or(CacheErrorKind::InvalidHeader)?;
154
155        if header.magic == raw::PPDBCACHE_MAGIC_FLIPPED {
156            return Err(CacheErrorKind::WrongEndianness.into());
157        }
158        if header.magic != raw::PPDBCACHE_MAGIC {
159            return Err(CacheErrorKind::InvalidMagic(header.magic).into());
160        }
161
162        if header.version != PPDBCACHE_VERSION {
163            return Err(CacheErrorKind::WrongVersion(header.version).into());
164        }
165
166        let (_, rest) = align_to(rest, 8).ok_or(CacheErrorKind::InvalidFiles)?;
167
168        let (files, rest) = raw::File::slice_from_prefix(rest, header.num_files as usize)
169            .ok_or(CacheErrorKind::InvalidFiles)?;
170
171        let (_, rest) = align_to(rest, 8).ok_or(CacheErrorKind::InvalidSourceLocations)?;
172
173        let (source_locations, rest) =
174            raw::SourceLocation::slice_from_prefix(rest, header.num_ranges as usize)
175                .ok_or(CacheErrorKind::InvalidSourceLocations)?;
176
177        let (_, rest) = align_to(rest, 8).ok_or(CacheErrorKind::InvalidRanges)?;
178
179        let (ranges, rest) = raw::Range::slice_from_prefix(rest, header.num_ranges as usize)
180            .ok_or(CacheErrorKind::InvalidRanges)?;
181
182        let (_, rest) = align_to(rest, 8).ok_or(CacheErrorKind::UnexpectedStringBytes {
183            expected: header.string_bytes as usize,
184            found: 0,
185        })?;
186
187        if rest.len() < header.string_bytes as usize {
188            return Err(CacheErrorKind::UnexpectedStringBytes {
189                expected: header.string_bytes as usize,
190                found: rest.len(),
191            }
192            .into());
193        }
194
195        Ok(Self {
196            header,
197            files,
198            source_locations,
199            ranges,
200            string_bytes: rest,
201        })
202    }
203
204    /// Returns the [`DebugId`] of this portable PDB cache.
205    pub fn debug_id(&self) -> DebugId {
206        self.header.pdb_id
207    }
208}
209
210impl std::fmt::Debug for PortablePdbCache<'_> {
211    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212        f.debug_struct("PortablePdbCache")
213            .field("version", &self.header.version)
214            .field("debug_id", &self.debug_id())
215            .field("files", &self.header.num_files)
216            .field("ranges", &self.header.num_ranges)
217            .field("string_bytes", &self.header.string_bytes)
218            .finish()
219    }
220}
221
222impl<'slf, 'd: 'slf> AsSelf<'slf> for PortablePdbCache<'d> {
223    type Ref = PortablePdbCache<'slf>;
224
225    fn as_self(&'slf self) -> &'slf Self::Ref {
226        self
227    }
228}