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    max_inline_depth: Option<u32>,
949}
950
951impl<'data> BreakpadObject<'data> {
952    /// Tests whether the buffer could contain a Breakpad object.
953    pub fn test(data: &[u8]) -> bool {
954        data.starts_with(b"MODULE ")
955    }
956
957    /// Tries to parse a Breakpad object from the given slice, with default options.
958    pub fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
959        Self::parse_with_opts(data, Default::default())
960    }
961
962    /// Tries to parse a Breakpad object from the given slice.
963    fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result<Self, BreakpadError> {
964        // Ensure that we do not read the entire file at once.
965        let header = if data.len() > BREAKPAD_HEADER_CAP {
966            match str::from_utf8(&data[..BREAKPAD_HEADER_CAP]) {
967                Ok(_) => &data[..BREAKPAD_HEADER_CAP],
968                Err(e) => match e.error_len() {
969                    None => &data[..e.valid_up_to()],
970                    Some(_) => return Err(e.into()),
971                },
972            }
973        } else {
974            data
975        };
976
977        let first_line = header.split(|b| *b == b'\n').next().unwrap_or_default();
978        let module = BreakpadModuleRecord::parse(first_line)?;
979
980        Ok(BreakpadObject {
981            id: module
982                .id
983                .parse()
984                .map_err(|_| BreakpadErrorKind::InvalidModuleId)?,
985            arch: module
986                .arch
987                .parse()
988                .map_err(|_| BreakpadErrorKind::InvalidArchitecture)?,
989            module,
990            data,
991            max_inline_depth: opts.max_inline_depth,
992        })
993    }
994
995    /// The container file format, which is always `FileFormat::Breakpad`.
996    pub fn file_format(&self) -> FileFormat {
997        FileFormat::Breakpad
998    }
999
1000    /// The code identifier of this object.
1001    pub fn code_id(&self) -> Option<CodeId> {
1002        for result in self.info_records().flatten() {
1003            if let BreakpadInfoRecord::CodeId { code_id, .. } = result {
1004                if !code_id.is_empty() {
1005                    return Some(CodeId::new(code_id.into()));
1006                }
1007            }
1008        }
1009
1010        None
1011    }
1012
1013    /// The debug information identifier of this object.
1014    pub fn debug_id(&self) -> DebugId {
1015        self.id
1016    }
1017
1018    /// The CPU architecture of this object.
1019    pub fn arch(&self) -> Arch {
1020        self.arch
1021    }
1022
1023    /// The debug file name of this object.
1024    ///
1025    /// This is the name of the original debug file that was used to create the Breakpad file. On
1026    /// Windows, this will have a `.pdb` extension, on other platforms that name is likely
1027    /// equivalent to the name of the code file (shared library or executable).
1028    pub fn name(&self) -> &'data str {
1029        self.module.name
1030    }
1031
1032    /// The kind of this object.
1033    pub fn kind(&self) -> ObjectKind {
1034        ObjectKind::Debug
1035    }
1036
1037    /// The address at which the image prefers to be loaded into memory.
1038    ///
1039    /// When Breakpad symbols are written, all addresses are rebased relative to the load address.
1040    /// Since the original load address is not stored in the file, it is assumed as zero.
1041    pub fn load_address(&self) -> u64 {
1042        0 // Breakpad rebases all addresses when dumping symbols
1043    }
1044
1045    /// Determines whether this object exposes a public symbol table.
1046    pub fn has_symbols(&self) -> bool {
1047        self.public_records().next().is_some()
1048    }
1049
1050    /// Returns an iterator over symbols in the public symbol table.
1051    pub fn symbols(&self) -> BreakpadSymbolIterator<'data> {
1052        BreakpadSymbolIterator {
1053            records: self.public_records(),
1054        }
1055    }
1056
1057    /// Returns an ordered map of symbols in the symbol table.
1058    pub fn symbol_map(&self) -> SymbolMap<'data> {
1059        self.symbols().collect()
1060    }
1061
1062    /// Determines whether this object contains debug information.
1063    pub fn has_debug_info(&self) -> bool {
1064        self.func_records().next().is_some()
1065    }
1066
1067    /// Constructs a debugging session.
1068    ///
1069    /// A debugging session loads certain information from the object file and creates caches for
1070    /// efficient access to various records in the debug information. Since this can be quite a
1071    /// costly process, try to reuse the debugging session as long as possible.
1072    ///
1073    /// Constructing this session will also work if the object does not contain debugging
1074    /// information, in which case the session will be a no-op. This can be checked via
1075    /// [`has_debug_info`](struct.BreakpadObject.html#method.has_debug_info).
1076    pub fn debug_session(&self) -> Result<BreakpadDebugSession<'data>, BreakpadError> {
1077        Ok(BreakpadDebugSession {
1078            file_map: self.file_map(),
1079            lines: Lines::new(self.data),
1080            max_inline_depth: self.max_inline_depth,
1081        })
1082    }
1083
1084    /// Determines whether this object contains stack unwinding information.
1085    pub fn has_unwind_info(&self) -> bool {
1086        self.stack_records().next().is_some()
1087    }
1088
1089    /// Determines whether this object contains embedded source.
1090    pub fn has_sources(&self) -> bool {
1091        false
1092    }
1093
1094    /// Determines whether this object is malformed and was only partially parsed
1095    pub fn is_malformed(&self) -> bool {
1096        false
1097    }
1098
1099    /// Returns an iterator over info records.
1100    pub fn info_records(&self) -> BreakpadInfoRecords<'data> {
1101        BreakpadInfoRecords {
1102            lines: Lines::new(self.data),
1103            finished: false,
1104        }
1105    }
1106
1107    /// Returns an iterator over file records.
1108    pub fn file_records(&self) -> BreakpadFileRecords<'data> {
1109        BreakpadFileRecords {
1110            lines: Lines::new(self.data),
1111            finished: false,
1112        }
1113    }
1114
1115    /// Returns a map for file name lookups by id.
1116    pub fn file_map(&self) -> BreakpadFileMap<'data> {
1117        self.file_records()
1118            .filter_map(Result::ok)
1119            .map(|file| (file.id, file.name))
1120            .collect()
1121    }
1122
1123    /// Returns an iterator over public symbol records.
1124    pub fn public_records(&self) -> BreakpadPublicRecords<'data> {
1125        BreakpadPublicRecords {
1126            lines: Lines::new(self.data),
1127            finished: false,
1128        }
1129    }
1130
1131    /// Returns an iterator over function records.
1132    pub fn func_records(&self) -> BreakpadFuncRecords<'data> {
1133        BreakpadFuncRecords {
1134            lines: Lines::new(self.data),
1135            finished: false,
1136        }
1137    }
1138
1139    /// Returns an iterator over stack frame records.
1140    pub fn stack_records(&self) -> BreakpadStackRecords<'data> {
1141        BreakpadStackRecords {
1142            lines: Lines::new(self.data),
1143            finished: false,
1144        }
1145    }
1146
1147    /// Returns the raw data of the Breakpad file.
1148    pub fn data(&self) -> &'data [u8] {
1149        self.data
1150    }
1151}
1152
1153impl fmt::Debug for BreakpadObject<'_> {
1154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1155        f.debug_struct("BreakpadObject")
1156            .field("code_id", &self.code_id())
1157            .field("debug_id", &self.debug_id())
1158            .field("arch", &self.arch())
1159            .field("name", &self.name())
1160            .field("has_symbols", &self.has_symbols())
1161            .field("has_debug_info", &self.has_debug_info())
1162            .field("has_unwind_info", &self.has_unwind_info())
1163            .field("is_malformed", &self.is_malformed())
1164            .finish()
1165    }
1166}
1167
1168impl<'slf, 'data: 'slf> AsSelf<'slf> for BreakpadObject<'data> {
1169    type Ref = BreakpadObject<'slf>;
1170
1171    fn as_self(&'slf self) -> &'slf Self::Ref {
1172        self
1173    }
1174}
1175
1176impl<'data> Parse<'data> for BreakpadObject<'data> {
1177    type Error = BreakpadError;
1178
1179    fn test(data: &[u8]) -> bool {
1180        Self::test(data)
1181    }
1182
1183    fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result<Self, Self::Error> {
1184        Self::parse_with_opts(data, opts)
1185    }
1186}
1187
1188impl<'data: 'object, 'object> ObjectLike<'data, 'object> for BreakpadObject<'data> {
1189    type Error = BreakpadError;
1190    type Session = BreakpadDebugSession<'data>;
1191    type SymbolIterator = BreakpadSymbolIterator<'data>;
1192
1193    fn file_format(&self) -> FileFormat {
1194        self.file_format()
1195    }
1196
1197    fn code_id(&self) -> Option<CodeId> {
1198        self.code_id()
1199    }
1200
1201    fn debug_id(&self) -> DebugId {
1202        self.debug_id()
1203    }
1204
1205    fn arch(&self) -> Arch {
1206        self.arch()
1207    }
1208
1209    fn kind(&self) -> ObjectKind {
1210        self.kind()
1211    }
1212
1213    fn load_address(&self) -> u64 {
1214        self.load_address()
1215    }
1216
1217    fn has_symbols(&self) -> bool {
1218        self.has_symbols()
1219    }
1220
1221    fn symbols(&self) -> Self::SymbolIterator {
1222        self.symbols()
1223    }
1224
1225    fn symbol_map(&self) -> SymbolMap<'data> {
1226        self.symbol_map()
1227    }
1228
1229    fn has_debug_info(&self) -> bool {
1230        self.has_debug_info()
1231    }
1232
1233    fn debug_session(&self) -> Result<Self::Session, Self::Error> {
1234        self.debug_session()
1235    }
1236
1237    fn has_unwind_info(&self) -> bool {
1238        self.has_unwind_info()
1239    }
1240
1241    fn has_sources(&self) -> bool {
1242        self.has_sources()
1243    }
1244
1245    fn is_malformed(&self) -> bool {
1246        self.is_malformed()
1247    }
1248}
1249
1250/// An iterator over symbols in the Breakpad object.
1251///
1252/// Returned by [`BreakpadObject::symbols`](struct.BreakpadObject.html#method.symbols).
1253pub struct BreakpadSymbolIterator<'data> {
1254    records: BreakpadPublicRecords<'data>,
1255}
1256
1257impl<'data> Iterator for BreakpadSymbolIterator<'data> {
1258    type Item = Symbol<'data>;
1259
1260    fn next(&mut self) -> Option<Self::Item> {
1261        self.records.find_map(Result::ok).map(|record| Symbol {
1262            name: Some(Cow::Borrowed(record.name)),
1263            address: record.address,
1264            size: 0,
1265        })
1266    }
1267}
1268
1269/// Debug session for Breakpad objects.
1270pub struct BreakpadDebugSession<'data> {
1271    file_map: BreakpadFileMap<'data>,
1272    lines: Lines<'data>,
1273    max_inline_depth: Option<u32>,
1274}
1275
1276impl BreakpadDebugSession<'_> {
1277    /// Returns an iterator over all functions in this debug file.
1278    pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
1279        BreakpadFunctionIterator::new(&self.file_map, self.lines.clone(), self.max_inline_depth)
1280    }
1281
1282    /// Returns an iterator over all source files in this debug file.
1283    pub fn files(&self) -> BreakpadFileIterator<'_> {
1284        BreakpadFileIterator {
1285            files: self.file_map.values(),
1286        }
1287    }
1288
1289    /// See [DebugSession::source_by_path] for more information.
1290    pub fn source_by_path(
1291        &self,
1292        _path: &str,
1293    ) -> Result<Option<SourceFileDescriptor<'_>>, BreakpadError> {
1294        Ok(None)
1295    }
1296}
1297
1298impl<'session> DebugSession<'session> for BreakpadDebugSession<'_> {
1299    type Error = BreakpadError;
1300    type FunctionIterator = BreakpadFunctionIterator<'session>;
1301    type FileIterator = BreakpadFileIterator<'session>;
1302
1303    fn functions(&'session self) -> Self::FunctionIterator {
1304        self.functions()
1305    }
1306
1307    fn files(&'session self) -> Self::FileIterator {
1308        self.files()
1309    }
1310
1311    fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
1312        self.source_by_path(path)
1313    }
1314}
1315
1316/// An iterator over source files in a Breakpad object.
1317pub struct BreakpadFileIterator<'s> {
1318    files: std::collections::btree_map::Values<'s, u64, &'s str>,
1319}
1320
1321impl<'s> Iterator for BreakpadFileIterator<'s> {
1322    type Item = Result<FileEntry<'s>, BreakpadError>;
1323
1324    fn next(&mut self) -> Option<Self::Item> {
1325        let path = self.files.next()?;
1326        Some(Ok(FileEntry::new(
1327            Cow::default(),
1328            FileInfo::from_path(path.as_bytes()),
1329        )))
1330    }
1331}
1332
1333/// An iterator over functions in a Breakpad object.
1334pub struct BreakpadFunctionIterator<'s> {
1335    file_map: &'s BreakpadFileMap<'s>,
1336    next_line: Option<&'s [u8]>,
1337    inline_origin_map: BreakpadInlineOriginMap<'s>,
1338    lines: Lines<'s>,
1339    max_inline_depth: Option<u32>,
1340}
1341
1342impl<'s> BreakpadFunctionIterator<'s> {
1343    fn new(
1344        file_map: &'s BreakpadFileMap<'s>,
1345        mut lines: Lines<'s>,
1346        max_inline_depth: Option<u32>,
1347    ) -> Self {
1348        let next_line = lines.next();
1349        Self {
1350            file_map,
1351            next_line,
1352            inline_origin_map: Default::default(),
1353            lines,
1354            max_inline_depth,
1355        }
1356    }
1357}
1358
1359impl<'s> Iterator for BreakpadFunctionIterator<'s> {
1360    type Item = Result<Function<'s>, BreakpadError>;
1361
1362    fn next(&mut self) -> Option<Self::Item> {
1363        // Advance to the next FUNC line.
1364        let line = loop {
1365            let line = self.next_line.take()?;
1366            if line.starts_with(b"FUNC ") {
1367                break line;
1368            }
1369
1370            // Fast path: FUNC records are always before stack records. Once we encounter the
1371            // first stack record, we can therefore exit.
1372            if line.starts_with(b"STACK ") {
1373                return None;
1374            }
1375
1376            if line.starts_with(b"INLINE_ORIGIN ") {
1377                let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1378                    Ok(record) => record,
1379                    Err(e) => return Some(Err(e)),
1380                };
1381                self.inline_origin_map
1382                    .insert(inline_origin_record.id, inline_origin_record.name);
1383            }
1384
1385            self.next_line = self.lines.next();
1386        };
1387
1388        let fun_record = match BreakpadFuncRecord::parse(line, Lines::new(&[])) {
1389            Ok(record) => record,
1390            Err(e) => return Some(Err(e)),
1391        };
1392
1393        let mut builder = FunctionBuilder::new(
1394            Name::new(fun_record.name, NameMangling::Unmangled, Language::Unknown),
1395            b"",
1396            fun_record.address,
1397            fun_record.size,
1398        )
1399        .max_inline_depth(self.max_inline_depth);
1400
1401        for line in self.lines.by_ref() {
1402            // Stop parsing LINE records once other expected records are encountered.
1403            if line.starts_with(b"FUNC ")
1404                || line.starts_with(b"PUBLIC ")
1405                || line.starts_with(b"STACK ")
1406            {
1407                self.next_line = Some(line);
1408                break;
1409            }
1410
1411            if line.starts_with(b"INLINE_ORIGIN ") {
1412                let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1413                    Ok(record) => record,
1414                    Err(e) => return Some(Err(e)),
1415                };
1416                self.inline_origin_map
1417                    .insert(inline_origin_record.id, inline_origin_record.name);
1418                continue;
1419            }
1420
1421            if line.starts_with(b"INLINE ") {
1422                let inline_record = match BreakpadInlineRecord::parse(line) {
1423                    Ok(record) => record,
1424                    Err(e) => return Some(Err(e)),
1425                };
1426
1427                let name = self
1428                    .inline_origin_map
1429                    .get(&inline_record.origin_id)
1430                    .cloned()
1431                    .unwrap_or_default();
1432
1433                for address_range in &inline_record.address_ranges {
1434                    builder.add_inlinee(
1435                        inline_record.inline_depth as u32,
1436                        Name::new(name, NameMangling::Unmangled, Language::Unknown),
1437                        address_range.address,
1438                        address_range.size,
1439                        FileInfo::from_path(
1440                            self.file_map
1441                                .get(&inline_record.call_site_file_id)
1442                                .cloned()
1443                                .unwrap_or_default()
1444                                .as_bytes(),
1445                        ),
1446                        inline_record.call_site_line,
1447                    );
1448                }
1449                continue;
1450            }
1451
1452            // There might be empty lines throughout the file (or at the end). This is the only
1453            // iterator that cannot rely on a record identifier, so we have to explicitly skip empty
1454            // lines.
1455            if line.is_empty() {
1456                continue;
1457            }
1458
1459            let line_record = match BreakpadLineRecord::parse(line) {
1460                Ok(line_record) => line_record,
1461                Err(e) => return Some(Err(e)),
1462            };
1463
1464            // Skip line records for empty ranges. These do not carry any information.
1465            if line_record.size == 0 {
1466                continue;
1467            }
1468
1469            let filename = line_record.filename(self.file_map).unwrap_or_default();
1470
1471            builder.add_leaf_line(
1472                line_record.address,
1473                Some(line_record.size),
1474                FileInfo::from_path(filename.as_bytes()),
1475                line_record.line,
1476            );
1477        }
1478
1479        Some(Ok(builder.finish()))
1480    }
1481}
1482
1483impl std::iter::FusedIterator for BreakpadFunctionIterator<'_> {}
1484
1485mod parsing {
1486    use nom::branch::alt;
1487    use nom::bytes::complete::take_while;
1488    use nom::character::complete::{char, hex_digit1, multispace1};
1489    use nom::combinator::{cond, eof, map, rest};
1490    use nom::multi::many1;
1491    use nom::sequence::{pair, tuple};
1492    use nom::{IResult, Parser};
1493    use nom_supreme::error::ErrorTree;
1494    use nom_supreme::final_parser::{Location, RecreateContext};
1495    use nom_supreme::parser_ext::ParserExt;
1496    use nom_supreme::tag::complete::tag;
1497
1498    use super::*;
1499
1500    type ParseResult<'a, T> = IResult<&'a str, T, ErrorTree<&'a str>>;
1501    pub type ParseBreakpadError = ErrorTree<ErrorLine>;
1502
1503    /// A line with a 1-based column position, used for displaying errors.
1504    ///
1505    /// With the default formatter, this prints the line followed by the column number.
1506    /// With the alternate formatter (using `:#`), it prints the line and a caret
1507    /// pointing at the column position.
1508    ///
1509    /// # Example
1510    /// ```ignore
1511    /// use symbolic_debuginfo::breakpad::parsing::ErrorLine;
1512    ///
1513    /// let error_line = ErrorLine {
1514    ///     line: "This line cnotains a typo.".to_string(),
1515    ///     column: 12,
1516    /// };
1517    ///
1518    /// // "This line cnotains a typo.", column 12
1519    /// println!("{}", error_line);
1520    ///
1521    /// // "This line cnotains a typo."
1522    /// //             ^
1523    /// println!("{:#}", error_line);
1524    /// ```
1525    #[derive(Clone, Debug, PartialEq, Eq)]
1526    pub struct ErrorLine {
1527        /// A line of text containing an error.
1528        pub line: String,
1529
1530        /// The position of the error, 1-based.
1531        pub column: usize,
1532    }
1533
1534    impl<'a> RecreateContext<&'a str> for ErrorLine {
1535        fn recreate_context(original_input: &'a str, tail: &'a str) -> Self {
1536            let Location { column, .. } = Location::recreate_context(original_input, tail);
1537            Self {
1538                line: original_input.to_string(),
1539                column,
1540            }
1541        }
1542    }
1543
1544    impl fmt::Display for ErrorLine {
1545        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1546            if f.alternate() {
1547                writeln!(f)?;
1548            }
1549
1550            write!(f, "\"{}\"", self.line)?;
1551
1552            if f.alternate() {
1553                writeln!(f, "\n{:>width$}", "^", width = self.column + 1)?;
1554            } else {
1555                write!(f, ", column {}", self.column)?;
1556            }
1557
1558            Ok(())
1559        }
1560    }
1561
1562    /// Parse a sequence of decimal digits as a number of the given type.
1563    macro_rules! num_dec {
1564        ($ty:ty) => {
1565            nom::character::complete::digit1.map_res(|s: &str| s.parse::<$ty>())
1566        };
1567    }
1568
1569    /// Parse a sequence of hexadecimal digits as a number of the given type.
1570    macro_rules! num_hex {
1571        ($ty:ty) => {
1572            nom::character::complete::hex_digit1.map_res(|n| <$ty>::from_str_radix(n, 16))
1573        };
1574    }
1575
1576    /// Parse a sequence of non-whitespace characters.
1577    fn non_whitespace(input: &str) -> ParseResult<'_, &str> {
1578        take_while(|c: char| !c.is_whitespace())(input)
1579    }
1580
1581    /// Parse to the end of input and return the resulting string.
1582    ///
1583    /// If there is no input, return [`UNKNOWN_NAME`] instead.
1584    fn name(input: &str) -> ParseResult<'_, &str> {
1585        rest.map(|name: &str| if name.is_empty() { UNKNOWN_NAME } else { name })
1586            .parse(input)
1587    }
1588
1589    /// Attempt to parse the character `m` followed by one or more spaces.
1590    ///
1591    /// Returns true if the parse was successful.
1592    fn multiple(input: &str) -> ParseResult<'_, bool> {
1593        let (mut input, multiple) = char('m').opt().parse(input)?;
1594        let multiple = multiple.is_some();
1595        if multiple {
1596            input = multispace1(input)?.0;
1597        }
1598        Ok((input, multiple))
1599    }
1600
1601    /// Parse a line number as a signed decimal number and return `max(0, n)`.
1602    fn line_num(input: &str) -> ParseResult<'_, u64> {
1603        pair(char('-').opt(), num_dec!(u64))
1604            .map(|(sign, num)| if sign.is_some() { 0 } else { num })
1605            .parse(input)
1606    }
1607
1608    /// Parse a [`BreakpadStackWinRecordType`].
1609    fn stack_win_record_type(input: &str) -> ParseResult<'_, BreakpadStackWinRecordType> {
1610        alt((
1611            char('0').value(BreakpadStackWinRecordType::Fpo),
1612            char('1').value(BreakpadStackWinRecordType::Trap),
1613            char('2').value(BreakpadStackWinRecordType::Tss),
1614            char('3').value(BreakpadStackWinRecordType::Standard),
1615            char('4').value(BreakpadStackWinRecordType::FrameData),
1616            non_whitespace.value(BreakpadStackWinRecordType::Unknown),
1617        ))(input)
1618    }
1619
1620    /// Parse a [`BreakpadModuleRecord`].
1621    ///
1622    /// A module record has the form `MODULE <os> <arch> <id>( <name>)?`.
1623    fn module_record(input: &str) -> ParseResult<'_, BreakpadModuleRecord<'_>> {
1624        let (input, _) = tag("MODULE")
1625            .terminated(multispace1)
1626            .context("module record prefix")
1627            .parse(input)?;
1628        let (input, (os, arch, id, name)) = tuple((
1629            non_whitespace.terminated(multispace1).context("os"),
1630            non_whitespace.terminated(multispace1).context("arch"),
1631            hex_digit1
1632                .terminated(multispace1.or(eof))
1633                .context("module id"),
1634            name.context("module name"),
1635        ))
1636        .cut()
1637        .context("module record body")
1638        .parse(input)?;
1639
1640        Ok((input, BreakpadModuleRecord { os, arch, id, name }))
1641    }
1642
1643    /// Parse a [`BreakpadModuleRecord`].
1644    ///
1645    /// A module record has the form `MODULE <os> <arch> <id>( <name>)?`.
1646    /// This will fail if there is any input left over after the record.
1647    pub fn module_record_final(
1648        input: &str,
1649    ) -> Result<BreakpadModuleRecord<'_>, ErrorTree<ErrorLine>> {
1650        nom_supreme::final_parser::final_parser(module_record)(input)
1651    }
1652
1653    /// Parse the `CodeId` variant of a [`BreakpadInfoRecord`].
1654    ///
1655    /// A `CodeId` record has the form `CODE_ID <code_id>( <code_file>)?`.
1656    fn info_code_id_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1657        let (input, _) = tag("CODE_ID")
1658            .terminated(multispace1)
1659            .context("info code_id record prefix")
1660            .parse(input)?;
1661
1662        let (input, (code_id, code_file)) = pair(
1663            hex_digit1
1664                .terminated(multispace1.or(eof))
1665                .context("code id"),
1666            name.context("file name"),
1667        )
1668        .cut()
1669        .context("info code_id record body")
1670        .parse(input)?;
1671
1672        Ok((input, BreakpadInfoRecord::CodeId { code_id, code_file }))
1673    }
1674
1675    /// Parse the `Other` variant of a [`BreakpadInfoRecord`].
1676    ///
1677    /// An `Other` record has the form `<scope>( <info>)?`.
1678    fn info_other_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1679        let (input, (scope, info)) = pair(
1680            non_whitespace
1681                .terminated(multispace1.or(eof))
1682                .context("info scope"),
1683            rest,
1684        )
1685        .cut()
1686        .context("info other record body")
1687        .parse(input)?;
1688
1689        Ok((input, BreakpadInfoRecord::Other { scope, info }))
1690    }
1691
1692    /// Parse a [`BreakpadInfoRecord`].
1693    ///
1694    /// An INFO record has the form `INFO (<code_id_record> | <other_record>)`.
1695    fn info_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1696        let (input, _) = tag("INFO")
1697            .terminated(multispace1)
1698            .context("info record prefix")
1699            .parse(input)?;
1700
1701        info_code_id_record
1702            .or(info_other_record)
1703            .cut()
1704            .context("info record body")
1705            .parse(input)
1706    }
1707
1708    /// Parse a [`BreakpadInfoRecord`].
1709    ///
1710    /// An INFO record has the form `INFO (<code_id_record> | <other_record>)`.
1711    /// This will fail if there is any input left over after the record.
1712    pub fn info_record_final(input: &str) -> Result<BreakpadInfoRecord<'_>, ErrorTree<ErrorLine>> {
1713        nom_supreme::final_parser::final_parser(info_record)(input)
1714    }
1715
1716    /// Parse a [`BreakpadFileRecord`].
1717    ///
1718    /// A FILE record has the form `FILE <id>( <name>)?`.
1719    fn file_record(input: &str) -> ParseResult<'_, BreakpadFileRecord<'_>> {
1720        let (input, _) = tag("FILE")
1721            .terminated(multispace1)
1722            .context("file record prefix")
1723            .parse(input)?;
1724
1725        let (input, (id, name)) = pair(
1726            num_dec!(u64)
1727                .terminated(multispace1.or(eof))
1728                .context("file id"),
1729            rest.context("file name"),
1730        )
1731        .cut()
1732        .context("file record body")
1733        .parse(input)?;
1734
1735        Ok((input, BreakpadFileRecord { id, name }))
1736    }
1737
1738    /// Parse a [`BreakpadFileRecord`].
1739    ///
1740    /// A FILE record has the form `FILE <id>( <name>)?`.
1741    /// This will fail if there is any input left over after the record.
1742    pub fn file_record_final(input: &str) -> Result<BreakpadFileRecord<'_>, ErrorTree<ErrorLine>> {
1743        nom_supreme::final_parser::final_parser(file_record)(input)
1744    }
1745
1746    /// Parse a [`BreakpadInlineOriginRecord`].
1747    ///
1748    /// An INLINE_ORIGIN record has the form `INLINE_ORIGIN <id> <name>`.
1749    fn inline_origin_record(input: &str) -> ParseResult<'_, BreakpadInlineOriginRecord<'_>> {
1750        let (input, _) = tag("INLINE_ORIGIN")
1751            .terminated(multispace1)
1752            .context("inline origin record prefix")
1753            .parse(input)?;
1754
1755        let (input, (id, name)) = pair(
1756            num_dec!(u64)
1757                .terminated(multispace1)
1758                .context("inline origin id"),
1759            rest.context("inline origin name"),
1760        )
1761        .cut()
1762        .context("inline origin record body")
1763        .parse(input)?;
1764
1765        Ok((input, BreakpadInlineOriginRecord { id, name }))
1766    }
1767
1768    /// Parse a [`BreakpadInlineOriginRecord`].
1769    ///
1770    /// An INLINE_ORIGIN record has the form `INLINE_ORIGIN <id> <name>`.
1771    /// This will fail if there is any input left over after the record.
1772    pub fn inline_origin_record_final(
1773        input: &str,
1774    ) -> Result<BreakpadInlineOriginRecord<'_>, ErrorTree<ErrorLine>> {
1775        nom_supreme::final_parser::final_parser(inline_origin_record)(input)
1776    }
1777
1778    /// Parse a [`BreakpadPublicRecord`].
1779    ///
1780    /// A PUBLIC record has the form `PUBLIC (m )? <address> <parameter_size> ( <name>)?`.
1781    fn public_record(input: &str) -> ParseResult<'_, BreakpadPublicRecord<'_>> {
1782        let (input, _) = tag("PUBLIC")
1783            .terminated(multispace1)
1784            .context("public record prefix")
1785            .parse(input)?;
1786
1787        let (input, (multiple, address, parameter_size, name)) = tuple((
1788            multiple.context("multiple flag"),
1789            num_hex!(u64).terminated(multispace1).context("address"),
1790            num_hex!(u64)
1791                .terminated(multispace1.or(eof))
1792                .context("param size"),
1793            name.context("symbol name"),
1794        ))
1795        .cut()
1796        .context("public record body")
1797        .parse(input)?;
1798
1799        Ok((
1800            input,
1801            BreakpadPublicRecord {
1802                multiple,
1803                address,
1804                parameter_size,
1805                name,
1806            },
1807        ))
1808    }
1809
1810    /// Parse a [`BreakpadPublicRecord`].
1811    ///
1812    /// A PUBLIC record has the form `PUBLIC (m )? <address> <parameter_size> ( <name>)?`.
1813    /// This will fail if there is any input left over after the record.
1814    pub fn public_record_final(
1815        input: &str,
1816    ) -> Result<BreakpadPublicRecord<'_>, ErrorTree<ErrorLine>> {
1817        nom_supreme::final_parser::final_parser(public_record)(input)
1818    }
1819
1820    /// Parse a [`BreakpadFuncRecord`].
1821    ///
1822    /// A FUNC record has the form `FUNC (m )? <address> <size> <parameter_size> ( <name>)?`.
1823    fn func_record(input: &str) -> ParseResult<'_, BreakpadFuncRecord<'_>> {
1824        let (input, _) = tag("FUNC")
1825            .terminated(multispace1)
1826            .context("func record prefix")
1827            .parse(input)?;
1828
1829        let (input, (multiple, address, size, parameter_size, name)) = tuple((
1830            multiple.context("multiple flag"),
1831            num_hex!(u64).terminated(multispace1).context("address"),
1832            num_hex!(u64).terminated(multispace1).context("size"),
1833            num_hex!(u64)
1834                .terminated(multispace1.or(eof))
1835                .context("param size"),
1836            name.context("symbol name"),
1837        ))
1838        .cut()
1839        .context("func record body")
1840        .parse(input)?;
1841
1842        Ok((
1843            input,
1844            BreakpadFuncRecord {
1845                multiple,
1846                address,
1847                size,
1848                parameter_size,
1849                name,
1850                lines: Lines::default(),
1851            },
1852        ))
1853    }
1854
1855    /// Parse a [`BreakpadFuncRecord`].
1856    ///
1857    /// A FUNC record has the form `FUNC (m )? <address> <size> <parameter_size> ( <name>)?`.
1858    /// This will fail if there is any input left over after the record.
1859    pub fn func_record_final(input: &str) -> Result<BreakpadFuncRecord<'_>, ErrorTree<ErrorLine>> {
1860        nom_supreme::final_parser::final_parser(func_record)(input)
1861    }
1862
1863    /// Parse a [`BreakpadLineRecord`].
1864    ///
1865    /// A LINE record has the form `<address> <size> <line> <file_id>`.
1866    fn line_record(input: &str) -> ParseResult<'_, BreakpadLineRecord> {
1867        let (input, (address, size, line, file_id)) = tuple((
1868            num_hex!(u64).terminated(multispace1).context("address"),
1869            num_hex!(u64).terminated(multispace1).context("size"),
1870            line_num.terminated(multispace1).context("line number"),
1871            num_dec!(u64).context("file id"),
1872        ))
1873        .context("line record")
1874        .parse(input)?;
1875
1876        Ok((
1877            input,
1878            BreakpadLineRecord {
1879                address,
1880                size,
1881                line,
1882                file_id,
1883            },
1884        ))
1885    }
1886
1887    /// Parse a [`BreakpadLineRecord`].
1888    ///
1889    /// A LINE record has the form `<address> <size> <line> <file_id>`.
1890    /// This will fail if there is any input left over after the record.
1891    pub fn line_record_final(input: &str) -> Result<BreakpadLineRecord, ErrorTree<ErrorLine>> {
1892        nom_supreme::final_parser::final_parser(line_record)(input)
1893    }
1894
1895    /// Parse a [`BreakpadInlineRecord`].
1896    ///
1897    /// An INLINE record has the form `INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id> [<address> <size>]+`.
1898    fn inline_record(input: &str) -> ParseResult<'_, BreakpadInlineRecord> {
1899        let (input, _) = tag("INLINE")
1900            .terminated(multispace1)
1901            .context("inline record prefix")
1902            .parse(input)?;
1903
1904        let (input, (inline_depth, call_site_line, call_site_file_id, origin_id)) = tuple((
1905            num_dec!(u64)
1906                .terminated(multispace1)
1907                .context("inline_nest_level"),
1908            num_dec!(u64)
1909                .terminated(multispace1)
1910                .context("call_site_line"),
1911            num_dec!(u64)
1912                .terminated(multispace1)
1913                .context("call_site_file_id"),
1914            num_dec!(u64).terminated(multispace1).context("origin_id"),
1915        ))
1916        .cut()
1917        .context("func record body")
1918        .parse(input)?;
1919
1920        let (input, address_ranges) = many1(map(
1921            pair(
1922                num_hex!(u64).terminated(multispace1).context("address"),
1923                num_hex!(u64)
1924                    .terminated(multispace1.or(eof))
1925                    .context("size"),
1926            ),
1927            |(address, size)| BreakpadInlineAddressRange { address, size },
1928        ))
1929        .cut()
1930        .context("inline record body")
1931        .parse(input)?;
1932
1933        Ok((
1934            input,
1935            BreakpadInlineRecord {
1936                inline_depth,
1937                call_site_line,
1938                call_site_file_id,
1939                origin_id,
1940                address_ranges,
1941            },
1942        ))
1943    }
1944
1945    /// Parse a [`BreakpadInlineRecord`].
1946    ///
1947    /// An INLINE record has the form `INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id> [<address> <size>]+`.
1948    /// This will fail if there is any input left over after the record.
1949    pub fn inline_record_final(input: &str) -> Result<BreakpadInlineRecord, ErrorTree<ErrorLine>> {
1950        nom_supreme::final_parser::final_parser(inline_record)(input)
1951    }
1952
1953    /// Parse a [`BreakpadStackCfiDeltaRecord`].
1954    ///
1955    /// A STACK CFI Delta record has the form `STACK CFI <address> <rules>`.
1956    fn stack_cfi_delta_record(input: &str) -> ParseResult<'_, BreakpadStackCfiDeltaRecord<'_>> {
1957        let (input, _) = tag("STACK CFI")
1958            .terminated(multispace1)
1959            .context("stack cfi prefix")
1960            .parse(input)?;
1961
1962        let (input, (address, rules)) = pair(
1963            num_hex!(u64).terminated(multispace1).context("address"),
1964            rest.context("rules"),
1965        )
1966        .cut()
1967        .context("stack cfi delta record body")
1968        .parse(input)?;
1969
1970        Ok((input, BreakpadStackCfiDeltaRecord { address, rules }))
1971    }
1972
1973    /// Parse a [`BreakpadStackCfiDeltaRecord`].
1974    ///
1975    /// A STACK CFI Delta record has the form `STACK CFI <address> <rules>`.
1976    /// This will fail if there is any input left over after the record.
1977    pub fn stack_cfi_delta_record_final(
1978        input: &str,
1979    ) -> Result<BreakpadStackCfiDeltaRecord<'_>, ErrorTree<ErrorLine>> {
1980        nom_supreme::final_parser::final_parser(stack_cfi_delta_record)(input)
1981    }
1982
1983    /// Parse a [`BreakpadStackCfiRecord`].
1984    ///
1985    /// A STACK CFI INIT record has the form `STACK CFI INIT <address> <size> <init_rules>`.
1986    fn stack_cfi_record(input: &str) -> ParseResult<'_, BreakpadStackCfiRecord<'_>> {
1987        let (input, _) = tag("STACK CFI INIT")
1988            .terminated(multispace1)
1989            .context("stack cfi init  prefix")
1990            .parse(input)?;
1991
1992        let (input, (start, size, init_rules)) = tuple((
1993            num_hex!(u64).terminated(multispace1).context("start"),
1994            num_hex!(u64).terminated(multispace1).context("size"),
1995            rest.context("rules"),
1996        ))
1997        .cut()
1998        .context("stack cfi record body")
1999        .parse(input)?;
2000
2001        Ok((
2002            input,
2003            BreakpadStackCfiRecord {
2004                start,
2005                size,
2006                init_rules,
2007                deltas: Lines::default(),
2008            },
2009        ))
2010    }
2011
2012    /// Parse a [`BreakpadStackCfiRecord`].
2013    ///
2014    /// A STACK CFI INIT record has the form `STACK CFI INIT <address> <size> <init_rules>`.
2015    /// This will fail if there is any input left over after the record.
2016    pub fn stack_cfi_record_final(
2017        input: &str,
2018    ) -> Result<BreakpadStackCfiRecord<'_>, ErrorTree<ErrorLine>> {
2019        nom_supreme::final_parser::final_parser(stack_cfi_record)(input)
2020    }
2021
2022    /// Parse a [`BreakpadStackWinRecord`].
2023    ///
2024    /// A STACK WIN record has the form
2025    /// `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>)`.
2026    fn stack_win_record(input: &str) -> ParseResult<'_, BreakpadStackWinRecord<'_>> {
2027        let (input, _) = tag("STACK WIN")
2028            .terminated(multispace1)
2029            .context("stack win prefix")
2030            .parse(input)?;
2031
2032        let (
2033            input,
2034            (
2035                ty,
2036                code_start,
2037                code_size,
2038                prolog_size,
2039                epilog_size,
2040                params_size,
2041                saved_regs_size,
2042                locals_size,
2043                max_stack_size,
2044                has_program_string,
2045            ),
2046        ) = tuple((
2047            stack_win_record_type
2048                .terminated(multispace1)
2049                .context("record type"),
2050            num_hex!(u32).terminated(multispace1).context("code start"),
2051            num_hex!(u32).terminated(multispace1).context("code size"),
2052            num_hex!(u16).terminated(multispace1).context("prolog size"),
2053            num_hex!(u16).terminated(multispace1).context("epilog size"),
2054            num_hex!(u32).terminated(multispace1).context("params size"),
2055            num_hex!(u16)
2056                .terminated(multispace1)
2057                .context("saved regs size"),
2058            num_hex!(u32).terminated(multispace1).context("locals size"),
2059            num_hex!(u32)
2060                .terminated(multispace1)
2061                .context("max stack size"),
2062            non_whitespace
2063                .map(|s| s != "0")
2064                .terminated(multispace1)
2065                .context("has_program_string"),
2066        ))
2067        .cut()
2068        .context("stack win record body")
2069        .parse(input)?;
2070
2071        let (input, program_string) =
2072            cond(has_program_string, rest.context("program string"))(input)?;
2073        let (input, uses_base_pointer) =
2074            cond(!has_program_string, non_whitespace.map(|s| s != "0"))
2075                .map(|o| o.unwrap_or(false))
2076                .parse(input)?;
2077
2078        Ok((
2079            input,
2080            BreakpadStackWinRecord {
2081                ty,
2082                code_start,
2083                code_size,
2084                prolog_size,
2085                epilog_size,
2086                params_size,
2087                saved_regs_size,
2088                locals_size,
2089                max_stack_size,
2090                uses_base_pointer,
2091                program_string,
2092            },
2093        ))
2094    }
2095
2096    /// Parse a [`BreakpadStackWinRecord`].
2097    ///
2098    /// A STACK WIN record has the form
2099    /// `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>)`.
2100    /// This will fail if there is any input left over after the record.
2101    pub fn stack_win_record_final(
2102        input: &str,
2103    ) -> Result<BreakpadStackWinRecord<'_>, ErrorTree<ErrorLine>> {
2104        nom_supreme::final_parser::final_parser(stack_win_record)(input)
2105    }
2106
2107    /// Parse a [`BreakpadStackRecord`], containing either a [`BreakpadStackCfiRecord`] or a
2108    /// [`BreakpadStackWinRecord`].
2109    ///
2110    /// This will fail if there is any input left over after the record.
2111    pub fn stack_record_final(input: &str) -> Result<BreakpadStackRecord<'_>, ParseBreakpadError> {
2112        nom_supreme::final_parser::final_parser(alt((
2113            stack_cfi_record.map(BreakpadStackRecord::Cfi),
2114            stack_win_record.map(BreakpadStackRecord::Win),
2115        )))(input)
2116    }
2117}
2118#[cfg(test)]
2119mod tests {
2120    use super::*;
2121
2122    #[test]
2123    fn test_parse_module_record() -> Result<(), BreakpadError> {
2124        let string = b"MODULE Linux x86_64 492E2DD23CC306CA9C494EEF1533A3810 crash";
2125        let record = BreakpadModuleRecord::parse(string)?;
2126
2127        insta::assert_debug_snapshot!(record, @r#"
2128        BreakpadModuleRecord {
2129            os: "Linux",
2130            arch: "x86_64",
2131            id: "492E2DD23CC306CA9C494EEF1533A3810",
2132            name: "crash",
2133        }
2134        "#);
2135
2136        Ok(())
2137    }
2138
2139    #[test]
2140    fn test_parse_module_record_short_id() -> Result<(), BreakpadError> {
2141        // NB: This id is one character short, missing the age. DebugId can handle this, however.
2142        let string = b"MODULE Linux x86_64 6216C672A8D33EC9CF4A1BAB8B29D00E libdispatch.so";
2143        let record = BreakpadModuleRecord::parse(string)?;
2144
2145        insta::assert_debug_snapshot!(record, @r#"
2146        BreakpadModuleRecord {
2147            os: "Linux",
2148            arch: "x86_64",
2149            id: "6216C672A8D33EC9CF4A1BAB8B29D00E",
2150            name: "libdispatch.so",
2151        }
2152        "#);
2153
2154        Ok(())
2155    }
2156
2157    #[test]
2158    fn test_parse_file_record() -> Result<(), BreakpadError> {
2159        let string = b"FILE 37 /usr/include/libkern/i386/_OSByteOrder.h";
2160        let record = BreakpadFileRecord::parse(string)?;
2161
2162        insta::assert_debug_snapshot!(record, @r#"
2163        BreakpadFileRecord {
2164            id: 37,
2165            name: "/usr/include/libkern/i386/_OSByteOrder.h",
2166        }
2167        "#);
2168
2169        Ok(())
2170    }
2171
2172    #[test]
2173    fn test_parse_file_record_space() -> Result<(), BreakpadError> {
2174        let string = b"FILE 38 /usr/local/src/filename with spaces.c";
2175        let record = BreakpadFileRecord::parse(string)?;
2176
2177        insta::assert_debug_snapshot!(record, @r#"
2178        BreakpadFileRecord {
2179            id: 38,
2180            name: "/usr/local/src/filename with spaces.c",
2181        }
2182        "#);
2183
2184        Ok(())
2185    }
2186
2187    #[test]
2188    fn test_parse_inline_origin_record() -> Result<(), BreakpadError> {
2189        let string = b"INLINE_ORIGIN 3529 LZ4F_initStream";
2190        let record = BreakpadInlineOriginRecord::parse(string)?;
2191
2192        insta::assert_debug_snapshot!(record, @r#"
2193        BreakpadInlineOriginRecord {
2194            id: 3529,
2195            name: "LZ4F_initStream",
2196        }
2197        "#);
2198
2199        Ok(())
2200    }
2201
2202    #[test]
2203    fn test_parse_inline_origin_record_space() -> Result<(), BreakpadError> {
2204        let string =
2205            b"INLINE_ORIGIN 3576 unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)";
2206        let record = BreakpadInlineOriginRecord::parse(string)?;
2207
2208        insta::assert_debug_snapshot!(record, @r#"
2209        BreakpadInlineOriginRecord {
2210            id: 3576,
2211            name: "unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)",
2212        }
2213        "#);
2214
2215        Ok(())
2216    }
2217
2218    #[test]
2219    fn test_parse_func_record() -> Result<(), BreakpadError> {
2220        // Lines will be tested separately
2221        let string = b"FUNC 1730 1a 0 <name omitted>";
2222        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2223
2224        insta::assert_debug_snapshot!(record, @r#"
2225        BreakpadFuncRecord {
2226            multiple: false,
2227            address: 5936,
2228            size: 26,
2229            parameter_size: 0,
2230            name: "<name omitted>",
2231        }
2232        "#);
2233
2234        Ok(())
2235    }
2236
2237    #[test]
2238    fn test_parse_func_record_multiple() -> Result<(), BreakpadError> {
2239        let string = b"FUNC m 1730 1a 0 <name omitted>";
2240        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2241
2242        insta::assert_debug_snapshot!(record, @r#"
2243        BreakpadFuncRecord {
2244            multiple: true,
2245            address: 5936,
2246            size: 26,
2247            parameter_size: 0,
2248            name: "<name omitted>",
2249        }
2250        "#);
2251
2252        Ok(())
2253    }
2254
2255    #[test]
2256    fn test_parse_func_record_no_name() -> Result<(), BreakpadError> {
2257        let string = b"FUNC 0 f 0";
2258        let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2259
2260        insta::assert_debug_snapshot!(record, @r#"
2261        BreakpadFuncRecord {
2262            multiple: false,
2263            address: 0,
2264            size: 15,
2265            parameter_size: 0,
2266            name: "<unknown>",
2267        }
2268        "#);
2269
2270        Ok(())
2271    }
2272
2273    #[test]
2274    fn test_parse_line_record() -> Result<(), BreakpadError> {
2275        let string = b"1730 6 93 20";
2276        let record = BreakpadLineRecord::parse(string)?;
2277
2278        insta::assert_debug_snapshot!(record, @r"
2279        BreakpadLineRecord {
2280            address: 5936,
2281            size: 6,
2282            line: 93,
2283            file_id: 20,
2284        }
2285        ");
2286
2287        Ok(())
2288    }
2289
2290    #[test]
2291    fn test_parse_line_record_negative_line() -> Result<(), BreakpadError> {
2292        let string = b"e0fd10 5 -376 2225";
2293        let record = BreakpadLineRecord::parse(string)?;
2294
2295        insta::assert_debug_snapshot!(record, @r"
2296        BreakpadLineRecord {
2297            address: 14744848,
2298            size: 5,
2299            line: 0,
2300            file_id: 2225,
2301        }
2302        ");
2303
2304        Ok(())
2305    }
2306
2307    #[test]
2308    fn test_parse_line_record_whitespace() -> Result<(), BreakpadError> {
2309        let string = b"    1000 1c 2972 2
2310";
2311        let record = BreakpadLineRecord::parse(string)?;
2312
2313        insta::assert_debug_snapshot!(
2314            record, @r"
2315        BreakpadLineRecord {
2316            address: 4096,
2317            size: 28,
2318            line: 2972,
2319            file_id: 2,
2320        }
2321        ");
2322
2323        Ok(())
2324    }
2325
2326    #[test]
2327    fn test_parse_public_record() -> Result<(), BreakpadError> {
2328        let string = b"PUBLIC 5180 0 __clang_call_terminate";
2329        let record = BreakpadPublicRecord::parse(string)?;
2330
2331        insta::assert_debug_snapshot!(record, @r#"
2332        BreakpadPublicRecord {
2333            multiple: false,
2334            address: 20864,
2335            parameter_size: 0,
2336            name: "__clang_call_terminate",
2337        }
2338        "#);
2339
2340        Ok(())
2341    }
2342
2343    #[test]
2344    fn test_parse_public_record_multiple() -> Result<(), BreakpadError> {
2345        let string = b"PUBLIC m 5180 0 __clang_call_terminate";
2346        let record = BreakpadPublicRecord::parse(string)?;
2347
2348        insta::assert_debug_snapshot!(record, @r#"
2349        BreakpadPublicRecord {
2350            multiple: true,
2351            address: 20864,
2352            parameter_size: 0,
2353            name: "__clang_call_terminate",
2354        }
2355        "#);
2356
2357        Ok(())
2358    }
2359
2360    #[test]
2361    fn test_parse_public_record_no_name() -> Result<(), BreakpadError> {
2362        let string = b"PUBLIC 5180 0";
2363        let record = BreakpadPublicRecord::parse(string)?;
2364
2365        insta::assert_debug_snapshot!(record, @r#"
2366        BreakpadPublicRecord {
2367            multiple: false,
2368            address: 20864,
2369            parameter_size: 0,
2370            name: "<unknown>",
2371        }
2372        "#);
2373
2374        Ok(())
2375    }
2376
2377    #[test]
2378    fn test_parse_inline_record() -> Result<(), BreakpadError> {
2379        let string = b"INLINE 0 3082 52 1410 49200 10";
2380        let record = BreakpadInlineRecord::parse(string)?;
2381
2382        insta::assert_debug_snapshot!(record, @r"
2383        BreakpadInlineRecord {
2384            inline_depth: 0,
2385            call_site_line: 3082,
2386            call_site_file_id: 52,
2387            origin_id: 1410,
2388            address_ranges: [
2389                BreakpadInlineAddressRange {
2390                    address: 299520,
2391                    size: 16,
2392                },
2393            ],
2394        }
2395        ");
2396
2397        Ok(())
2398    }
2399
2400    #[test]
2401    fn test_parse_inline_record_multiple() -> Result<(), BreakpadError> {
2402        let string = b"INLINE 6 642 8 207 8b110 18 8b154 18";
2403        let record = BreakpadInlineRecord::parse(string)?;
2404
2405        insta::assert_debug_snapshot!(record, @r"
2406        BreakpadInlineRecord {
2407            inline_depth: 6,
2408            call_site_line: 642,
2409            call_site_file_id: 8,
2410            origin_id: 207,
2411            address_ranges: [
2412                BreakpadInlineAddressRange {
2413                    address: 569616,
2414                    size: 24,
2415                },
2416                BreakpadInlineAddressRange {
2417                    address: 569684,
2418                    size: 24,
2419                },
2420            ],
2421        }
2422        ");
2423
2424        Ok(())
2425    }
2426
2427    #[test]
2428    fn test_parse_inline_record_err_missing_address_range() {
2429        let string = b"INLINE 6 642 8 207";
2430        let record = BreakpadInlineRecord::parse(string);
2431        assert!(record.is_err());
2432    }
2433
2434    #[test]
2435    fn test_parse_stack_cfi_init_record() -> Result<(), BreakpadError> {
2436        let string = b"STACK CFI INIT 1880 2d .cfa: $rsp 8 + .ra: .cfa -8 + ^";
2437        let record = BreakpadStackRecord::parse(string)?;
2438
2439        insta::assert_debug_snapshot!(record, @r#"
2440        Cfi(
2441            BreakpadStackCfiRecord {
2442                start: 6272,
2443                size: 45,
2444                init_rules: ".cfa: $rsp 8 + .ra: .cfa -8 + ^",
2445                deltas: Lines(
2446                    LineOffsets {
2447                        data: [],
2448                        finished: true,
2449                        index: 0,
2450                    },
2451                ),
2452            },
2453        )
2454        "#);
2455
2456        Ok(())
2457    }
2458
2459    #[test]
2460    fn test_parse_stack_win_record() -> Result<(), BreakpadError> {
2461        let string =
2462            b"STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =";
2463        let record = BreakpadStackRecord::parse(string)?;
2464
2465        insta::assert_debug_snapshot!(record, @r#"
2466        Win(
2467            BreakpadStackWinRecord {
2468                ty: FrameData,
2469                code_start: 14106,
2470                code_size: 12,
2471                prolog_size: 0,
2472                epilog_size: 0,
2473                params_size: 0,
2474                saved_regs_size: 0,
2475                locals_size: 0,
2476                max_stack_size: 0,
2477                uses_base_pointer: false,
2478                program_string: Some(
2479                    "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2480                ),
2481            },
2482        )
2483        "#);
2484
2485        Ok(())
2486    }
2487
2488    #[test]
2489    fn test_parse_stack_win_record_type_3() -> Result<(), BreakpadError> {
2490        let string = b"STACK WIN 3 8a10b ec b 0 c c 4 0 0 1";
2491        let record = BreakpadStackWinRecord::parse(string)?;
2492
2493        insta::assert_debug_snapshot!(record, @r"
2494        BreakpadStackWinRecord {
2495            ty: Standard,
2496            code_start: 565515,
2497            code_size: 236,
2498            prolog_size: 11,
2499            epilog_size: 0,
2500            params_size: 12,
2501            saved_regs_size: 12,
2502            locals_size: 4,
2503            max_stack_size: 0,
2504            uses_base_pointer: true,
2505            program_string: None,
2506        }
2507        ");
2508
2509        Ok(())
2510    }
2511
2512    #[test]
2513    fn test_parse_stack_win_whitespace() -> Result<(), BreakpadError> {
2514        let string =
2515            b"     STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =
2516                ";
2517        let record = BreakpadStackRecord::parse(string)?;
2518
2519        insta::assert_debug_snapshot!(record, @r#"
2520        Win(
2521            BreakpadStackWinRecord {
2522                ty: FrameData,
2523                code_start: 14106,
2524                code_size: 12,
2525                prolog_size: 0,
2526                epilog_size: 0,
2527                params_size: 0,
2528                saved_regs_size: 0,
2529                locals_size: 0,
2530                max_stack_size: 0,
2531                uses_base_pointer: false,
2532                program_string: Some(
2533                    "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2534                ),
2535            },
2536        )
2537        "#);
2538        Ok(())
2539    }
2540
2541    use similar_asserts::assert_eq;
2542
2543    #[test]
2544    fn test_lineoffsets_fused() {
2545        let data = b"";
2546        let mut offsets = LineOffsets::new(data);
2547
2548        offsets.next();
2549        assert_eq!(None, offsets.next());
2550        assert_eq!(None, offsets.next());
2551        assert_eq!(None, offsets.next());
2552    }
2553
2554    macro_rules! test_lineoffsets {
2555        ($name:ident, $data:literal, $( ($index:literal, $line:literal) ),*) => {
2556            #[test]
2557            fn $name() {
2558                let mut offsets = LineOffsets::new($data);
2559
2560                $(
2561                    assert_eq!(Some(($index, &$line[..])), offsets.next());
2562                )*
2563                assert_eq!(None, offsets.next());
2564            }
2565        };
2566    }
2567
2568    test_lineoffsets!(test_lineoffsets_empty, b"", (0, b""));
2569    test_lineoffsets!(test_lineoffsets_oneline, b"hello", (0, b"hello"));
2570    test_lineoffsets!(
2571        test_lineoffsets_trailing_n,
2572        b"hello\n",
2573        (0, b"hello"),
2574        (6, b"")
2575    );
2576    test_lineoffsets!(
2577        test_lineoffsets_trailing_rn,
2578        b"hello\r\n",
2579        (0, b"hello"),
2580        (7, b"")
2581    );
2582    test_lineoffsets!(
2583        test_lineoffsets_n,
2584        b"hello\nworld\nyo",
2585        (0, b"hello"),
2586        (6, b"world"),
2587        (12, b"yo")
2588    );
2589    test_lineoffsets!(
2590        test_lineoffsets_rn,
2591        b"hello\r\nworld\r\nyo",
2592        (0, b"hello"),
2593        (7, b"world"),
2594        (14, b"yo")
2595    );
2596    test_lineoffsets!(
2597        test_lineoffsets_mixed,
2598        b"hello\r\nworld\nyo",
2599        (0, b"hello"),
2600        (7, b"world"),
2601        (13, b"yo")
2602    );
2603}