Skip to main content

symbolic_debuginfo/
ppdb.rs

1//! Support for Portable PDB Objects.
2use std::borrow::Cow;
3use std::collections::HashMap;
4use std::fmt;
5use std::iter;
6use std::sync::OnceLock;
7
8use symbolic_common::{Arch, CodeId, DebugId};
9use symbolic_ppdb::EmbeddedSource;
10use symbolic_ppdb::{Document, FormatError, PortablePdb};
11
12use crate::base::*;
13use crate::sourcebundle::SourceFileDescriptor;
14use crate::ParseObjectOptions;
15
16/// An iterator over symbols in a [`PortablePdbObject`].
17pub type PortablePdbSymbolIterator<'data> = iter::Empty<Symbol<'data>>;
18/// An iterator over functions in a [`PortablePdbObject`].
19pub type PortablePdbFunctionIterator<'session> =
20    iter::Empty<Result<Function<'session>, FormatError>>;
21
22/// An object wrapping a Portable PDB file.
23pub struct PortablePdbObject<'data> {
24    data: &'data [u8],
25    ppdb: PortablePdb<'data>,
26}
27
28impl<'data> PortablePdbObject<'data> {
29    /// Tries to parse a Portable PDB object from the given slice, with default options.
30    pub fn parse(data: &'data [u8]) -> Result<Self, FormatError> {
31        let ppdb = PortablePdb::parse(data)?;
32        Ok(Self { data, ppdb })
33    }
34
35    /// Returns the Portable PDB contained in this object.
36    pub fn portable_pdb(&self) -> &PortablePdb<'_> {
37        &self.ppdb
38    }
39
40    /// Returns the raw data of the Portable PDB file.
41    pub fn data(&self) -> &'data [u8] {
42        self.data
43    }
44}
45
46impl<'data: 'object, 'object> ObjectLike<'data, 'object> for PortablePdbObject<'data> {
47    type Error = FormatError;
48    type Session = PortablePdbDebugSession<'data>;
49    type SymbolIterator = PortablePdbSymbolIterator<'data>;
50
51    /// The debug information identifier of a Portable PDB file.
52    fn debug_id(&self) -> DebugId {
53        self.ppdb.pdb_id().unwrap_or_default()
54    }
55
56    /// The code identifier of this object.
57    ///
58    /// Portable PDB does not provide code identifiers.
59    fn code_id(&self) -> Option<CodeId> {
60        None
61    }
62
63    /// The CPU architecture of this object.
64    fn arch(&self) -> Arch {
65        Arch::Unknown
66    }
67
68    /// The kind of this object.
69    fn kind(&self) -> ObjectKind {
70        ObjectKind::Debug
71    }
72
73    /// The address at which the image prefers to be loaded into memory.
74    ///
75    /// This is always 0 as this does not really apply to Portable PDB.
76    fn load_address(&self) -> u64 {
77        0
78    }
79
80    /// Returns true if this object exposes a public symbol table.
81    fn has_symbols(&self) -> bool {
82        false
83    }
84
85    /// Returns an iterator over symbols in the public symbol table.
86    fn symbols(&self) -> PortablePdbSymbolIterator<'data> {
87        iter::empty()
88    }
89
90    /// Returns an ordered map of symbols in the symbol table.
91    fn symbol_map(&self) -> SymbolMap<'data> {
92        SymbolMap::new()
93    }
94
95    /// Determines whether this object contains debug information.
96    fn has_debug_info(&self) -> bool {
97        self.ppdb.has_debug_info()
98    }
99
100    /// Constructs a debugging session.
101    fn debug_session(&self) -> Result<PortablePdbDebugSession<'data>, FormatError> {
102        PortablePdbDebugSession::new(&self.ppdb)
103    }
104
105    /// Determines whether this object contains stack unwinding information.
106    fn has_unwind_info(&self) -> bool {
107        false
108    }
109
110    /// Determines whether this object contains embedded or linked sources.
111    fn has_sources(&self) -> bool {
112        self.ppdb.has_source_links().unwrap_or(false)
113            || match self.ppdb.get_embedded_sources() {
114                Ok(mut iter) => iter.any(|v| v.is_ok()),
115                Err(_) => false,
116            }
117    }
118
119    /// Determines whether this object is malformed and was only partially parsed.
120    fn is_malformed(&self) -> bool {
121        false
122    }
123
124    /// The container file format, which currently is always `FileFormat::PortablePdb`.
125    fn file_format(&self) -> FileFormat {
126        FileFormat::PortablePdb
127    }
128}
129
130impl<'data> Parse<'data> for PortablePdbObject<'data> {
131    type Error = FormatError;
132
133    fn test(data: &[u8]) -> bool {
134        PortablePdb::peek(data)
135    }
136
137    fn parse_with_opts(data: &'data [u8], _opts: ParseObjectOptions) -> Result<Self, Self::Error> {
138        Self::parse(data)
139    }
140}
141
142impl fmt::Debug for PortablePdbObject<'_> {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.debug_struct("PortablePdbObject")
145            .field("portable_pdb", &self.portable_pdb())
146            .finish()
147    }
148}
149
150/// A debug session for a Portable PDB object.
151pub struct PortablePdbDebugSession<'data> {
152    ppdb: PortablePdb<'data>,
153    sources: OnceLock<HashMap<String, PPDBSource<'data>>>,
154}
155
156#[derive(Debug, Clone)]
157enum PPDBSource<'data> {
158    Embedded(EmbeddedSource<'data>),
159    Link(Document),
160}
161
162impl<'data> PortablePdbDebugSession<'data> {
163    fn new(ppdb: &'_ PortablePdb<'data>) -> Result<Self, FormatError> {
164        Ok(PortablePdbDebugSession {
165            ppdb: ppdb.clone(),
166            sources: OnceLock::new(),
167        })
168    }
169
170    fn init_sources(&self) -> HashMap<String, PPDBSource<'data>> {
171        let count = self.ppdb.get_documents_count().unwrap_or(0);
172        let mut result = HashMap::with_capacity(count);
173
174        if let Ok(iter) = self.ppdb.get_embedded_sources() {
175            for source in iter.flatten() {
176                result.insert(source.get_path().to_string(), PPDBSource::Embedded(source));
177            }
178        };
179
180        for i in 1..count + 1 {
181            if let Ok(doc) = self.ppdb.get_document(i) {
182                if !result.contains_key(&doc.name) {
183                    result.insert(doc.name.clone(), PPDBSource::Link(doc));
184                }
185            }
186        }
187
188        result
189    }
190
191    /// Returns an iterator over all functions in this debug file.
192    pub fn functions(&self) -> PortablePdbFunctionIterator<'_> {
193        iter::empty()
194    }
195
196    /// Returns an iterator over all source files in this debug file.
197    pub fn files(&self) -> PortablePdbFileIterator<'_> {
198        PortablePdbFileIterator::new(&self.ppdb)
199    }
200
201    /// See [DebugSession::source_by_path] for more information.
202    pub fn source_by_path(
203        &self,
204        path: &str,
205    ) -> Result<Option<SourceFileDescriptor<'_>>, FormatError> {
206        let sources = self.sources.get_or_init(|| self.init_sources());
207        match sources.get(path) {
208            None => Ok(None),
209            Some(PPDBSource::Embedded(source)) => source.get_contents().map(|bytes| {
210                Some(SourceFileDescriptor::new_embedded(
211                    from_utf8_cow_lossy(&bytes),
212                    None,
213                ))
214            }),
215            Some(PPDBSource::Link(document)) => Ok(self
216                .ppdb
217                .get_source_link(document)
218                .map(SourceFileDescriptor::new_remote)),
219        }
220    }
221}
222
223impl<'session> DebugSession<'session> for PortablePdbDebugSession<'_> {
224    type Error = FormatError;
225    type FunctionIterator = PortablePdbFunctionIterator<'session>;
226    type FileIterator = PortablePdbFileIterator<'session>;
227
228    fn functions(&'session self) -> Self::FunctionIterator {
229        self.functions()
230    }
231
232    fn files(&'session self) -> Self::FileIterator {
233        self.files()
234    }
235
236    fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
237        self.source_by_path(path)
238    }
239}
240
241/// An iterator over source files in a Portable PDB file.
242pub struct PortablePdbFileIterator<'s> {
243    ppdb: &'s PortablePdb<'s>,
244    row: usize,
245    size: usize,
246}
247
248impl<'s> PortablePdbFileIterator<'s> {
249    fn new(ppdb: &'s PortablePdb<'s>) -> Self {
250        PortablePdbFileIterator {
251            ppdb,
252            // ppdb.get_document(index) - index is 1-based
253            row: 1,
254            // Zero indicates the value is unknown and must be read during the first next() call.
255            // We do it this way so that we can return a FormatError in case one occurs when determining the size.
256            size: 0,
257        }
258    }
259}
260
261impl<'s> Iterator for PortablePdbFileIterator<'s> {
262    type Item = Result<FileEntry<'s>, FormatError>;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        if self.size == 0 {
266            match self.ppdb.get_documents_count() {
267                Ok(size) => {
268                    debug_assert!(size != usize::MAX);
269                    self.size = size;
270                }
271                Err(e) => {
272                    return Some(Err(e));
273                }
274            }
275        }
276
277        if self.row > self.size {
278            return None;
279        }
280
281        let index = self.row;
282        self.row += 1;
283
284        let document = match self.ppdb.get_document(index) {
285            Ok(doc) => doc,
286            Err(e) => {
287                return Some(Err(e));
288            }
289        };
290        Some(Ok(FileEntry::new(
291            Cow::default(),
292            FileInfo::from_path_owned(document.name.as_bytes()),
293        )))
294    }
295}