symbolic_debuginfo/
ppdb.rs

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