1use 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
117const BREAKPAD_HEADER_CAP: usize = 320;
122
123const UNKNOWN_NAME: &str = "<unknown>";
125
126#[non_exhaustive]
128#[derive(Copy, Clone, Debug, PartialEq, Eq)]
129pub enum BreakpadErrorKind {
130 InvalidMagic,
132
133 BadEncoding,
135
136 #[deprecated(note = "This is now covered by the Parse variant")]
138 BadSyntax,
139
140 Parse(&'static str),
144
145 InvalidModuleId,
147
148 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#[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 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 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
217pub struct BreakpadModuleRecord<'d> {
218 pub os: &'d str,
220 pub arch: &'d str,
222 pub id: &'d str,
224 pub name: &'d str,
230}
231
232impl<'d> BreakpadModuleRecord<'d> {
233 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#[derive(Clone, Debug, Eq, PartialEq)]
246pub enum BreakpadInfoRecord<'d> {
247 CodeId {
249 code_id: &'d str,
251 code_file: &'d str,
253 },
254 Other {
256 scope: &'d str,
258 info: &'d str,
260 },
261}
262
263impl<'d> BreakpadInfoRecord<'d> {
264 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#[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 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
315pub struct BreakpadFileRecord<'d> {
316 pub id: u64,
318 pub name: &'d str,
320}
321
322impl<'d> BreakpadFileRecord<'d> {
323 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#[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 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
363pub type BreakpadFileMap<'d> = BTreeMap<u64, &'d str>;
365
366#[derive(Clone, Debug, Default, Eq, PartialEq)]
378pub struct BreakpadInlineOriginRecord<'d> {
379 pub id: u64,
381 pub name: &'d str,
383}
384
385impl<'d> BreakpadInlineOriginRecord<'d> {
386 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
393pub type BreakpadInlineOriginMap<'d> = BTreeMap<u64, &'d str>;
395
396#[derive(Clone, Debug, Default, Eq, PartialEq)]
402pub struct BreakpadPublicRecord<'d> {
403 pub multiple: bool,
405 pub address: u64,
407 pub parameter_size: u64,
409 pub name: &'d str,
411}
412
413impl<'d> BreakpadPublicRecord<'d> {
414 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#[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 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#[derive(Clone, Default)]
461pub struct BreakpadFuncRecord<'d> {
462 pub multiple: bool,
464 pub address: u64,
466 pub size: u64,
468 pub parameter_size: u64,
470 pub name: &'d str,
472 lines: Lines<'d>,
473}
474
475impl<'d> BreakpadFuncRecord<'d> {
476 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 pub fn lines(&self) -> BreakpadLineRecords<'d> {
491 BreakpadLineRecords {
492 lines: self.lines.clone(),
493 finished: false,
494 }
495 }
496
497 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#[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 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
572pub struct BreakpadLineRecord {
573 pub address: u64,
575 pub size: u64,
577 pub line: u64,
579 pub file_id: u64,
581}
582
583impl BreakpadLineRecord {
584 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 pub fn filename<'d>(&self, file_map: &BreakpadFileMap<'d>) -> Option<&'d str> {
592 file_map.get(&self.file_id).cloned()
593 }
594
595 pub fn range(&self) -> Range<u64> {
597 self.address..self.address + self.size
598 }
599}
600
601#[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 if line.starts_with(b"FUNC ")
619 || line.starts_with(b"PUBLIC ")
620 || line.starts_with(b"STACK ")
621 {
622 break;
623 }
624
625 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 if record.size > 0 {
639 return Some(Ok(record));
640 }
641 }
642
643 self.finished = true;
644 None
645 }
646}
647
648#[derive(Clone, Debug, Default, Eq, PartialEq)]
659pub struct BreakpadInlineRecord {
660 pub inline_depth: u64,
662 pub call_site_line: u64,
664 pub call_site_file_id: u64,
666 pub origin_id: u64,
668 pub address_ranges: Vec<BreakpadInlineAddressRange>,
671}
672
673impl BreakpadInlineRecord {
674 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#[derive(Clone, Debug, Default, Eq, PartialEq)]
684pub struct BreakpadInlineAddressRange {
685 pub address: u64,
687 pub size: u64,
689}
690
691impl BreakpadInlineAddressRange {
692 pub fn range(&self) -> Range<u64> {
694 self.address..self.address + self.size
695 }
696}
697
698#[derive(Clone, Debug, Eq, PartialEq, Default)]
700pub struct BreakpadStackCfiDeltaRecord<'d> {
701 pub address: u64,
703
704 pub rules: &'d str,
706}
707
708impl<'d> BreakpadStackCfiDeltaRecord<'d> {
709 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#[derive(Clone, Debug, Default)]
721pub struct BreakpadStackCfiRecord<'d> {
722 pub start: u64,
724
725 pub size: u64,
727
728 pub init_rules: &'d str,
730
731 deltas: Lines<'d>,
733}
734
735impl<'d> BreakpadStackCfiRecord<'d> {
736 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 pub fn deltas(&self) -> BreakpadStackCfiDeltaRecords<'d> {
744 BreakpadStackCfiDeltaRecords {
745 lines: self.deltas.clone(),
746 }
747 }
748
749 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#[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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
790pub enum BreakpadStackWinRecordType {
791 Fpo = 0,
793
794 Trap = 1,
796
797 Tss = 2,
799
800 Standard = 3,
802
803 FrameData = 4,
805
806 Unknown = -1,
808}
809
810#[derive(Clone, Debug, Eq, PartialEq)]
816pub struct BreakpadStackWinRecord<'d> {
817 pub ty: BreakpadStackWinRecordType,
819
820 pub code_start: u32,
822
823 pub code_size: u32,
825
826 pub prolog_size: u16,
828
829 pub epilog_size: u16,
831
832 pub params_size: u32,
834
835 pub saved_regs_size: u16,
837
838 pub locals_size: u32,
840
841 pub max_stack_size: u32,
843
844 pub uses_base_pointer: bool,
848
849 pub program_string: Option<&'d str>,
853}
854
855impl<'d> BreakpadStackWinRecord<'d> {
856 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 pub fn code_range(&self) -> Range<u32> {
864 self.code_start..self.code_start + self.code_size
865 }
866}
867
868#[derive(Clone, Debug, Eq, PartialEq)]
870pub enum BreakpadStackRecord<'d> {
871 Cfi(BreakpadStackCfiRecord<'d>),
873 Win(BreakpadStackWinRecord<'d>),
875}
876
877impl<'d> BreakpadStackRecord<'d> {
878 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#[derive(Clone, Debug)]
887pub struct BreakpadStackRecords<'d> {
888 lines: Lines<'d>,
889 finished: bool,
890}
891
892impl<'d> BreakpadStackRecords<'d> {
893 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
928pub struct BreakpadObject<'data> {
943 id: DebugId,
944 arch: Arch,
945 module: BreakpadModuleRecord<'data>,
946 data: &'data [u8],
947}
948
949impl<'data> BreakpadObject<'data> {
950 pub fn test(data: &[u8]) -> bool {
952 data.starts_with(b"MODULE ")
953 }
954
955 pub fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
957 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 pub fn file_format(&self) -> FileFormat {
989 FileFormat::Breakpad
990 }
991
992 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 pub fn debug_id(&self) -> DebugId {
1007 self.id
1008 }
1009
1010 pub fn arch(&self) -> Arch {
1012 self.arch
1013 }
1014
1015 pub fn name(&self) -> &'data str {
1021 self.module.name
1022 }
1023
1024 pub fn kind(&self) -> ObjectKind {
1026 ObjectKind::Debug
1027 }
1028
1029 pub fn load_address(&self) -> u64 {
1034 0 }
1036
1037 pub fn has_symbols(&self) -> bool {
1039 self.public_records().next().is_some()
1040 }
1041
1042 pub fn symbols(&self) -> BreakpadSymbolIterator<'data> {
1044 BreakpadSymbolIterator {
1045 records: self.public_records(),
1046 }
1047 }
1048
1049 pub fn symbol_map(&self) -> SymbolMap<'data> {
1051 self.symbols().collect()
1052 }
1053
1054 pub fn has_debug_info(&self) -> bool {
1056 self.func_records().next().is_some()
1057 }
1058
1059 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 pub fn has_unwind_info(&self) -> bool {
1077 self.stack_records().next().is_some()
1078 }
1079
1080 pub fn has_sources(&self) -> bool {
1082 false
1083 }
1084
1085 pub fn is_malformed(&self) -> bool {
1087 false
1088 }
1089
1090 pub fn info_records(&self) -> BreakpadInfoRecords<'data> {
1092 BreakpadInfoRecords {
1093 lines: Lines::new(self.data),
1094 finished: false,
1095 }
1096 }
1097
1098 pub fn file_records(&self) -> BreakpadFileRecords<'data> {
1100 BreakpadFileRecords {
1101 lines: Lines::new(self.data),
1102 finished: false,
1103 }
1104 }
1105
1106 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 pub fn public_records(&self) -> BreakpadPublicRecords<'data> {
1116 BreakpadPublicRecords {
1117 lines: Lines::new(self.data),
1118 finished: false,
1119 }
1120 }
1121
1122 pub fn func_records(&self) -> BreakpadFuncRecords<'data> {
1124 BreakpadFuncRecords {
1125 lines: Lines::new(self.data),
1126 finished: false,
1127 }
1128 }
1129
1130 pub fn stack_records(&self) -> BreakpadStackRecords<'data> {
1132 BreakpadStackRecords {
1133 lines: Lines::new(self.data),
1134 finished: false,
1135 }
1136 }
1137
1138 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
1241pub 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
1260pub struct BreakpadDebugSession<'data> {
1262 file_map: BreakpadFileMap<'data>,
1263 lines: Lines<'data>,
1264}
1265
1266impl BreakpadDebugSession<'_> {
1267 pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
1269 BreakpadFunctionIterator::new(&self.file_map, self.lines.clone())
1270 }
1271
1272 pub fn files(&self) -> BreakpadFileIterator<'_> {
1274 BreakpadFileIterator {
1275 files: self.file_map.values(),
1276 }
1277 }
1278
1279 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
1306pub 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
1323pub 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 let line = loop {
1349 let line = self.next_line.take()?;
1350 if line.starts_with(b"FUNC ") {
1351 break line;
1352 }
1353
1354 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 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 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 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 #[derive(Clone, Debug, PartialEq, Eq)]
1509 pub struct ErrorLine {
1510 pub line: String,
1512
1513 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 macro_rules! num_dec {
1547 ($ty:ty) => {
1548 nom::character::complete::digit1.map_res(|s: &str| s.parse::<$ty>())
1549 };
1550 }
1551
1552 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 fn non_whitespace(input: &str) -> ParseResult<'_, &str> {
1561 take_while(|c: char| !c.is_whitespace())(input)
1562 }
1563
1564 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 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 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 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 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 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 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 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 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 pub fn info_record_final(input: &str) -> Result<BreakpadInfoRecord<'_>, ErrorTree<ErrorLine>> {
1696 nom_supreme::final_parser::final_parser(info_record)(input)
1697 }
1698
1699 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 pub fn file_record_final(input: &str) -> Result<BreakpadFileRecord<'_>, ErrorTree<ErrorLine>> {
1726 nom_supreme::final_parser::final_parser(file_record)(input)
1727 }
1728
1729 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 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 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 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 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 pub fn func_record_final(input: &str) -> Result<BreakpadFuncRecord<'_>, ErrorTree<ErrorLine>> {
1843 nom_supreme::final_parser::final_parser(func_record)(input)
1844 }
1845
1846 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 pub fn line_record_final(input: &str) -> Result<BreakpadLineRecord, ErrorTree<ErrorLine>> {
1875 nom_supreme::final_parser::final_parser(line_record)(input)
1876 }
1877
1878 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 pub fn inline_record_final(input: &str) -> Result<BreakpadInlineRecord, ErrorTree<ErrorLine>> {
1933 nom_supreme::final_parser::final_parser(inline_record)(input)
1934 }
1935
1936 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 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 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 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 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 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 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 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 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}