Skip to main content

symbolic_debuginfo/
breakpad.rs

1//! Support for Breakpad ASCII symbols, used by the Breakpad and Crashpad libraries.
2
3use std::borrow::Cow;
4use std::collections::BTreeMap;
5use std::error::Error;
6use std::fmt;
7use std::ops::Range;
8use std::str;
9
10use thiserror::Error;
11
12use symbolic_common::{Arch, AsSelf, CodeId, DebugId, Language, Name, NameMangling};
13
14use crate::base::*;
15use crate::function_builder::FunctionBuilder;
16use crate::sourcebundle::SourceFileDescriptor;
17use crate::ParseObjectOptions;
18
19#[derive(Clone, Debug)]
20struct LineOffsets<'data> {
21    data: &'data [u8],
22    finished: bool,
23    index: usize,
24}
25
26impl<'data> LineOffsets<'data> {
27    #[inline]
28    fn new(data: &'data [u8]) -> Self {
29        Self {
30            data,
31            finished: false,
32            index: 0,
33        }
34    }
35}
36
37impl Default for LineOffsets<'_> {
38    #[inline]
39    fn default() -> Self {
40        Self {
41            data: &[],
42            finished: true,
43            index: 0,
44        }
45    }
46}
47
48impl<'data> Iterator for LineOffsets<'data> {
49    type Item = (usize, &'data [u8]);
50
51    #[inline]
52    fn next(&mut self) -> Option<Self::Item> {
53        if self.finished {
54            return None;
55        }
56
57        match self.data.iter().position(|b| *b == b'\n') {
58            None => {
59                if self.finished {
60                    None
61                } else {
62                    self.finished = true;
63                    Some((self.index, self.data))
64                }
65            }
66            Some(index) => {
67                let mut data = &self.data[..index];
68                if index > 0 && data[index - 1] == b'\r' {
69                    data = &data[..index - 1];
70                }
71
72                let item = Some((self.index, data));
73                self.index += index + 1;
74                self.data = &self.data[index + 1..];
75                item
76            }
77        }
78    }
79
80    #[inline]
81    fn size_hint(&self) -> (usize, Option<usize>) {
82        if self.finished {
83            (0, Some(0))
84        } else {
85            (1, Some(self.data.len() + 1))
86        }
87    }
88}
89
90impl std::iter::FusedIterator for LineOffsets<'_> {}
91
92#[allow(missing_docs)]
93#[derive(Clone, Debug, Default)]
94pub struct Lines<'data>(LineOffsets<'data>);
95
96impl<'data> Lines<'data> {
97    #[inline]
98    #[allow(missing_docs)]
99    pub fn new(data: &'data [u8]) -> Self {
100        Self(LineOffsets::new(data))
101    }
102}
103
104impl<'data> Iterator for Lines<'data> {
105    type Item = &'data [u8];
106
107    fn next(&mut self) -> Option<Self::Item> {
108        self.0.next().map(|tup| tup.1)
109    }
110
111    fn size_hint(&self) -> (usize, Option<usize>) {
112        self.0.size_hint()
113    }
114}
115
116impl std::iter::FusedIterator for Lines<'_> {}
117
118/// Length at which the breakpad header will be capped.
119///
120/// This is a protection against reading an entire breakpad file at once if the first characters do
121/// not contain a valid line break.
122const BREAKPAD_HEADER_CAP: usize = 320;
123
124/// Placeholder used for missing function or symbol names.
125const UNKNOWN_NAME: &str = "<unknown>";
126
127/// The error type for [`BreakpadError`].
128#[non_exhaustive]
129#[derive(Copy, Clone, Debug, PartialEq, Eq)]
130pub enum BreakpadErrorKind {
131    /// The symbol header (`MODULE` record) is missing.
132    InvalidMagic,
133
134    /// A part of the file is not encoded in valid UTF-8.
135    BadEncoding,
136
137    /// A record violates the Breakpad symbol syntax.
138    #[deprecated(note = "This is now covered by the Parse variant")]
139    BadSyntax,
140
141    /// Parsing of a record failed.
142    ///
143    /// The field exists only for API compatibility reasons.
144    Parse(&'static str),
145
146    /// The module ID is invalid.
147    InvalidModuleId,
148
149    /// The architecture is invalid.
150    InvalidArchitecture,
151}
152
153impl fmt::Display for BreakpadErrorKind {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            Self::InvalidMagic => write!(f, "missing breakpad symbol header"),
157            Self::BadEncoding => write!(f, "bad utf-8 sequence"),
158            Self::Parse(_) => write!(f, "parsing error"),
159            Self::InvalidModuleId => write!(f, "invalid module id"),
160            Self::InvalidArchitecture => write!(f, "invalid architecture"),
161            _ => Ok(()),
162        }
163    }
164}
165
166/// An error when dealing with [`BreakpadObject`](struct.BreakpadObject.html).
167#[derive(Debug, Error)]
168#[error("{kind}")]
169pub struct BreakpadError {
170    kind: BreakpadErrorKind,
171    #[source]
172    source: Option<Box<dyn Error + Send + Sync + 'static>>,
173}
174
175impl BreakpadError {
176    /// Creates a new Breakpad error from a known kind of error as well as an arbitrary error
177    /// payload.
178    fn new<E>(kind: BreakpadErrorKind, source: E) -> Self
179    where
180        E: Into<Box<dyn Error + Send + Sync>>,
181    {
182        let source = Some(source.into());
183        Self { kind, source }
184    }
185
186    /// Returns the corresponding [`BreakpadErrorKind`] for this error.
187    pub fn kind(&self) -> BreakpadErrorKind {
188        self.kind
189    }
190}
191
192impl From<BreakpadErrorKind> for BreakpadError {
193    fn from(kind: BreakpadErrorKind) -> Self {
194        Self { kind, source: None }
195    }
196}
197
198impl From<str::Utf8Error> for BreakpadError {
199    fn from(e: str::Utf8Error) -> Self {
200        Self::new(BreakpadErrorKind::BadEncoding, e)
201    }
202}
203
204impl From<parsing::ParseBreakpadError> for BreakpadError {
205    fn from(e: parsing::ParseBreakpadError) -> Self {
206        Self::new(BreakpadErrorKind::Parse(""), e)
207    }
208}
209
210// TODO(ja): Test the parser
211
212/// A [module record], constituting the header of a Breakpad file.
213///
214/// Example: `MODULE Linux x86 D3096ED481217FD4C16B29CD9BC208BA0 firefox-bin`
215///
216/// [module record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#module-records
217#[derive(Clone, Debug, Default, Eq, PartialEq)]
218pub struct BreakpadModuleRecord<'d> {
219    /// Name of the operating system.
220    pub os: &'d str,
221    /// Name of the CPU architecture.
222    pub arch: &'d str,
223    /// Breakpad identifier.
224    pub id: &'d str,
225    /// Name of the original file.
226    ///
227    /// This usually corresponds to the debug file (such as a PDB), but might not necessarily have a
228    /// special file extension, such as for MachO dSYMs which share the same name as their code
229    /// file.
230    pub name: &'d str,
231}
232
233impl<'d> BreakpadModuleRecord<'d> {
234    /// Parses a module record from a single line.
235    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
236        let string = str::from_utf8(data)?;
237        Ok(parsing::module_record_final(string.trim())?)
238    }
239}
240
241/// An information record.
242///
243/// This record type is not documented, but appears in Breakpad symbols after the header. It seems
244/// that currently only a `CODE_ID` scope is used, which contains the platform-dependent original
245/// code identifier of an object file.
246#[derive(Clone, Debug, Eq, PartialEq)]
247pub enum BreakpadInfoRecord<'d> {
248    /// Information on the code file.
249    CodeId {
250        /// Identifier of the code file.
251        code_id: &'d str,
252        /// File name of the code file.
253        code_file: &'d str,
254    },
255    /// Any other INFO record.
256    Other {
257        /// The scope of this info record.
258        scope: &'d str,
259        /// The information for this scope.
260        info: &'d str,
261    },
262}
263
264impl<'d> BreakpadInfoRecord<'d> {
265    /// Parses an info record from a single line.
266    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
267        let string = str::from_utf8(data)?;
268        Ok(parsing::info_record_final(string.trim())?)
269    }
270}
271
272/// An iterator over info records in a Breakpad object.
273#[derive(Clone, Debug)]
274pub struct BreakpadInfoRecords<'d> {
275    lines: Lines<'d>,
276    finished: bool,
277}
278
279impl<'d> Iterator for BreakpadInfoRecords<'d> {
280    type Item = Result<BreakpadInfoRecord<'d>, BreakpadError>;
281
282    fn next(&mut self) -> Option<Self::Item> {
283        if self.finished {
284            return None;
285        }
286
287        for line in &mut self.lines {
288            if line.starts_with(b"MODULE ") {
289                continue;
290            }
291
292            // Fast path: INFO records come right after the header.
293            if !line.starts_with(b"INFO ") {
294                break;
295            }
296
297            return Some(BreakpadInfoRecord::parse(line));
298        }
299
300        self.finished = true;
301        None
302    }
303}
304
305/// A [file record], specifying the path to a source code file.
306///
307/// The ID of this record is referenced by [`BreakpadLineRecord`]. File records are not necessarily
308/// consecutive or sorted by their identifier. The Breakpad symbol writer might reuse original
309/// identifiers from the source debug file when dumping symbols.
310///
311/// Example: `FILE 2 /home/jimb/mc/in/browser/app/nsBrowserApp.cpp`
312///
313/// [file record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#file-records
314/// [`LineRecord`]: struct.BreakpadLineRecord.html
315#[derive(Clone, Debug, Default, Eq, PartialEq)]
316pub struct BreakpadFileRecord<'d> {
317    /// Breakpad-internal identifier of the file.
318    pub id: u64,
319    /// The path to the source file, usually relative to the compilation directory.
320    pub name: &'d str,
321}
322
323impl<'d> BreakpadFileRecord<'d> {
324    /// Parses a file record from a single line.
325    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
326        let string = str::from_utf8(data)?;
327        Ok(parsing::file_record_final(string.trim())?)
328    }
329}
330
331/// An iterator over file records in a Breakpad object.
332#[derive(Clone, Debug)]
333pub struct BreakpadFileRecords<'d> {
334    lines: Lines<'d>,
335    finished: bool,
336}
337
338impl<'d> Iterator for BreakpadFileRecords<'d> {
339    type Item = Result<BreakpadFileRecord<'d>, BreakpadError>;
340
341    fn next(&mut self) -> Option<Self::Item> {
342        if self.finished {
343            return None;
344        }
345
346        for line in &mut self.lines {
347            if line.starts_with(b"MODULE ") || line.starts_with(b"INFO ") {
348                continue;
349            }
350
351            // Fast path: FILE records come right after the header.
352            if !line.starts_with(b"FILE ") {
353                break;
354            }
355
356            return Some(BreakpadFileRecord::parse(line));
357        }
358
359        self.finished = true;
360        None
361    }
362}
363
364/// A map of file paths by their file ID.
365pub type BreakpadFileMap<'d> = BTreeMap<u64, &'d str>;
366
367/// An [inline origin record], specifying the function name of a function for which at least one
368/// call to this function has been inlined.
369///
370/// The ID of this record is referenced by [`BreakpadInlineRecord`]. Inline origin records are not
371/// necessarily consecutive or sorted by their identifier, and they don't have to be present in a
372/// contiguous block in the file; they can be interspersed with FUNC records or other records.
373///
374/// Example: `INLINE_ORIGIN 1305 SharedLibraryInfo::Initialize()`
375///
376/// [inline origin record]: https://github.com/google/breakpad/blob/main/docs/symbol_files.md#inline_origin-records
377/// [`BreakpadInlineRecord`]: struct.BreakpadInlineRecord.html
378#[derive(Clone, Debug, Default, Eq, PartialEq)]
379pub struct BreakpadInlineOriginRecord<'d> {
380    /// Breakpad-internal identifier of the function.
381    pub id: u64,
382    /// The function name.
383    pub name: &'d str,
384}
385
386impl<'d> BreakpadInlineOriginRecord<'d> {
387    /// Parses an inline origin record from a single line.
388    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
389        let string = str::from_utf8(data)?;
390        Ok(parsing::inline_origin_record_final(string.trim())?)
391    }
392}
393
394/// A map of function names by their inline origin ID.
395pub type BreakpadInlineOriginMap<'d> = BTreeMap<u64, &'d str>;
396
397/// A [public function symbol record].
398///
399/// Example: `PUBLIC m 2160 0 Public2_1`
400///
401/// [public function symbol record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#public-records
402#[derive(Clone, Debug, Default, Eq, PartialEq)]
403pub struct BreakpadPublicRecord<'d> {
404    /// Whether this symbol was referenced multiple times.
405    pub multiple: bool,
406    /// The address of this symbol relative to the image base (load address).
407    pub address: u64,
408    /// The size of the parameters on the runtime stack.
409    pub parameter_size: u64,
410    /// The demangled function name of the symbol.
411    pub name: &'d str,
412}
413
414impl<'d> BreakpadPublicRecord<'d> {
415    /// Parses a public record from a single line.
416    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
417        let string = str::from_utf8(data)?;
418        Ok(parsing::public_record_final(string.trim())?)
419    }
420}
421
422/// An iterator over public symbol records in a Breakpad object.
423#[derive(Clone, Debug)]
424pub struct BreakpadPublicRecords<'d> {
425    lines: Lines<'d>,
426    finished: bool,
427}
428
429impl<'d> Iterator for BreakpadPublicRecords<'d> {
430    type Item = Result<BreakpadPublicRecord<'d>, BreakpadError>;
431
432    fn next(&mut self) -> Option<Self::Item> {
433        if self.finished {
434            return None;
435        }
436
437        for line in &mut self.lines {
438            // Fast path: PUBLIC records are always before stack records. Once we encounter the
439            // first stack record, we can therefore exit.
440            if line.starts_with(b"STACK ") {
441                break;
442            }
443
444            if !line.starts_with(b"PUBLIC ") {
445                continue;
446            }
447
448            return Some(BreakpadPublicRecord::parse(line));
449        }
450
451        self.finished = true;
452        None
453    }
454}
455
456/// A [function record] including line information.
457///
458/// Example: `FUNC m c184 30 0 nsQueryInterfaceWithError::operator()(nsID const&, void**) const`
459///
460/// [function record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#func-records
461#[derive(Clone, Default)]
462pub struct BreakpadFuncRecord<'d> {
463    /// Whether this function was referenced multiple times.
464    pub multiple: bool,
465    /// The start address of this function relative to the image base (load address).
466    pub address: u64,
467    /// The size of the code covered by this function's line records.
468    pub size: u64,
469    /// The size of the parameters on the runtime stack.
470    pub parameter_size: u64,
471    /// The demangled function name.
472    pub name: &'d str,
473    lines: Lines<'d>,
474}
475
476impl<'d> BreakpadFuncRecord<'d> {
477    /// Parses a function record from a set of lines.
478    ///
479    /// The first line must contain the function record itself. The lines iterator may contain line
480    /// records for this function, which are read until another record isencountered or the file
481    /// ends.
482    pub fn parse(data: &'d [u8], lines: Lines<'d>) -> Result<Self, BreakpadError> {
483        let string = str::from_utf8(data)?;
484        let mut record = parsing::func_record_final(string.trim())?;
485
486        record.lines = lines;
487        Ok(record)
488    }
489
490    /// Returns an iterator over line records associated to this function.
491    pub fn lines(&self) -> BreakpadLineRecords<'d> {
492        BreakpadLineRecords {
493            lines: self.lines.clone(),
494            finished: false,
495        }
496    }
497
498    /// Returns the range of addresses covered by this record.
499    pub fn range(&self) -> Range<u64> {
500        self.address..self.address + self.size
501    }
502}
503
504impl PartialEq for BreakpadFuncRecord<'_> {
505    fn eq(&self, other: &BreakpadFuncRecord<'_>) -> bool {
506        self.multiple == other.multiple
507            && self.address == other.address
508            && self.size == other.size
509            && self.parameter_size == other.parameter_size
510            && self.name == other.name
511    }
512}
513
514impl Eq for BreakpadFuncRecord<'_> {}
515
516impl fmt::Debug for BreakpadFuncRecord<'_> {
517    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518        f.debug_struct("BreakpadFuncRecord")
519            .field("multiple", &self.multiple)
520            .field("address", &self.address)
521            .field("size", &self.size)
522            .field("parameter_size", &self.parameter_size)
523            .field("name", &self.name)
524            .finish()
525    }
526}
527
528/// An iterator over function records in a Breakpad object.
529#[derive(Clone, Debug)]
530pub struct BreakpadFuncRecords<'d> {
531    lines: Lines<'d>,
532    finished: bool,
533}
534
535impl<'d> Iterator for BreakpadFuncRecords<'d> {
536    type Item = Result<BreakpadFuncRecord<'d>, BreakpadError>;
537
538    fn next(&mut self) -> Option<Self::Item> {
539        if self.finished {
540            return None;
541        }
542
543        for line in &mut self.lines {
544            // Fast path: FUNC records are always before stack records. Once we encounter the
545            // first stack record, we can therefore exit.
546            if line.starts_with(b"STACK ") {
547                break;
548            }
549
550            if !line.starts_with(b"FUNC ") {
551                continue;
552            }
553
554            return Some(BreakpadFuncRecord::parse(line, self.lines.clone()));
555        }
556
557        self.finished = true;
558        None
559    }
560}
561
562/// A [line record] associated to a `BreakpadFunctionRecord`.
563///
564/// Line records are so frequent in a Breakpad symbol file that they do not have a record
565/// identifier. They immediately follow the [`BreakpadFuncRecord`] that they belong to. Thus, an
566/// iterator over line records can be obtained from the function record.
567///
568/// Example: `c184 7 59 4`
569///
570/// [line record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#line-records
571/// [`BreakpadFuncRecord`]: struct.BreakpadFuncRecord.html
572#[derive(Clone, Debug, Default, Eq, PartialEq)]
573pub struct BreakpadLineRecord {
574    /// The start address for this line relative to the image base (load address).
575    pub address: u64,
576    /// The size of the code covered by this line record.
577    pub size: u64,
578    /// The line number (zero means no line number).
579    pub line: u64,
580    /// Identifier of the [`BreakpadFileRecord`] specifying the file name.
581    pub file_id: u64,
582}
583
584impl BreakpadLineRecord {
585    /// Parses a line record from a single line.
586    pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
587        let string = str::from_utf8(data)?;
588        Ok(parsing::line_record_final(string.trim())?)
589    }
590
591    /// Resolves the filename for this record in the file map.
592    pub fn filename<'d>(&self, file_map: &BreakpadFileMap<'d>) -> Option<&'d str> {
593        file_map.get(&self.file_id).cloned()
594    }
595
596    /// Returns the range of addresses covered by this record.
597    pub fn range(&self) -> Range<u64> {
598        self.address..self.address + self.size
599    }
600}
601
602/// An iterator over line records in a `BreakpadFunctionRecord`.
603#[derive(Clone, Debug)]
604pub struct BreakpadLineRecords<'d> {
605    lines: Lines<'d>,
606    finished: bool,
607}
608
609impl Iterator for BreakpadLineRecords<'_> {
610    type Item = Result<BreakpadLineRecord, BreakpadError>;
611
612    fn next(&mut self) -> Option<Self::Item> {
613        if self.finished {
614            return None;
615        }
616
617        for line in &mut self.lines {
618            // Stop parsing LINE records once other expected records are encountered.
619            if line.starts_with(b"FUNC ")
620                || line.starts_with(b"PUBLIC ")
621                || line.starts_with(b"STACK ")
622            {
623                break;
624            }
625
626            // There might be empty lines throughout the file (or at the end). This is the only
627            // iterator that cannot rely on a record identifier, so we have to explicitly skip empty
628            // lines.
629            if line.is_empty() {
630                continue;
631            }
632
633            let record = match BreakpadLineRecord::parse(line) {
634                Ok(record) => record,
635                Err(error) => return Some(Err(error)),
636            };
637
638            // Skip line records for empty ranges. These do not carry any information.
639            if record.size > 0 {
640                return Some(Ok(record));
641            }
642        }
643
644        self.finished = true;
645        None
646    }
647}
648
649/// An [inline record] associated with a `BreakpadFunctionRecord`.
650///
651/// Inline records are so frequent in a Breakpad symbol file that they do not have a record
652/// identifier. They immediately follow the [`BreakpadFuncRecord`] that they belong to. Thus, an
653/// iterator over inline records can be obtained from the function record.
654///
655/// Example: `INLINE 1 61 1 2 7b60 3b4`
656///
657/// [inline record]: https://github.com/google/breakpad/blob/main/docs/symbol_files.md#inline-records
658/// [`BreakpadFuncRecord`]: struct.BreakpadFuncRecord.html
659#[derive(Clone, Debug, Default, Eq, PartialEq)]
660pub struct BreakpadInlineRecord {
661    /// The depth of nested inline calls.
662    pub inline_depth: u64,
663    /// The line number of the call, in the parent function. Zero means no line number.
664    pub call_site_line: u64,
665    /// Identifier of the [`BreakpadFileRecord`] specifying the file name of the line of the call.
666    pub call_site_file_id: u64,
667    /// Identifier of the [`BreakpadInlineOriginRecord`] specifying the function name.
668    pub origin_id: u64,
669    /// A list of address ranges which contain the instructions for this inline call. Contains at
670    /// least one element.
671    pub address_ranges: Vec<BreakpadInlineAddressRange>,
672}
673
674impl BreakpadInlineRecord {
675    /// Parses a line record from a single line.
676    pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
677        let string = str::from_utf8(data)?;
678        Ok(parsing::inline_record_final(string.trim())?)
679    }
680}
681
682/// Identifies one contiguous slice of bytes / instruction addresses which is covered by a
683/// [`BreakpadInlineRecord`].
684#[derive(Clone, Debug, Default, Eq, PartialEq)]
685pub struct BreakpadInlineAddressRange {
686    /// The start address for this address range relative to the image base (load address).
687    pub address: u64,
688    /// The length of the range, in bytes.
689    pub size: u64,
690}
691
692impl BreakpadInlineAddressRange {
693    /// Returns the range of addresses covered by this record.
694    pub fn range(&self) -> Range<u64> {
695        self.address..self.address + self.size
696    }
697}
698
699/// A `STACK CFI` record. Usually associated with a [BreakpadStackCfiRecord].
700#[derive(Clone, Debug, Eq, PartialEq, Default)]
701pub struct BreakpadStackCfiDeltaRecord<'d> {
702    /// The address covered by the record.
703    pub address: u64,
704
705    /// The unwind program rules.
706    pub rules: &'d str,
707}
708
709impl<'d> BreakpadStackCfiDeltaRecord<'d> {
710    /// Parses a single `STACK CFI` record.
711    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
712        let string = str::from_utf8(data)?;
713        Ok(parsing::stack_cfi_delta_record_final(string.trim())?)
714    }
715}
716
717/// A [call frame information record](https://github.com/google/breakpad/blob/master/docs/symbol_files.md#stack-cfi-records)
718/// for platforms other than Windows x86.
719///
720/// This bundles together a `STACK CFI INIT` record and its associated `STACK CFI` records.
721#[derive(Clone, Debug, Default)]
722pub struct BreakpadStackCfiRecord<'d> {
723    /// The starting address covered by this record.
724    pub start: u64,
725
726    /// The number of bytes covered by this record.
727    pub size: u64,
728
729    /// The unwind program rules in the `STACK CFI INIT` record.
730    pub init_rules: &'d str,
731
732    /// The `STACK CFI` records belonging to a single `STACK CFI INIT record.
733    deltas: Lines<'d>,
734}
735
736impl<'d> BreakpadStackCfiRecord<'d> {
737    /// Parses a `STACK CFI INIT` record from a single line.
738    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
739        let string = str::from_utf8(data)?;
740        Ok(parsing::stack_cfi_record_final(string.trim())?)
741    }
742
743    /// Returns an iterator over this record's delta records.
744    pub fn deltas(&self) -> BreakpadStackCfiDeltaRecords<'d> {
745        BreakpadStackCfiDeltaRecords {
746            lines: self.deltas.clone(),
747        }
748    }
749
750    /// Returns the range of addresses covered by this record.
751    pub fn range(&self) -> Range<u64> {
752        self.start..self.start + self.size
753    }
754}
755
756impl PartialEq for BreakpadStackCfiRecord<'_> {
757    fn eq(&self, other: &Self) -> bool {
758        self.start == other.start && self.size == other.size && self.init_rules == other.init_rules
759    }
760}
761
762impl Eq for BreakpadStackCfiRecord<'_> {}
763
764/// An iterator over stack cfi delta records associated with a particular
765/// [`BreakpadStackCfiRecord`].
766#[derive(Clone, Debug, Default)]
767pub struct BreakpadStackCfiDeltaRecords<'d> {
768    lines: Lines<'d>,
769}
770
771impl<'d> Iterator for BreakpadStackCfiDeltaRecords<'d> {
772    type Item = Result<BreakpadStackCfiDeltaRecord<'d>, BreakpadError>;
773
774    fn next(&mut self) -> Option<Self::Item> {
775        if let Some(line) = self.lines.next() {
776            if line.starts_with(b"STACK CFI INIT") || !line.starts_with(b"STACK CFI") {
777                self.lines = Lines::default();
778            } else {
779                return Some(BreakpadStackCfiDeltaRecord::parse(line));
780            }
781        }
782
783        None
784    }
785}
786
787/// Possible types of data held by a [`BreakpadStackWinRecord`], as listed in
788/// <http://msdn.microsoft.com/en-us/library/bc5207xw%28VS.100%29.aspx>. Breakpad only deals with
789/// types 0 (`FPO`) and 4 (`FrameData`).
790#[derive(Clone, Copy, Debug, Eq, PartialEq)]
791pub enum BreakpadStackWinRecordType {
792    /// Frame pointer omitted; FPO info available.
793    Fpo = 0,
794
795    /// Kernel Trap frame.
796    Trap = 1,
797
798    /// Kernel Trap frame.
799    Tss = 2,
800
801    /// Standard EBP stack frame.
802    Standard = 3,
803
804    /// Frame pointer omitted; Frame data info available.
805    FrameData = 4,
806
807    /// Frame that does not have any debug info.
808    Unknown = -1,
809}
810
811/// A [Windows stack frame record], used on x86.
812///
813/// Example: `STACK WIN 4 2170 14 1 0 0 0 0 0 1 $eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =`
814///
815/// [Windows stack frame record]: https://github.com/google/breakpad/blob/master/docs/symbol_files.md#stack-win-records
816#[derive(Clone, Debug, Eq, PartialEq)]
817pub struct BreakpadStackWinRecord<'d> {
818    /// The type of frame data this record holds.
819    pub ty: BreakpadStackWinRecordType,
820
821    /// The starting address covered by this record, relative to the module's load address.
822    pub code_start: u32,
823
824    /// The number of bytes covered by this record.
825    pub code_size: u32,
826
827    /// The size of the prologue machine code within the record's range in bytes.
828    pub prolog_size: u16,
829
830    /// The size of the epilogue machine code within the record's range in bytes.
831    pub epilog_size: u16,
832
833    /// The number of bytes this function expects to be passed as arguments.
834    pub params_size: u32,
835
836    /// The number of bytes used by this function to save callee-saves registers.
837    pub saved_regs_size: u16,
838
839    /// The number of bytes used to save this function's local variables.
840    pub locals_size: u32,
841
842    /// The maximum number of bytes pushed on the stack in the frame.
843    pub max_stack_size: u32,
844
845    /// Whether this function uses the base pointer register as a general-purpose register.
846    ///
847    /// This is only relevant for records of type 0 (`FPO`).
848    pub uses_base_pointer: bool,
849
850    /// A string describing a program for recovering the caller's register values.
851    ///
852    /// This is only expected to be present for records of type 4 (`FrameData`).
853    pub program_string: Option<&'d str>,
854}
855
856impl<'d> BreakpadStackWinRecord<'d> {
857    /// Parses a Windows stack record from a single line.
858    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
859        let string = str::from_utf8(data)?;
860        Ok(parsing::stack_win_record_final(string.trim())?)
861    }
862
863    /// Returns the range of addresses covered by this record.
864    pub fn code_range(&self) -> Range<u32> {
865        self.code_start..self.code_start + self.code_size
866    }
867}
868
869/// Stack frame information record used for stack unwinding and stackwalking.
870#[derive(Clone, Debug, Eq, PartialEq)]
871pub enum BreakpadStackRecord<'d> {
872    /// CFI stack record, used for all platforms other than Windows x86.
873    Cfi(BreakpadStackCfiRecord<'d>),
874    /// Windows stack record, used for x86 binaries.
875    Win(BreakpadStackWinRecord<'d>),
876}
877
878impl<'d> BreakpadStackRecord<'d> {
879    /// Parses a stack frame information record from a single line.
880    pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
881        let string = str::from_utf8(data)?;
882        Ok(parsing::stack_record_final(string.trim())?)
883    }
884}
885
886/// An iterator over stack frame records in a Breakpad object.
887#[derive(Clone, Debug)]
888pub struct BreakpadStackRecords<'d> {
889    lines: Lines<'d>,
890    finished: bool,
891}
892
893impl<'d> BreakpadStackRecords<'d> {
894    /// Creates an iterator over [`BreakpadStackRecord`]s contained in a slice of data.
895    pub fn new(data: &'d [u8]) -> Self {
896        Self {
897            lines: Lines::new(data),
898            finished: false,
899        }
900    }
901}
902
903impl<'d> Iterator for BreakpadStackRecords<'d> {
904    type Item = Result<BreakpadStackRecord<'d>, BreakpadError>;
905
906    fn next(&mut self) -> Option<Self::Item> {
907        if self.finished {
908            return None;
909        }
910
911        while let Some(line) = self.lines.next() {
912            if line.starts_with(b"STACK WIN") {
913                return Some(BreakpadStackRecord::parse(line));
914            }
915
916            if line.starts_with(b"STACK CFI INIT") {
917                return Some(BreakpadStackCfiRecord::parse(line).map(|mut r| {
918                    r.deltas = self.lines.clone();
919                    BreakpadStackRecord::Cfi(r)
920                }));
921            }
922        }
923
924        self.finished = true;
925        None
926    }
927}
928
929/// A Breakpad object file.
930///
931/// To process minidump crash reports without having to understand all sorts of native symbol
932/// formats, the Breakpad processor uses a text-based symbol file format. It comprises records
933/// describing the object file, functions and lines, public symbols, as well as unwind information
934/// for stackwalking.
935///
936/// > The platform-specific symbol dumping tools parse the debugging information the compiler
937/// > provides (whether as DWARF or STABS sections in an ELF file or as stand-alone PDB files), and
938/// > write that information back out in the Breakpad symbol file format. This format is much
939/// > simpler and less detailed than compiler debugging information, and values legibility over
940/// > compactness.
941///
942/// The full documentation resides [here](https://chromium.googlesource.com/breakpad/breakpad/+/refs/heads/master/docs/symbol_files.md).
943pub struct BreakpadObject<'data> {
944    id: DebugId,
945    arch: Arch,
946    module: BreakpadModuleRecord<'data>,
947    data: &'data [u8],
948}
949
950impl<'data> BreakpadObject<'data> {
951    /// Tests whether the buffer could contain a Breakpad object.
952    pub fn test(data: &[u8]) -> bool {
953        data.starts_with(b"MODULE ")
954    }
955
956    /// Tries to parse a Breakpad object from the given slice.
957    pub fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
958        // Ensure that we do not read the entire file at once.
959        let header = if data.len() > BREAKPAD_HEADER_CAP {
960            match str::from_utf8(&data[..BREAKPAD_HEADER_CAP]) {
961                Ok(_) => &data[..BREAKPAD_HEADER_CAP],
962                Err(e) => match e.error_len() {
963                    None => &data[..e.valid_up_to()],
964                    Some(_) => return Err(e.into()),
965                },
966            }
967        } else {
968            data
969        };
970
971        let first_line = header.split(|b| *b == b'\n').next().unwrap_or_default();
972        let module = BreakpadModuleRecord::parse(first_line)?;
973
974        Ok(BreakpadObject {
975            id: module
976                .id
977                .parse()
978                .map_err(|_| BreakpadErrorKind::InvalidModuleId)?,
979            arch: module
980                .arch
981                .parse()
982                .map_err(|_| BreakpadErrorKind::InvalidArchitecture)?,
983            module,
984            data,
985        })
986    }
987
988    /// The container file format, which is always `FileFormat::Breakpad`.
989    pub fn file_format(&self) -> FileFormat {
990        FileFormat::Breakpad
991    }
992
993    /// The code identifier of this object.
994    pub fn code_id(&self) -> Option<CodeId> {
995        for result in self.info_records().flatten() {
996            if let BreakpadInfoRecord::CodeId { code_id, .. } = result {
997                if !code_id.is_empty() {
998                    return Some(CodeId::new(code_id.into()));
999                }
1000            }
1001        }
1002
1003        None
1004    }
1005
1006    /// The debug information identifier of this object.
1007    pub fn debug_id(&self) -> DebugId {
1008        self.id
1009    }
1010
1011    /// The CPU architecture of this object.
1012    pub fn arch(&self) -> Arch {
1013        self.arch
1014    }
1015
1016    /// The debug file name of this object.
1017    ///
1018    /// This is the name of the original debug file that was used to create the Breakpad file. On
1019    /// Windows, this will have a `.pdb` extension, on other platforms that name is likely
1020    /// equivalent to the name of the code file (shared library or executable).
1021    pub fn name(&self) -> &'data str {
1022        self.module.name
1023    }
1024
1025    /// The kind of this object.
1026    pub fn kind(&self) -> ObjectKind {
1027        ObjectKind::Debug
1028    }
1029
1030    /// The address at which the image prefers to be loaded into memory.
1031    ///
1032    /// When Breakpad symbols are written, all addresses are rebased relative to the load address.
1033    /// Since the original load address is not stored in the file, it is assumed as zero.
1034    pub fn load_address(&self) -> u64 {
1035        0 // Breakpad rebases all addresses when dumping symbols
1036    }
1037
1038    /// Determines whether this object exposes a public symbol table.
1039    pub fn has_symbols(&self) -> bool {
1040        self.public_records().next().is_some()
1041    }
1042
1043    /// Returns an iterator over symbols in the public symbol table.
1044    pub fn symbols(&self) -> BreakpadSymbolIterator<'data> {
1045        BreakpadSymbolIterator {
1046            records: self.public_records(),
1047        }
1048    }
1049
1050    /// Returns an ordered map of symbols in the symbol table.
1051    pub fn symbol_map(&self) -> SymbolMap<'data> {
1052        self.symbols().collect()
1053    }
1054
1055    /// Determines whether this object contains debug information.
1056    pub fn has_debug_info(&self) -> bool {
1057        self.func_records().next().is_some()
1058    }
1059
1060    /// Constructs a debugging session.
1061    ///
1062    /// A debugging session loads certain information from the object file and creates caches for
1063    /// efficient access to various records in the debug information. Since this can be quite a
1064    /// costly process, try to reuse the debugging session as long as possible.
1065    ///
1066    /// Constructing this session will also work if the object does not contain debugging
1067    /// information, in which case the session will be a no-op. This can be checked via
1068    /// [`has_debug_info`](struct.BreakpadObject.html#method.has_debug_info).
1069    pub fn debug_session(&self) -> Result<BreakpadDebugSession<'data>, BreakpadError> {
1070        Ok(BreakpadDebugSession {
1071            file_map: self.file_map(),
1072            lines: Lines::new(self.data),
1073        })
1074    }
1075
1076    /// Determines whether this object contains stack unwinding information.
1077    pub fn has_unwind_info(&self) -> bool {
1078        self.stack_records().next().is_some()
1079    }
1080
1081    /// Determines whether this object contains embedded source.
1082    pub fn has_sources(&self) -> bool {
1083        false
1084    }
1085
1086    /// Determines whether this object is malformed and was only partially parsed
1087    pub fn is_malformed(&self) -> bool {
1088        false
1089    }
1090
1091    /// Returns an iterator over info records.
1092    pub fn info_records(&self) -> BreakpadInfoRecords<'data> {
1093        BreakpadInfoRecords {
1094            lines: Lines::new(self.data),
1095            finished: false,
1096        }
1097    }
1098
1099    /// Returns an iterator over file records.
1100    pub fn file_records(&self) -> BreakpadFileRecords<'data> {
1101        BreakpadFileRecords {
1102            lines: Lines::new(self.data),
1103            finished: false,
1104        }
1105    }
1106
1107    /// Returns a map for file name lookups by id.
1108    pub fn file_map(&self) -> BreakpadFileMap<'data> {
1109        self.file_records()
1110            .filter_map(Result::ok)
1111            .map(|file| (file.id, file.name))
1112            .collect()
1113    }
1114
1115    /// Returns an iterator over public symbol records.
1116    pub fn public_records(&self) -> BreakpadPublicRecords<'data> {
1117        BreakpadPublicRecords {
1118            lines: Lines::new(self.data),
1119            finished: false,
1120        }
1121    }
1122
1123    /// Returns an iterator over function records.
1124    pub fn func_records(&self) -> BreakpadFuncRecords<'data> {
1125        BreakpadFuncRecords {
1126            lines: Lines::new(self.data),
1127            finished: false,
1128        }
1129    }
1130
1131    /// Returns an iterator over stack frame records.
1132    pub fn stack_records(&self) -> BreakpadStackRecords<'data> {
1133        BreakpadStackRecords {
1134            lines: Lines::new(self.data),
1135            finished: false,
1136        }
1137    }
1138
1139    /// Returns the raw data of the Breakpad file.
1140    pub fn data(&self) -> &'data [u8] {
1141        self.data
1142    }
1143}
1144
1145impl fmt::Debug for BreakpadObject<'_> {
1146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1147        f.debug_struct("BreakpadObject")
1148            .field("code_id", &self.code_id())
1149            .field("debug_id", &self.debug_id())
1150            .field("arch", &self.arch())
1151            .field("name", &self.name())
1152            .field("has_symbols", &self.has_symbols())
1153            .field("has_debug_info", &self.has_debug_info())
1154            .field("has_unwind_info", &self.has_unwind_info())
1155            .field("is_malformed", &self.is_malformed())
1156            .finish()
1157    }
1158}
1159
1160impl<'slf, 'data: 'slf> AsSelf<'slf> for BreakpadObject<'data> {
1161    type Ref = BreakpadObject<'slf>;
1162
1163    fn as_self(&'slf self) -> &'slf Self::Ref {
1164        self
1165    }
1166}
1167
1168impl<'data> Parse<'data> for BreakpadObject<'data> {
1169    type Error = BreakpadError;
1170
1171    fn test(data: &[u8]) -> bool {
1172        Self::test(data)
1173    }
1174
1175    fn parse_with_opts(data: &'data [u8], _opts: ParseObjectOptions) -> Result<Self, Self::Error> {
1176        Self::parse(data)
1177    }
1178}
1179
1180impl<'data: 'object, 'object> ObjectLike<'data, 'object> for BreakpadObject<'data> {
1181    type Error = BreakpadError;
1182    type Session = BreakpadDebugSession<'data>;
1183    type SymbolIterator = BreakpadSymbolIterator<'data>;
1184
1185    fn file_format(&self) -> FileFormat {
1186        self.file_format()
1187    }
1188
1189    fn code_id(&self) -> Option<CodeId> {
1190        self.code_id()
1191    }
1192
1193    fn debug_id(&self) -> DebugId {
1194        self.debug_id()
1195    }
1196
1197    fn arch(&self) -> Arch {
1198        self.arch()
1199    }
1200
1201    fn kind(&self) -> ObjectKind {
1202        self.kind()
1203    }
1204
1205    fn load_address(&self) -> u64 {
1206        self.load_address()
1207    }
1208
1209    fn has_symbols(&self) -> bool {
1210        self.has_symbols()
1211    }
1212
1213    fn symbols(&self) -> Self::SymbolIterator {
1214        self.symbols()
1215    }
1216
1217    fn symbol_map(&self) -> SymbolMap<'data> {
1218        self.symbol_map()
1219    }
1220
1221    fn has_debug_info(&self) -> bool {
1222        self.has_debug_info()
1223    }
1224
1225    fn debug_session(&self) -> Result<Self::Session, Self::Error> {
1226        self.debug_session()
1227    }
1228
1229    fn has_unwind_info(&self) -> bool {
1230        self.has_unwind_info()
1231    }
1232
1233    fn has_sources(&self) -> bool {
1234        self.has_sources()
1235    }
1236
1237    fn is_malformed(&self) -> bool {
1238        self.is_malformed()
1239    }
1240}
1241
1242/// An iterator over symbols in the Breakpad object.
1243///
1244/// Returned by [`BreakpadObject::symbols`](struct.BreakpadObject.html#method.symbols).
1245pub struct BreakpadSymbolIterator<'data> {
1246    records: BreakpadPublicRecords<'data>,
1247}
1248
1249impl<'data> Iterator for BreakpadSymbolIterator<'data> {
1250    type Item = Symbol<'data>;
1251
1252    fn next(&mut self) -> Option<Self::Item> {
1253        self.records.find_map(Result::ok).map(|record| Symbol {
1254            name: Some(Cow::Borrowed(record.name)),
1255            address: record.address,
1256            size: 0,
1257        })
1258    }
1259}
1260
1261/// Debug session for Breakpad objects.
1262pub struct BreakpadDebugSession<'data> {
1263    file_map: BreakpadFileMap<'data>,
1264    lines: Lines<'data>,
1265}
1266
1267impl BreakpadDebugSession<'_> {
1268    /// Returns an iterator over all functions in this debug file.
1269    pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
1270        BreakpadFunctionIterator::new(&self.file_map, self.lines.clone())
1271    }
1272
1273    /// Returns an iterator over all source files in this debug file.
1274    pub fn files(&self) -> BreakpadFileIterator<'_> {
1275        BreakpadFileIterator {
1276            files: self.file_map.values(),
1277        }
1278    }
1279
1280    /// See [DebugSession::source_by_path] for more information.
1281    pub fn source_by_path(
1282        &self,
1283        _path: &str,
1284    ) -> Result<Option<SourceFileDescriptor<'_>>, BreakpadError> {
1285        Ok(None)
1286    }
1287}
1288
1289impl<'session> DebugSession<'session> for BreakpadDebugSession<'_> {
1290    type Error = BreakpadError;
1291    type FunctionIterator = BreakpadFunctionIterator<'session>;
1292    type FileIterator = BreakpadFileIterator<'session>;
1293
1294    fn functions(&'session self) -> Self::FunctionIterator {
1295        self.functions()
1296    }
1297
1298    fn files(&'session self) -> Self::FileIterator {
1299        self.files()
1300    }
1301
1302    fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
1303        self.source_by_path(path)
1304    }
1305}
1306
1307/// An iterator over source files in a Breakpad object.
1308pub struct BreakpadFileIterator<'s> {
1309    files: std::collections::btree_map::Values<'s, u64, &'s str>,
1310}
1311
1312impl<'s> Iterator for BreakpadFileIterator<'s> {
1313    type Item = Result<FileEntry<'s>, BreakpadError>;
1314
1315    fn next(&mut self) -> Option<Self::Item> {
1316        let path = self.files.next()?;
1317        Some(Ok(FileEntry::new(
1318            Cow::default(),
1319            FileInfo::from_path(path.as_bytes()),
1320        )))
1321    }
1322}
1323
1324/// An iterator over functions in a Breakpad object.
1325pub struct BreakpadFunctionIterator<'s> {
1326    file_map: &'s BreakpadFileMap<'s>,
1327    next_line: Option<&'s [u8]>,
1328    inline_origin_map: BreakpadInlineOriginMap<'s>,
1329    lines: Lines<'s>,
1330}
1331
1332impl<'s> BreakpadFunctionIterator<'s> {
1333    fn new(file_map: &'s BreakpadFileMap<'s>, mut lines: Lines<'s>) -> Self {
1334        let next_line = lines.next();
1335        Self {
1336            file_map,
1337            next_line,
1338            inline_origin_map: Default::default(),
1339            lines,
1340        }
1341    }
1342}
1343
1344impl<'s> Iterator for BreakpadFunctionIterator<'s> {
1345    type Item = Result<Function<'s>, BreakpadError>;
1346
1347    fn next(&mut self) -> Option<Self::Item> {
1348        // Advance to the next FUNC line.
1349        let line = loop {
1350            let line = self.next_line.take()?;
1351            if line.starts_with(b"FUNC ") {
1352                break line;
1353            }
1354
1355            // Fast path: FUNC records are always before stack records. Once we encounter the
1356            // first stack record, we can therefore exit.
1357            if line.starts_with(b"STACK ") {
1358                return None;
1359            }
1360
1361            if line.starts_with(b"INLINE_ORIGIN ") {
1362                let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1363                    Ok(record) => record,
1364                    Err(e) => return Some(Err(e)),
1365                };
1366                self.inline_origin_map
1367                    .insert(inline_origin_record.id, inline_origin_record.name);
1368            }
1369
1370            self.next_line = self.lines.next();
1371        };
1372
1373        let fun_record = match BreakpadFuncRecord::parse(line, Lines::new(&[])) {
1374            Ok(record) => record,
1375            Err(e) => return Some(Err(e)),
1376        };
1377
1378        let mut builder = FunctionBuilder::new(
1379            Name::new(fun_record.name, NameMangling::Unmangled, Language::Unknown),
1380            b"",
1381            fun_record.address,
1382            fun_record.size,
1383        );
1384
1385        for line in self.lines.by_ref() {
1386            // Stop parsing LINE records once other expected records are encountered.
1387            if line.starts_with(b"FUNC ")
1388                || line.starts_with(b"PUBLIC ")
1389                || line.starts_with(b"STACK ")
1390            {
1391                self.next_line = Some(line);
1392                break;
1393            }
1394
1395            if line.starts_with(b"INLINE_ORIGIN ") {
1396                let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1397                    Ok(record) => record,
1398                    Err(e) => return Some(Err(e)),
1399                };
1400                self.inline_origin_map
1401                    .insert(inline_origin_record.id, inline_origin_record.name);
1402                continue;
1403            }
1404
1405            if line.starts_with(b"INLINE ") {
1406                let inline_record = match BreakpadInlineRecord::parse(line) {
1407                    Ok(record) => record,
1408                    Err(e) => return Some(Err(e)),
1409                };
1410
1411                let name = self
1412                    .inline_origin_map
1413                    .get(&inline_record.origin_id)
1414                    .cloned()
1415                    .unwrap_or_default();
1416
1417                for address_range in &inline_record.address_ranges {
1418                    builder.add_inlinee(
1419                        inline_record.inline_depth as u32,
1420                        Name::new(name, NameMangling::Unmangled, Language::Unknown),
1421                        address_range.address,
1422                        address_range.size,
1423                        FileInfo::from_path(
1424                            self.file_map
1425                                .get(&inline_record.call_site_file_id)
1426                                .cloned()
1427                                .unwrap_or_default()
1428                                .as_bytes(),
1429                        ),
1430                        inline_record.call_site_line,
1431                    );
1432                }
1433                continue;
1434            }
1435
1436            // There might be empty lines throughout the file (or at the end). This is the only
1437            // iterator that cannot rely on a record identifier, so we have to explicitly skip empty
1438            // lines.
1439            if line.is_empty() {
1440                continue;
1441            }
1442
1443            let line_record = match BreakpadLineRecord::parse(line) {
1444                Ok(line_record) => line_record,
1445                Err(e) => return Some(Err(e)),
1446            };
1447
1448            // Skip line records for empty ranges. These do not carry any information.
1449            if line_record.size == 0 {
1450                continue;
1451            }
1452
1453            let filename = line_record.filename(self.file_map).unwrap_or_default();
1454
1455            builder.add_leaf_line(
1456                line_record.address,
1457                Some(line_record.size),
1458                FileInfo::from_path(filename.as_bytes()),
1459                line_record.line,
1460            );
1461        }
1462
1463        Some(Ok(builder.finish()))
1464    }
1465}
1466
1467impl std::iter::FusedIterator for BreakpadFunctionIterator<'_> {}
1468
1469mod parsing {
1470    use nom::branch::alt;
1471    use nom::bytes::complete::take_while;
1472    use nom::character::complete::{char, hex_digit1, multispace1};
1473    use nom::combinator::{cond, eof, map, rest};
1474    use nom::multi::many1;
1475    use nom::sequence::{pair, tuple};
1476    use nom::{IResult, Parser};
1477    use nom_supreme::error::ErrorTree;
1478    use nom_supreme::final_parser::{Location, RecreateContext};
1479    use nom_supreme::parser_ext::ParserExt;
1480    use nom_supreme::tag::complete::tag;
1481
1482    use super::*;
1483
1484    type ParseResult<'a, T> = IResult<&'a str, T, ErrorTree<&'a str>>;
1485    pub type ParseBreakpadError = ErrorTree<ErrorLine>;
1486
1487    /// A line with a 1-based column position, used for displaying errors.
1488    ///
1489    /// With the default formatter, this prints the line followed by the column number.
1490    /// With the alternate formatter (using `:#`), it prints the line and a caret
1491    /// pointing at the column position.
1492    ///
1493    /// # Example
1494    /// ```ignore
1495    /// use symbolic_debuginfo::breakpad::parsing::ErrorLine;
1496    ///
1497    /// let error_line = ErrorLine {
1498    ///     line: "This line cnotains a typo.".to_string(),
1499    ///     column: 12,
1500    /// };
1501    ///
1502    /// // "This line cnotains a typo.", column 12
1503    /// println!("{}", error_line);
1504    ///
1505    /// // "This line cnotains a typo."
1506    /// //             ^
1507    /// println!("{:#}", error_line);
1508    /// ```
1509    #[derive(Clone, Debug, PartialEq, Eq)]
1510    pub struct ErrorLine {
1511        /// A line of text containing an error.
1512        pub line: String,
1513
1514        /// The position of the error, 1-based.
1515        pub column: usize,
1516    }
1517
1518    impl<'a> RecreateContext<&'a str> for ErrorLine {
1519        fn recreate_context(original_input: &'a str, tail: &'a str) -> Self {
1520            let Location { column, .. } = Location::recreate_context(original_input, tail);
1521            Self {
1522                line: original_input.to_string(),
1523                column,
1524            }
1525        }
1526    }
1527
1528    impl fmt::Display for ErrorLine {
1529        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1530            if f.alternate() {
1531                writeln!(f)?;
1532            }
1533
1534            write!(f, "\"{}\"", self.line)?;
1535
1536            if f.alternate() {
1537                writeln!(f, "\n{:>width$}", "^", width = self.column + 1)?;
1538            } else {
1539                write!(f, ", column {}", self.column)?;
1540            }
1541
1542            Ok(())
1543        }
1544    }
1545
1546    /// Parse a sequence of decimal digits as a number of the given type.
1547    macro_rules! num_dec {
1548        ($ty:ty) => {
1549            nom::character::complete::digit1.map_res(|s: &str| s.parse::<$ty>())
1550        };
1551    }
1552
1553    /// Parse a sequence of hexadecimal digits as a number of the given type.
1554    macro_rules! num_hex {
1555        ($ty:ty) => {
1556            nom::character::complete::hex_digit1.map_res(|n| <$ty>::from_str_radix(n, 16))
1557        };
1558    }
1559
1560    /// Parse a sequence of non-whitespace characters.
1561    fn non_whitespace(input: &str) -> ParseResult<'_, &str> {
1562        take_while(|c: char| !c.is_whitespace())(input)
1563    }
1564
1565    /// Parse to the end of input and return the resulting string.
1566    ///
1567    /// If there is no input, return [`UNKNOWN_NAME`] instead.
1568    fn name(input: &str) -> ParseResult<'_, &str> {
1569        rest.map(|name: &str| if name.is_empty() { UNKNOWN_NAME } else { name })
1570            .parse(input)
1571    }
1572
1573    /// Attempt to parse the character `m` followed by one or more spaces.
1574    ///
1575    /// Returns true if the parse was successful.
1576    fn multiple(input: &str) -> ParseResult<'_, bool> {
1577        let (mut input, multiple) = char('m').opt().parse(input)?;
1578        let multiple = multiple.is_some();
1579        if multiple {
1580            input = multispace1(input)?.0;
1581        }
1582        Ok((input, multiple))
1583    }
1584
1585    /// Parse a line number as a signed decimal number and return `max(0, n)`.
1586    fn line_num(input: &str) -> ParseResult<'_, u64> {
1587        pair(char('-').opt(), num_dec!(u64))
1588            .map(|(sign, num)| if sign.is_some() { 0 } else { num })
1589            .parse(input)
1590    }
1591
1592    /// Parse a [`BreakpadStackWinRecordType`].
1593    fn stack_win_record_type(input: &str) -> ParseResult<'_, BreakpadStackWinRecordType> {
1594        alt((
1595            char('0').value(BreakpadStackWinRecordType::Fpo),
1596            char('1').value(BreakpadStackWinRecordType::Trap),
1597            char('2').value(BreakpadStackWinRecordType::Tss),
1598            char('3').value(BreakpadStackWinRecordType::Standard),
1599            char('4').value(BreakpadStackWinRecordType::FrameData),
1600            non_whitespace.value(BreakpadStackWinRecordType::Unknown),
1601        ))(input)
1602    }
1603
1604    /// Parse a [`BreakpadModuleRecord`].
1605    ///
1606    /// A module record has the form `MODULE <os> <arch> <id>( <name>)?`.
1607    fn module_record(input: &str) -> ParseResult<'_, BreakpadModuleRecord<'_>> {
1608        let (input, _) = tag("MODULE")
1609            .terminated(multispace1)
1610            .context("module record prefix")
1611            .parse(input)?;
1612        let (input, (os, arch, id, name)) = tuple((
1613            non_whitespace.terminated(multispace1).context("os"),
1614            non_whitespace.terminated(multispace1).context("arch"),
1615            hex_digit1
1616                .terminated(multispace1.or(eof))
1617                .context("module id"),
1618            name.context("module name"),
1619        ))
1620        .cut()
1621        .context("module record body")
1622        .parse(input)?;
1623
1624        Ok((input, BreakpadModuleRecord { os, arch, id, name }))
1625    }
1626
1627    /// Parse a [`BreakpadModuleRecord`].
1628    ///
1629    /// A module record has the form `MODULE <os> <arch> <id>( <name>)?`.
1630    /// This will fail if there is any input left over after the record.
1631    pub fn module_record_final(
1632        input: &str,
1633    ) -> Result<BreakpadModuleRecord<'_>, ErrorTree<ErrorLine>> {
1634        nom_supreme::final_parser::final_parser(module_record)(input)
1635    }
1636
1637    /// Parse the `CodeId` variant of a [`BreakpadInfoRecord`].
1638    ///
1639    /// A `CodeId` record has the form `CODE_ID <code_id>( <code_file>)?`.
1640    fn info_code_id_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1641        let (input, _) = tag("CODE_ID")
1642            .terminated(multispace1)
1643            .context("info code_id record prefix")
1644            .parse(input)?;
1645
1646        let (input, (code_id, code_file)) = pair(
1647            hex_digit1
1648                .terminated(multispace1.or(eof))
1649                .context("code id"),
1650            name.context("file name"),
1651        )
1652        .cut()
1653        .context("info code_id record body")
1654        .parse(input)?;
1655
1656        Ok((input, BreakpadInfoRecord::CodeId { code_id, code_file }))
1657    }
1658
1659    /// Parse the `Other` variant of a [`BreakpadInfoRecord`].
1660    ///
1661    /// An `Other` record has the form `<scope>( <info>)?`.
1662    fn info_other_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1663        let (input, (scope, info)) = pair(
1664            non_whitespace
1665                .terminated(multispace1.or(eof))
1666                .context("info scope"),
1667            rest,
1668        )
1669        .cut()
1670        .context("info other record body")
1671        .parse(input)?;
1672
1673        Ok((input, BreakpadInfoRecord::Other { scope, info }))
1674    }
1675
1676    /// Parse a [`BreakpadInfoRecord`].
1677    ///
1678    /// An INFO record has the form `INFO (<code_id_record> | <other_record>)`.
1679    fn info_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1680        let (input, _) = tag("INFO")
1681            .terminated(multispace1)
1682            .context("info record prefix")
1683            .parse(input)?;
1684
1685        info_code_id_record
1686            .or(info_other_record)
1687            .cut()
1688            .context("info record body")
1689            .parse(input)
1690    }
1691
1692    /// Parse a [`BreakpadInfoRecord`].
1693    ///
1694    /// An INFO record has the form `INFO (<code_id_record> | <other_record>)`.
1695    /// This will fail if there is any input left over after the record.
1696    pub fn info_record_final(input: &str) -> Result<BreakpadInfoRecord<'_>, ErrorTree<ErrorLine>> {
1697        nom_supreme::final_parser::final_parser(info_record)(input)
1698    }
1699
1700    /// Parse a [`BreakpadFileRecord`].
1701    ///
1702    /// A FILE record has the form `FILE <id>( <name>)?`.
1703    fn file_record(input: &str) -> ParseResult<'_, BreakpadFileRecord<'_>> {
1704        let (input, _) = tag("FILE")
1705            .terminated(multispace1)
1706            .context("file record prefix")
1707            .parse(input)?;
1708
1709        let (input, (id, name)) = pair(
1710            num_dec!(u64)
1711                .terminated(multispace1.or(eof))
1712                .context("file id"),
1713            rest.context("file name"),
1714        )
1715        .cut()
1716        .context("file record body")
1717        .parse(input)?;
1718
1719        Ok((input, BreakpadFileRecord { id, name }))
1720    }
1721
1722    /// Parse a [`BreakpadFileRecord`].
1723    ///
1724    /// A FILE record has the form `FILE <id>( <name>)?`.
1725    /// This will fail if there is any input left over after the record.
1726    pub fn file_record_final(input: &str) -> Result<BreakpadFileRecord<'_>, ErrorTree<ErrorLine>> {
1727        nom_supreme::final_parser::final_parser(file_record)(input)
1728    }
1729
1730    /// Parse a [`BreakpadInlineOriginRecord`].
1731    ///
1732    /// An INLINE_ORIGIN record has the form `INLINE_ORIGIN <id> <name>`.
1733    fn inline_origin_record(input: &str) -> ParseResult<'_, BreakpadInlineOriginRecord<'_>> {
1734        let (input, _) = tag("INLINE_ORIGIN")
1735            .terminated(multispace1)
1736            .context("inline origin record prefix")
1737            .parse(input)?;
1738
1739        let (input, (id, name)) = pair(
1740            num_dec!(u64)
1741                .terminated(multispace1)
1742                .context("inline origin id"),
1743            rest.context("inline origin name"),
1744        )
1745        .cut()
1746        .context("inline origin record body")
1747        .parse(input)?;
1748
1749        Ok((input, BreakpadInlineOriginRecord { id, name }))
1750    }
1751
1752    /// Parse a [`BreakpadInlineOriginRecord`].
1753    ///
1754    /// An INLINE_ORIGIN record has the form `INLINE_ORIGIN <id> <name>`.
1755    /// This will fail if there is any input left over after the record.
1756    pub fn inline_origin_record_final(
1757        input: &str,
1758    ) -> Result<BreakpadInlineOriginRecord<'_>, ErrorTree<ErrorLine>> {
1759        nom_supreme::final_parser::final_parser(inline_origin_record)(input)
1760    }
1761
1762    /// Parse a [`BreakpadPublicRecord`].
1763    ///
1764    /// A PUBLIC record has the form `PUBLIC (m )? <address> <parameter_size> ( <name>)?`.
1765    fn public_record(input: &str) -> ParseResult<'_, BreakpadPublicRecord<'_>> {
1766        let (input, _) = tag("PUBLIC")
1767            .terminated(multispace1)
1768            .context("public record prefix")
1769            .parse(input)?;
1770
1771        let (input, (multiple, address, parameter_size, name)) = tuple((
1772            multiple.context("multiple flag"),
1773            num_hex!(u64).terminated(multispace1).context("address"),
1774            num_hex!(u64)
1775                .terminated(multispace1.or(eof))
1776                .context("param size"),
1777            name.context("symbol name"),
1778        ))
1779        .cut()
1780        .context("public record body")
1781        .parse(input)?;
1782
1783        Ok((
1784            input,
1785            BreakpadPublicRecord {
1786                multiple,
1787                address,
1788                parameter_size,
1789                name,
1790            },
1791        ))
1792    }
1793
1794    /// Parse a [`BreakpadPublicRecord`].
1795    ///
1796    /// A PUBLIC record has the form `PUBLIC (m )? <address> <parameter_size> ( <name>)?`.
1797    /// This will fail if there is any input left over after the record.
1798    pub fn public_record_final(
1799        input: &str,
1800    ) -> Result<BreakpadPublicRecord<'_>, ErrorTree<ErrorLine>> {
1801        nom_supreme::final_parser::final_parser(public_record)(input)
1802    }
1803
1804    /// Parse a [`BreakpadFuncRecord`].
1805    ///
1806    /// A FUNC record has the form `FUNC (m )? <address> <size> <parameter_size> ( <name>)?`.
1807    fn func_record(input: &str) -> ParseResult<'_, BreakpadFuncRecord<'_>> {
1808        let (input, _) = tag("FUNC")
1809            .terminated(multispace1)
1810            .context("func record prefix")
1811            .parse(input)?;
1812
1813        let (input, (multiple, address, size, parameter_size, name)) = tuple((
1814            multiple.context("multiple flag"),
1815            num_hex!(u64).terminated(multispace1).context("address"),
1816            num_hex!(u64).terminated(multispace1).context("size"),
1817            num_hex!(u64)
1818                .terminated(multispace1.or(eof))
1819                .context("param size"),
1820            name.context("symbol name"),
1821        ))
1822        .cut()
1823        .context("func record body")
1824        .parse(input)?;
1825
1826        Ok((
1827            input,
1828            BreakpadFuncRecord {
1829                multiple,
1830                address,
1831                size,
1832                parameter_size,
1833                name,
1834                lines: Lines::default(),
1835            },
1836        ))
1837    }
1838
1839    /// Parse a [`BreakpadFuncRecord`].
1840    ///
1841    /// A FUNC record has the form `FUNC (m )? <address> <size> <parameter_size> ( <name>)?`.
1842    /// This will fail if there is any input left over after the record.
1843    pub fn func_record_final(input: &str) -> Result<BreakpadFuncRecord<'_>, ErrorTree<ErrorLine>> {
1844        nom_supreme::final_parser::final_parser(func_record)(input)
1845    }
1846
1847    /// Parse a [`BreakpadLineRecord`].
1848    ///
1849    /// A LINE record has the form `<address> <size> <line> <file_id>`.
1850    fn line_record(input: &str) -> ParseResult<'_, BreakpadLineRecord> {
1851        let (input, (address, size, line, file_id)) = tuple((
1852            num_hex!(u64).terminated(multispace1).context("address"),
1853            num_hex!(u64).terminated(multispace1).context("size"),
1854            line_num.terminated(multispace1).context("line number"),
1855            num_dec!(u64).context("file id"),
1856        ))
1857        .context("line record")
1858        .parse(input)?;
1859
1860        Ok((
1861            input,
1862            BreakpadLineRecord {
1863                address,
1864                size,
1865                line,
1866                file_id,
1867            },
1868        ))
1869    }
1870
1871    /// Parse a [`BreakpadLineRecord`].
1872    ///
1873    /// A LINE record has the form `<address> <size> <line> <file_id>`.
1874    /// This will fail if there is any input left over after the record.
1875    pub fn line_record_final(input: &str) -> Result<BreakpadLineRecord, ErrorTree<ErrorLine>> {
1876        nom_supreme::final_parser::final_parser(line_record)(input)
1877    }
1878
1879    /// Parse a [`BreakpadInlineRecord`].
1880    ///
1881    /// An INLINE record has the form `INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id> [<address> <size>]+`.
1882    fn inline_record(input: &str) -> ParseResult<'_, BreakpadInlineRecord> {
1883        let (input, _) = tag("INLINE")
1884            .terminated(multispace1)
1885            .context("inline record prefix")
1886            .parse(input)?;
1887
1888        let (input, (inline_depth, call_site_line, call_site_file_id, origin_id)) = tuple((
1889            num_dec!(u64)
1890                .terminated(multispace1)
1891                .context("inline_nest_level"),
1892            num_dec!(u64)
1893                .terminated(multispace1)
1894                .context("call_site_line"),
1895            num_dec!(u64)
1896                .terminated(multispace1)
1897                .context("call_site_file_id"),
1898            num_dec!(u64).terminated(multispace1).context("origin_id"),
1899        ))
1900        .cut()
1901        .context("func record body")
1902        .parse(input)?;
1903
1904        let (input, address_ranges) = many1(map(
1905            pair(
1906                num_hex!(u64).terminated(multispace1).context("address"),
1907                num_hex!(u64)
1908                    .terminated(multispace1.or(eof))
1909                    .context("size"),
1910            ),
1911            |(address, size)| BreakpadInlineAddressRange { address, size },
1912        ))
1913        .cut()
1914        .context("inline record body")
1915        .parse(input)?;
1916
1917        Ok((
1918            input,
1919            BreakpadInlineRecord {
1920                inline_depth,
1921                call_site_line,
1922                call_site_file_id,
1923                origin_id,
1924                address_ranges,
1925            },
1926        ))
1927    }
1928
1929    /// Parse a [`BreakpadInlineRecord`].
1930    ///
1931    /// An INLINE record has the form `INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id> [<address> <size>]+`.
1932    /// This will fail if there is any input left over after the record.
1933    pub fn inline_record_final(input: &str) -> Result<BreakpadInlineRecord, ErrorTree<ErrorLine>> {
1934        nom_supreme::final_parser::final_parser(inline_record)(input)
1935    }
1936
1937    /// Parse a [`BreakpadStackCfiDeltaRecord`].
1938    ///
1939    /// A STACK CFI Delta record has the form `STACK CFI <address> <rules>`.
1940    fn stack_cfi_delta_record(input: &str) -> ParseResult<'_, BreakpadStackCfiDeltaRecord<'_>> {
1941        let (input, _) = tag("STACK CFI")
1942            .terminated(multispace1)
1943            .context("stack cfi prefix")
1944            .parse(input)?;
1945
1946        let (input, (address, rules)) = pair(
1947            num_hex!(u64).terminated(multispace1).context("address"),
1948            rest.context("rules"),
1949        )
1950        .cut()
1951        .context("stack cfi delta record body")
1952        .parse(input)?;
1953
1954        Ok((input, BreakpadStackCfiDeltaRecord { address, rules }))
1955    }
1956
1957    /// Parse a [`BreakpadStackCfiDeltaRecord`].
1958    ///
1959    /// A STACK CFI Delta record has the form `STACK CFI <address> <rules>`.
1960    /// This will fail if there is any input left over after the record.
1961    pub fn stack_cfi_delta_record_final(
1962        input: &str,
1963    ) -> Result<BreakpadStackCfiDeltaRecord<'_>, ErrorTree<ErrorLine>> {
1964        nom_supreme::final_parser::final_parser(stack_cfi_delta_record)(input)
1965    }
1966
1967    /// Parse a [`BreakpadStackCfiRecord`].
1968    ///
1969    /// A STACK CFI INIT record has the form `STACK CFI INIT <address> <size> <init_rules>`.
1970    fn stack_cfi_record(input: &str) -> ParseResult<'_, BreakpadStackCfiRecord<'_>> {
1971        let (input, _) = tag("STACK CFI INIT")
1972            .terminated(multispace1)
1973            .context("stack cfi init  prefix")
1974            .parse(input)?;
1975
1976        let (input, (start, size, init_rules)) = tuple((
1977            num_hex!(u64).terminated(multispace1).context("start"),
1978            num_hex!(u64).terminated(multispace1).context("size"),
1979            rest.context("rules"),
1980        ))
1981        .cut()
1982        .context("stack cfi record body")
1983        .parse(input)?;
1984
1985        Ok((
1986            input,
1987            BreakpadStackCfiRecord {
1988                start,
1989                size,
1990                init_rules,
1991                deltas: Lines::default(),
1992            },
1993        ))
1994    }
1995
1996    /// Parse a [`BreakpadStackCfiRecord`].
1997    ///
1998    /// A STACK CFI INIT record has the form `STACK CFI INIT <address> <size> <init_rules>`.
1999    /// This will fail if there is any input left over after the record.
2000    pub fn stack_cfi_record_final(
2001        input: &str,
2002    ) -> Result<BreakpadStackCfiRecord<'_>, ErrorTree<ErrorLine>> {
2003        nom_supreme::final_parser::final_parser(stack_cfi_record)(input)
2004    }
2005
2006    /// Parse a [`BreakpadStackWinRecord`].
2007    ///
2008    /// A STACK WIN record has the form
2009    /// `STACK WIN <ty> <code_start> <code_size> <prolog_size> <epilog_size> <params_size> <saved_regs_size> <locals_size> <max_stack_size> <has_program_string> (<program_string> | <uses_base_pointer>)`.
2010    fn stack_win_record(input: &str) -> ParseResult<'_, BreakpadStackWinRecord<'_>> {
2011        let (input, _) = tag("STACK WIN")
2012            .terminated(multispace1)
2013            .context("stack win prefix")
2014            .parse(input)?;
2015
2016        let (
2017            input,
2018            (
2019                ty,
2020                code_start,
2021                code_size,
2022                prolog_size,
2023                epilog_size,
2024                params_size,
2025                saved_regs_size,
2026                locals_size,
2027                max_stack_size,
2028                has_program_string,
2029            ),
2030        ) = tuple((
2031            stack_win_record_type
2032                .terminated(multispace1)
2033                .context("record type"),
2034            num_hex!(u32).terminated(multispace1).context("code start"),
2035            num_hex!(u32).terminated(multispace1).context("code size"),
2036            num_hex!(u16).terminated(multispace1).context("prolog size"),
2037            num_hex!(u16).terminated(multispace1).context("epilog size"),
2038            num_hex!(u32).terminated(multispace1).context("params size"),
2039            num_hex!(u16)
2040                .terminated(multispace1)
2041                .context("saved regs size"),
2042            num_hex!(u32).terminated(multispace1).context("locals size"),
2043            num_hex!(u32)
2044                .terminated(multispace1)
2045                .context("max stack size"),
2046            non_whitespace
2047                .map(|s| s != "0")
2048                .terminated(multispace1)
2049                .context("has_program_string"),
2050        ))
2051        .cut()
2052        .context("stack win record body")
2053        .parse(input)?;
2054
2055        let (input, program_string) =
2056            cond(has_program_string, rest.context("program string"))(input)?;
2057        let (input, uses_base_pointer) =
2058            cond(!has_program_string, non_whitespace.map(|s| s != "0"))
2059                .map(|o| o.unwrap_or(false))
2060                .parse(input)?;
2061
2062        Ok((
2063            input,
2064            BreakpadStackWinRecord {
2065                ty,
2066                code_start,
2067                code_size,
2068                prolog_size,
2069                epilog_size,
2070                params_size,
2071                saved_regs_size,
2072                locals_size,
2073                max_stack_size,
2074                uses_base_pointer,
2075                program_string,
2076            },
2077        ))
2078    }
2079
2080    /// Parse a [`BreakpadStackWinRecord`].
2081    ///
2082    /// A STACK WIN record has the form
2083    /// `STACK WIN <ty> <code_start> <code_size> <prolog_size> <epilog_size> <params_size> <saved_regs_size> <locals_size> <max_stack_size> <has_program_string> (<program_string> | <uses_base_pointer>)`.
2084    /// This will fail if there is any input left over after the record.
2085    pub fn stack_win_record_final(
2086        input: &str,
2087    ) -> Result<BreakpadStackWinRecord<'_>, ErrorTree<ErrorLine>> {
2088        nom_supreme::final_parser::final_parser(stack_win_record)(input)
2089    }
2090
2091    /// Parse a [`BreakpadStackRecord`], containing either a [`BreakpadStackCfiRecord`] or a
2092    /// [`BreakpadStackWinRecord`].
2093    ///
2094    /// This will fail if there is any input left over after the record.
2095    pub fn stack_record_final(input: &str) -> Result<BreakpadStackRecord<'_>, ParseBreakpadError> {
2096        nom_supreme::final_parser::final_parser(alt((
2097            stack_cfi_record.map(BreakpadStackRecord::Cfi),
2098            stack_win_record.map(BreakpadStackRecord::Win),
2099        )))(input)
2100    }
2101}
2102#[cfg(test)]
2103mod tests {
2104    use super::*;
2105
2106    #[test]
2107    fn test_parse_module_record() -> Result<(), BreakpadError> {
2108        let string = b"MODULE Linux x86_64 492E2DD23CC306CA9C494EEF1533A3810 crash";
2109        let record = BreakpadModuleRecord::parse(string)?;
2110
2111        insta::assert_debug_snapshot!(record, @r#"
2112        BreakpadModuleRecord {
2113            os: "Linux",
2114            arch: "x86_64",
2115            id: "492E2DD23CC306CA9C494EEF1533A3810",
2116            name: "crash",
2117        }
2118        "#);
2119
2120        Ok(())
2121    }
2122
2123    #[test]
2124    fn test_parse_module_record_short_id() -> Result<(), BreakpadError> {
2125        // NB: This id is one character short, missing the age. DebugId can handle this, however.
2126        let string = b"MODULE Linux x86_64 6216C672A8D33EC9CF4A1BAB8B29D00E libdispatch.so";
2127        let record = BreakpadModuleRecord::parse(string)?;
2128
2129        insta::assert_debug_snapshot!(record, @r#"
2130        BreakpadModuleRecord {
2131            os: "Linux",
2132            arch: "x86_64",
2133            id: "6216C672A8D33EC9CF4A1BAB8B29D00E",
2134            name: "libdispatch.so",
2135        }
2136        "#);
2137
2138        Ok(())
2139    }
2140
2141    #[test]
2142    fn test_parse_file_record() -> Result<(), BreakpadError> {
2143        let string = b"FILE 37 /usr/include/libkern/i386/_OSByteOrder.h";
2144        let record = BreakpadFileRecord::parse(string)?;
2145
2146        insta::assert_debug_snapshot!(record, @r#"
2147        BreakpadFileRecord {
2148            id: 37,
2149            name: "/usr/include/libkern/i386/_OSByteOrder.h",
2150        }
2151        "#);
2152
2153        Ok(())
2154    }
2155
2156    #[test]
2157    fn test_parse_file_record_space() -> Result<(), BreakpadError> {
2158        let string = b"FILE 38 /usr/local/src/filename with spaces.c";
2159        let record = BreakpadFileRecord::parse(string)?;
2160
2161        insta::assert_debug_snapshot!(record, @r#"
2162        BreakpadFileRecord {
2163            id: 38,
2164            name: "/usr/local/src/filename with spaces.c",
2165        }
2166        "#);
2167
2168        Ok(())
2169    }
2170
2171    #[test]
2172    fn test_parse_inline_origin_record() -> Result<(), BreakpadError> {
2173        let string = b"INLINE_ORIGIN 3529 LZ4F_initStream";
2174        let record = BreakpadInlineOriginRecord::parse(string)?;
2175
2176        insta::assert_debug_snapshot!(record, @r#"
2177        BreakpadInlineOriginRecord {
2178            id: 3529,
2179            name: "LZ4F_initStream",
2180        }
2181        "#);
2182
2183        Ok(())
2184    }
2185
2186    #[test]
2187    fn test_parse_inline_origin_record_space() -> Result<(), BreakpadError> {
2188        let string =
2189            b"INLINE_ORIGIN 3576 unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)";
2190        let record = BreakpadInlineOriginRecord::parse(string)?;
2191
2192        insta::assert_debug_snapshot!(record, @r#"
2193        BreakpadInlineOriginRecord {
2194            id: 3576,
2195            name: "unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)",
2196        }
2197        "#);
2198
2199        Ok(())
2200    }
2201
2202    #[test]
2203    fn test_parse_func_record() -> Result<(), BreakpadError> {
2204        // Lines will be tested separately
2205        let string = b"FUNC 1730 1a 0 <name omitted>";
2206        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2207
2208        insta::assert_debug_snapshot!(record, @r#"
2209        BreakpadFuncRecord {
2210            multiple: false,
2211            address: 5936,
2212            size: 26,
2213            parameter_size: 0,
2214            name: "<name omitted>",
2215        }
2216        "#);
2217
2218        Ok(())
2219    }
2220
2221    #[test]
2222    fn test_parse_func_record_multiple() -> Result<(), BreakpadError> {
2223        let string = b"FUNC m 1730 1a 0 <name omitted>";
2224        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2225
2226        insta::assert_debug_snapshot!(record, @r#"
2227        BreakpadFuncRecord {
2228            multiple: true,
2229            address: 5936,
2230            size: 26,
2231            parameter_size: 0,
2232            name: "<name omitted>",
2233        }
2234        "#);
2235
2236        Ok(())
2237    }
2238
2239    #[test]
2240    fn test_parse_func_record_no_name() -> Result<(), BreakpadError> {
2241        let string = b"FUNC 0 f 0";
2242        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2243
2244        insta::assert_debug_snapshot!(record, @r#"
2245        BreakpadFuncRecord {
2246            multiple: false,
2247            address: 0,
2248            size: 15,
2249            parameter_size: 0,
2250            name: "<unknown>",
2251        }
2252        "#);
2253
2254        Ok(())
2255    }
2256
2257    #[test]
2258    fn test_parse_line_record() -> Result<(), BreakpadError> {
2259        let string = b"1730 6 93 20";
2260        let record = BreakpadLineRecord::parse(string)?;
2261
2262        insta::assert_debug_snapshot!(record, @r"
2263        BreakpadLineRecord {
2264            address: 5936,
2265            size: 6,
2266            line: 93,
2267            file_id: 20,
2268        }
2269        ");
2270
2271        Ok(())
2272    }
2273
2274    #[test]
2275    fn test_parse_line_record_negative_line() -> Result<(), BreakpadError> {
2276        let string = b"e0fd10 5 -376 2225";
2277        let record = BreakpadLineRecord::parse(string)?;
2278
2279        insta::assert_debug_snapshot!(record, @r"
2280        BreakpadLineRecord {
2281            address: 14744848,
2282            size: 5,
2283            line: 0,
2284            file_id: 2225,
2285        }
2286        ");
2287
2288        Ok(())
2289    }
2290
2291    #[test]
2292    fn test_parse_line_record_whitespace() -> Result<(), BreakpadError> {
2293        let string = b"    1000 1c 2972 2
2294";
2295        let record = BreakpadLineRecord::parse(string)?;
2296
2297        insta::assert_debug_snapshot!(
2298            record, @r"
2299        BreakpadLineRecord {
2300            address: 4096,
2301            size: 28,
2302            line: 2972,
2303            file_id: 2,
2304        }
2305        ");
2306
2307        Ok(())
2308    }
2309
2310    #[test]
2311    fn test_parse_public_record() -> Result<(), BreakpadError> {
2312        let string = b"PUBLIC 5180 0 __clang_call_terminate";
2313        let record = BreakpadPublicRecord::parse(string)?;
2314
2315        insta::assert_debug_snapshot!(record, @r#"
2316        BreakpadPublicRecord {
2317            multiple: false,
2318            address: 20864,
2319            parameter_size: 0,
2320            name: "__clang_call_terminate",
2321        }
2322        "#);
2323
2324        Ok(())
2325    }
2326
2327    #[test]
2328    fn test_parse_public_record_multiple() -> Result<(), BreakpadError> {
2329        let string = b"PUBLIC m 5180 0 __clang_call_terminate";
2330        let record = BreakpadPublicRecord::parse(string)?;
2331
2332        insta::assert_debug_snapshot!(record, @r#"
2333        BreakpadPublicRecord {
2334            multiple: true,
2335            address: 20864,
2336            parameter_size: 0,
2337            name: "__clang_call_terminate",
2338        }
2339        "#);
2340
2341        Ok(())
2342    }
2343
2344    #[test]
2345    fn test_parse_public_record_no_name() -> Result<(), BreakpadError> {
2346        let string = b"PUBLIC 5180 0";
2347        let record = BreakpadPublicRecord::parse(string)?;
2348
2349        insta::assert_debug_snapshot!(record, @r#"
2350        BreakpadPublicRecord {
2351            multiple: false,
2352            address: 20864,
2353            parameter_size: 0,
2354            name: "<unknown>",
2355        }
2356        "#);
2357
2358        Ok(())
2359    }
2360
2361    #[test]
2362    fn test_parse_inline_record() -> Result<(), BreakpadError> {
2363        let string = b"INLINE 0 3082 52 1410 49200 10";
2364        let record = BreakpadInlineRecord::parse(string)?;
2365
2366        insta::assert_debug_snapshot!(record, @r"
2367        BreakpadInlineRecord {
2368            inline_depth: 0,
2369            call_site_line: 3082,
2370            call_site_file_id: 52,
2371            origin_id: 1410,
2372            address_ranges: [
2373                BreakpadInlineAddressRange {
2374                    address: 299520,
2375                    size: 16,
2376                },
2377            ],
2378        }
2379        ");
2380
2381        Ok(())
2382    }
2383
2384    #[test]
2385    fn test_parse_inline_record_multiple() -> Result<(), BreakpadError> {
2386        let string = b"INLINE 6 642 8 207 8b110 18 8b154 18";
2387        let record = BreakpadInlineRecord::parse(string)?;
2388
2389        insta::assert_debug_snapshot!(record, @r"
2390        BreakpadInlineRecord {
2391            inline_depth: 6,
2392            call_site_line: 642,
2393            call_site_file_id: 8,
2394            origin_id: 207,
2395            address_ranges: [
2396                BreakpadInlineAddressRange {
2397                    address: 569616,
2398                    size: 24,
2399                },
2400                BreakpadInlineAddressRange {
2401                    address: 569684,
2402                    size: 24,
2403                },
2404            ],
2405        }
2406        ");
2407
2408        Ok(())
2409    }
2410
2411    #[test]
2412    fn test_parse_inline_record_err_missing_address_range() {
2413        let string = b"INLINE 6 642 8 207";
2414        let record = BreakpadInlineRecord::parse(string);
2415        assert!(record.is_err());
2416    }
2417
2418    #[test]
2419    fn test_parse_stack_cfi_init_record() -> Result<(), BreakpadError> {
2420        let string = b"STACK CFI INIT 1880 2d .cfa: $rsp 8 + .ra: .cfa -8 + ^";
2421        let record = BreakpadStackRecord::parse(string)?;
2422
2423        insta::assert_debug_snapshot!(record, @r#"
2424        Cfi(
2425            BreakpadStackCfiRecord {
2426                start: 6272,
2427                size: 45,
2428                init_rules: ".cfa: $rsp 8 + .ra: .cfa -8 + ^",
2429                deltas: Lines(
2430                    LineOffsets {
2431                        data: [],
2432                        finished: true,
2433                        index: 0,
2434                    },
2435                ),
2436            },
2437        )
2438        "#);
2439
2440        Ok(())
2441    }
2442
2443    #[test]
2444    fn test_parse_stack_win_record() -> Result<(), BreakpadError> {
2445        let string =
2446            b"STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =";
2447        let record = BreakpadStackRecord::parse(string)?;
2448
2449        insta::assert_debug_snapshot!(record, @r#"
2450        Win(
2451            BreakpadStackWinRecord {
2452                ty: FrameData,
2453                code_start: 14106,
2454                code_size: 12,
2455                prolog_size: 0,
2456                epilog_size: 0,
2457                params_size: 0,
2458                saved_regs_size: 0,
2459                locals_size: 0,
2460                max_stack_size: 0,
2461                uses_base_pointer: false,
2462                program_string: Some(
2463                    "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2464                ),
2465            },
2466        )
2467        "#);
2468
2469        Ok(())
2470    }
2471
2472    #[test]
2473    fn test_parse_stack_win_record_type_3() -> Result<(), BreakpadError> {
2474        let string = b"STACK WIN 3 8a10b ec b 0 c c 4 0 0 1";
2475        let record = BreakpadStackWinRecord::parse(string)?;
2476
2477        insta::assert_debug_snapshot!(record, @r"
2478        BreakpadStackWinRecord {
2479            ty: Standard,
2480            code_start: 565515,
2481            code_size: 236,
2482            prolog_size: 11,
2483            epilog_size: 0,
2484            params_size: 12,
2485            saved_regs_size: 12,
2486            locals_size: 4,
2487            max_stack_size: 0,
2488            uses_base_pointer: true,
2489            program_string: None,
2490        }
2491        ");
2492
2493        Ok(())
2494    }
2495
2496    #[test]
2497    fn test_parse_stack_win_whitespace() -> Result<(), BreakpadError> {
2498        let string =
2499            b"     STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =
2500                ";
2501        let record = BreakpadStackRecord::parse(string)?;
2502
2503        insta::assert_debug_snapshot!(record, @r#"
2504        Win(
2505            BreakpadStackWinRecord {
2506                ty: FrameData,
2507                code_start: 14106,
2508                code_size: 12,
2509                prolog_size: 0,
2510                epilog_size: 0,
2511                params_size: 0,
2512                saved_regs_size: 0,
2513                locals_size: 0,
2514                max_stack_size: 0,
2515                uses_base_pointer: false,
2516                program_string: Some(
2517                    "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2518                ),
2519            },
2520        )
2521        "#);
2522        Ok(())
2523    }
2524
2525    use similar_asserts::assert_eq;
2526
2527    #[test]
2528    fn test_lineoffsets_fused() {
2529        let data = b"";
2530        let mut offsets = LineOffsets::new(data);
2531
2532        offsets.next();
2533        assert_eq!(None, offsets.next());
2534        assert_eq!(None, offsets.next());
2535        assert_eq!(None, offsets.next());
2536    }
2537
2538    macro_rules! test_lineoffsets {
2539        ($name:ident, $data:literal, $( ($index:literal, $line:literal) ),*) => {
2540            #[test]
2541            fn $name() {
2542                let mut offsets = LineOffsets::new($data);
2543
2544                $(
2545                    assert_eq!(Some(($index, &$line[..])), offsets.next());
2546                )*
2547                assert_eq!(None, offsets.next());
2548            }
2549        };
2550    }
2551
2552    test_lineoffsets!(test_lineoffsets_empty, b"", (0, b""));
2553    test_lineoffsets!(test_lineoffsets_oneline, b"hello", (0, b"hello"));
2554    test_lineoffsets!(
2555        test_lineoffsets_trailing_n,
2556        b"hello\n",
2557        (0, b"hello"),
2558        (6, b"")
2559    );
2560    test_lineoffsets!(
2561        test_lineoffsets_trailing_rn,
2562        b"hello\r\n",
2563        (0, b"hello"),
2564        (7, b"")
2565    );
2566    test_lineoffsets!(
2567        test_lineoffsets_n,
2568        b"hello\nworld\nyo",
2569        (0, b"hello"),
2570        (6, b"world"),
2571        (12, b"yo")
2572    );
2573    test_lineoffsets!(
2574        test_lineoffsets_rn,
2575        b"hello\r\nworld\r\nyo",
2576        (0, b"hello"),
2577        (7, b"world"),
2578        (14, b"yo")
2579    );
2580    test_lineoffsets!(
2581        test_lineoffsets_mixed,
2582        b"hello\r\nworld\nyo",
2583        (0, b"hello"),
2584        (7, b"world"),
2585        (13, b"yo")
2586    );
2587}