symbolic_debuginfo/
ppdb.rs1use 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
16pub type PortablePdbSymbolIterator<'data> = iter::Empty<Symbol<'data>>;
18pub type PortablePdbFunctionIterator<'session> =
20 iter::Empty<Result<Function<'session>, FormatError>>;
21
22pub struct PortablePdbObject<'data> {
24 data: &'data [u8],
25 ppdb: PortablePdb<'data>,
26 max_decompressed_embedded_source_size: Option<usize>,
27}
28
29impl<'data> PortablePdbObject<'data> {
30 pub fn parse(data: &'data [u8]) -> Result<Self, FormatError> {
32 Self::parse_with_opts(data, Default::default())
33 }
34
35 pub fn parse_with_opts(
37 data: &'data [u8],
38 opts: ParseObjectOptions,
39 ) -> Result<Self, FormatError> {
40 let ppdb = PortablePdb::parse(data)?;
41 Ok(Self {
42 data,
43 ppdb,
44 max_decompressed_embedded_source_size: opts.max_decompressed_embedded_source_size,
45 })
46 }
47
48 pub fn portable_pdb(&self) -> &PortablePdb<'_> {
50 &self.ppdb
51 }
52
53 pub fn data(&self) -> &'data [u8] {
55 self.data
56 }
57}
58
59impl<'data: 'object, 'object> ObjectLike<'data, 'object> for PortablePdbObject<'data> {
60 type Error = FormatError;
61 type Session = PortablePdbDebugSession<'data>;
62 type SymbolIterator = PortablePdbSymbolIterator<'data>;
63
64 fn debug_id(&self) -> DebugId {
66 self.ppdb.pdb_id().unwrap_or_default()
67 }
68
69 fn code_id(&self) -> Option<CodeId> {
73 None
74 }
75
76 fn arch(&self) -> Arch {
78 Arch::Unknown
79 }
80
81 fn kind(&self) -> ObjectKind {
83 ObjectKind::Debug
84 }
85
86 fn load_address(&self) -> u64 {
90 0
91 }
92
93 fn has_symbols(&self) -> bool {
95 false
96 }
97
98 fn symbols(&self) -> PortablePdbSymbolIterator<'data> {
100 iter::empty()
101 }
102
103 fn symbol_map(&self) -> SymbolMap<'data> {
105 SymbolMap::new()
106 }
107
108 fn has_debug_info(&self) -> bool {
110 self.ppdb.has_debug_info()
111 }
112
113 fn debug_session(&self) -> Result<PortablePdbDebugSession<'data>, FormatError> {
115 PortablePdbDebugSession::new(&self.ppdb, self.max_decompressed_embedded_source_size)
116 }
117
118 fn has_unwind_info(&self) -> bool {
120 false
121 }
122
123 fn has_sources(&self) -> bool {
125 self.ppdb.has_source_links().unwrap_or(false)
126 || match self.ppdb.get_embedded_sources() {
127 Ok(mut iter) => iter.any(|v| v.is_ok()),
128 Err(_) => false,
129 }
130 }
131
132 fn is_malformed(&self) -> bool {
134 false
135 }
136
137 fn file_format(&self) -> FileFormat {
139 FileFormat::PortablePdb
140 }
141}
142
143impl<'data> Parse<'data> for PortablePdbObject<'data> {
144 type Error = FormatError;
145
146 fn test(data: &[u8]) -> bool {
147 PortablePdb::peek(data)
148 }
149
150 fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result<Self, Self::Error> {
151 Self::parse_with_opts(data, opts)
152 }
153}
154
155impl fmt::Debug for PortablePdbObject<'_> {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.debug_struct("PortablePdbObject")
158 .field("portable_pdb", &self.portable_pdb())
159 .finish()
160 }
161}
162
163pub struct PortablePdbDebugSession<'data> {
165 ppdb: PortablePdb<'data>,
166 sources: OnceLock<HashMap<String, PPDBSource<'data>>>,
167 max_decompressed_embedded_source_size: Option<usize>,
168}
169
170#[derive(Debug, Clone)]
171enum PPDBSource<'data> {
172 Embedded(EmbeddedSource<'data>),
173 Link(Document),
174}
175
176impl<'data> PortablePdbDebugSession<'data> {
177 fn new(
178 ppdb: &'_ PortablePdb<'data>,
179 max_decompressed_embedded_source_size: Option<usize>,
180 ) -> Result<Self, FormatError> {
181 Ok(PortablePdbDebugSession {
182 ppdb: ppdb.clone(),
183 sources: OnceLock::new(),
184 max_decompressed_embedded_source_size,
185 })
186 }
187
188 fn init_sources(&self) -> HashMap<String, PPDBSource<'data>> {
189 let count = self.ppdb.get_documents_count().unwrap_or(0);
190 let mut result = HashMap::with_capacity(count);
191
192 if let Ok(iter) = self.ppdb.get_embedded_sources() {
193 for source in iter.flatten() {
194 result.insert(source.get_path().to_string(), PPDBSource::Embedded(source));
195 }
196 };
197
198 for i in 1..count + 1 {
199 if let Ok(doc) = self.ppdb.get_document(i) {
200 if !result.contains_key(&doc.name) {
201 result.insert(doc.name.clone(), PPDBSource::Link(doc));
202 }
203 }
204 }
205
206 result
207 }
208
209 pub fn functions(&self) -> PortablePdbFunctionIterator<'_> {
211 iter::empty()
212 }
213
214 pub fn files(&self) -> PortablePdbFileIterator<'_> {
216 PortablePdbFileIterator::new(&self.ppdb)
217 }
218
219 pub fn source_by_path(
221 &self,
222 path: &str,
223 ) -> Result<Option<SourceFileDescriptor<'_>>, FormatError> {
224 let sources = self.sources.get_or_init(|| self.init_sources());
225 match sources.get(path) {
226 None => Ok(None),
227 Some(PPDBSource::Embedded(source)) => source
228 .get_contents_bounded(self.max_decompressed_embedded_source_size)
229 .map(|bytes| {
230 Some(SourceFileDescriptor::new_embedded(
231 from_utf8_cow_lossy(&bytes),
232 None,
233 ))
234 }),
235 Some(PPDBSource::Link(document)) => Ok(self
236 .ppdb
237 .get_source_link(document)
238 .map(SourceFileDescriptor::new_remote)),
239 }
240 }
241}
242
243impl<'session> DebugSession<'session> for PortablePdbDebugSession<'_> {
244 type Error = FormatError;
245 type FunctionIterator = PortablePdbFunctionIterator<'session>;
246 type FileIterator = PortablePdbFileIterator<'session>;
247
248 fn functions(&'session self) -> Self::FunctionIterator {
249 self.functions()
250 }
251
252 fn files(&'session self) -> Self::FileIterator {
253 self.files()
254 }
255
256 fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
257 self.source_by_path(path)
258 }
259}
260
261pub struct PortablePdbFileIterator<'s> {
263 ppdb: &'s PortablePdb<'s>,
264 row: usize,
265 size: usize,
266}
267
268impl<'s> PortablePdbFileIterator<'s> {
269 fn new(ppdb: &'s PortablePdb<'s>) -> Self {
270 PortablePdbFileIterator {
271 ppdb,
272 row: 1,
274 size: 0,
277 }
278 }
279}
280
281impl<'s> Iterator for PortablePdbFileIterator<'s> {
282 type Item = Result<FileEntry<'s>, FormatError>;
283
284 fn next(&mut self) -> Option<Self::Item> {
285 if self.size == 0 {
286 match self.ppdb.get_documents_count() {
287 Ok(size) => {
288 debug_assert!(size != usize::MAX);
289 self.size = size;
290 }
291 Err(e) => {
292 return Some(Err(e));
293 }
294 }
295 }
296
297 if self.row > self.size {
298 return None;
299 }
300
301 let index = self.row;
302 self.row += 1;
303
304 let document = match self.ppdb.get_document(index) {
305 Ok(doc) => doc,
306 Err(e) => {
307 return Some(Err(e));
308 }
309 };
310 Some(Ok(FileEntry::new(
311 Cow::default(),
312 FileInfo::from_path_owned(document.name.as_bytes()),
313 )))
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use symbolic_common::ByteView;
320 use symbolic_ppdb::FormatErrorKind as PpdbErrorKind;
321 use symbolic_testutils::fixture;
322
323 use crate::ppdb::PortablePdbObject;
324 use crate::{ObjectLike, ParseObjectOptions};
325
326 #[test]
327 fn test_ppdb_source_by_path_size_limit() {
328 let opts = ParseObjectOptions {
329 max_decompressed_embedded_source_size: Some(200),
330 ..Default::default()
331 };
332
333 let view = ByteView::open(fixture("windows/Sentry.Samples.Console.Basic.pdb")).unwrap();
334 let object = PortablePdbObject::parse_with_opts(&view, opts).unwrap();
335
336 let session = object.debug_session().unwrap();
337 let err = session
338 .source_by_path(
339 "C:\\dev\\sentry-dotnet\\samples\\Sentry.Samples.Console.Basic\\Program.cs",
340 )
341 .unwrap_err();
342
343 assert!(matches!(
344 err.kind(),
345 PpdbErrorKind::EmbeddedSourceFileSizeExceeded(204)
346 ));
347 }
348}