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