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;
17use crate::ParseObjectOptions;
18
19#[derive(Clone, Debug)]
20struct LineOffsets<'data> {
21 data: &'data [u8],
22 finished: bool,
23 index: usize,
24}
25
26impl<'data> LineOffsets<'data> {
27 #[inline]
28 fn new(data: &'data [u8]) -> Self {
29 Self {
30 data,
31 finished: false,
32 index: 0,
33 }
34 }
35}
36
37impl Default for LineOffsets<'_> {
38 #[inline]
39 fn default() -> Self {
40 Self {
41 data: &[],
42 finished: true,
43 index: 0,
44 }
45 }
46}
47
48impl<'data> Iterator for LineOffsets<'data> {
49 type Item = (usize, &'data [u8]);
50
51 #[inline]
52 fn next(&mut self) -> Option<Self::Item> {
53 if self.finished {
54 return None;
55 }
56
57 match self.data.iter().position(|b| *b == b'\n') {
58 None => {
59 if self.finished {
60 None
61 } else {
62 self.finished = true;
63 Some((self.index, self.data))
64 }
65 }
66 Some(index) => {
67 let mut data = &self.data[..index];
68 if index > 0 && data[index - 1] == b'\r' {
69 data = &data[..index - 1];
70 }
71
72 let item = Some((self.index, data));
73 self.index += index + 1;
74 self.data = &self.data[index + 1..];
75 item
76 }
77 }
78 }
79
80 #[inline]
81 fn size_hint(&self) -> (usize, Option<usize>) {
82 if self.finished {
83 (0, Some(0))
84 } else {
85 (1, Some(self.data.len() + 1))
86 }
87 }
88}
89
90impl std::iter::FusedIterator for LineOffsets<'_> {}
91
92#[allow(missing_docs)]
93#[derive(Clone, Debug, Default)]
94pub struct Lines<'data>(LineOffsets<'data>);
95
96impl<'data> Lines<'data> {
97 #[inline]
98 #[allow(missing_docs)]
99 pub fn new(data: &'data [u8]) -> Self {
100 Self(LineOffsets::new(data))
101 }
102}
103
104impl<'data> Iterator for Lines<'data> {
105 type Item = &'data [u8];
106
107 fn next(&mut self) -> Option<Self::Item> {
108 self.0.next().map(|tup| tup.1)
109 }
110
111 fn size_hint(&self) -> (usize, Option<usize>) {
112 self.0.size_hint()
113 }
114}
115
116impl std::iter::FusedIterator for Lines<'_> {}
117
118const BREAKPAD_HEADER_CAP: usize = 320;
123
124const UNKNOWN_NAME: &str = "<unknown>";
126
127#[non_exhaustive]
129#[derive(Copy, Clone, Debug, PartialEq, Eq)]
130pub enum BreakpadErrorKind {
131 InvalidMagic,
133
134 BadEncoding,
136
137 #[deprecated(note = "This is now covered by the Parse variant")]
139 BadSyntax,
140
141 Parse(&'static str),
145
146 InvalidModuleId,
148
149 InvalidArchitecture,
151}
152
153impl fmt::Display for BreakpadErrorKind {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 match self {
156 Self::InvalidMagic => write!(f, "missing breakpad symbol header"),
157 Self::BadEncoding => write!(f, "bad utf-8 sequence"),
158 Self::Parse(_) => write!(f, "parsing error"),
159 Self::InvalidModuleId => write!(f, "invalid module id"),
160 Self::InvalidArchitecture => write!(f, "invalid architecture"),
161 _ => Ok(()),
162 }
163 }
164}
165
166#[derive(Debug, Error)]
168#[error("{kind}")]
169pub struct BreakpadError {
170 kind: BreakpadErrorKind,
171 #[source]
172 source: Option<Box<dyn Error + Send + Sync + 'static>>,
173}
174
175impl BreakpadError {
176 fn new<E>(kind: BreakpadErrorKind, source: E) -> Self
179 where
180 E: Into<Box<dyn Error + Send + Sync>>,
181 {
182 let source = Some(source.into());
183 Self { kind, source }
184 }
185
186 pub fn kind(&self) -> BreakpadErrorKind {
188 self.kind
189 }
190}
191
192impl From<BreakpadErrorKind> for BreakpadError {
193 fn from(kind: BreakpadErrorKind) -> Self {
194 Self { kind, source: None }
195 }
196}
197
198impl From<str::Utf8Error> for BreakpadError {
199 fn from(e: str::Utf8Error) -> Self {
200 Self::new(BreakpadErrorKind::BadEncoding, e)
201 }
202}
203
204impl From<parsing::ParseBreakpadError> for BreakpadError {
205 fn from(e: parsing::ParseBreakpadError) -> Self {
206 Self::new(BreakpadErrorKind::Parse(""), e)
207 }
208}
209
210#[derive(Clone, Debug, Default, Eq, PartialEq)]
218pub struct BreakpadModuleRecord<'d> {
219 pub os: &'d str,
221 pub arch: &'d str,
223 pub id: &'d str,
225 pub name: &'d str,
231}
232
233impl<'d> BreakpadModuleRecord<'d> {
234 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
236 let string = str::from_utf8(data)?;
237 Ok(parsing::module_record_final(string.trim())?)
238 }
239}
240
241#[derive(Clone, Debug, Eq, PartialEq)]
247pub enum BreakpadInfoRecord<'d> {
248 CodeId {
250 code_id: &'d str,
252 code_file: &'d str,
254 },
255 Other {
257 scope: &'d str,
259 info: &'d str,
261 },
262}
263
264impl<'d> BreakpadInfoRecord<'d> {
265 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
267 let string = str::from_utf8(data)?;
268 Ok(parsing::info_record_final(string.trim())?)
269 }
270}
271
272#[derive(Clone, Debug)]
274pub struct BreakpadInfoRecords<'d> {
275 lines: Lines<'d>,
276 finished: bool,
277}
278
279impl<'d> Iterator for BreakpadInfoRecords<'d> {
280 type Item = Result<BreakpadInfoRecord<'d>, BreakpadError>;
281
282 fn next(&mut self) -> Option<Self::Item> {
283 if self.finished {
284 return None;
285 }
286
287 for line in &mut self.lines {
288 if line.starts_with(b"MODULE ") {
289 continue;
290 }
291
292 if !line.starts_with(b"INFO ") {
294 break;
295 }
296
297 return Some(BreakpadInfoRecord::parse(line));
298 }
299
300 self.finished = true;
301 None
302 }
303}
304
305#[derive(Clone, Debug, Default, Eq, PartialEq)]
316pub struct BreakpadFileRecord<'d> {
317 pub id: u64,
319 pub name: &'d str,
321}
322
323impl<'d> BreakpadFileRecord<'d> {
324 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
326 let string = str::from_utf8(data)?;
327 Ok(parsing::file_record_final(string.trim())?)
328 }
329}
330
331#[derive(Clone, Debug)]
333pub struct BreakpadFileRecords<'d> {
334 lines: Lines<'d>,
335 finished: bool,
336}
337
338impl<'d> Iterator for BreakpadFileRecords<'d> {
339 type Item = Result<BreakpadFileRecord<'d>, BreakpadError>;
340
341 fn next(&mut self) -> Option<Self::Item> {
342 if self.finished {
343 return None;
344 }
345
346 for line in &mut self.lines {
347 if line.starts_with(b"MODULE ") || line.starts_with(b"INFO ") {
348 continue;
349 }
350
351 if !line.starts_with(b"FILE ") {
353 break;
354 }
355
356 return Some(BreakpadFileRecord::parse(line));
357 }
358
359 self.finished = true;
360 None
361 }
362}
363
364pub type BreakpadFileMap<'d> = BTreeMap<u64, &'d str>;
366
367#[derive(Clone, Debug, Default, Eq, PartialEq)]
379pub struct BreakpadInlineOriginRecord<'d> {
380 pub id: u64,
382 pub name: &'d str,
384}
385
386impl<'d> BreakpadInlineOriginRecord<'d> {
387 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
389 let string = str::from_utf8(data)?;
390 Ok(parsing::inline_origin_record_final(string.trim())?)
391 }
392}
393
394pub type BreakpadInlineOriginMap<'d> = BTreeMap<u64, &'d str>;
396
397#[derive(Clone, Debug, Default, Eq, PartialEq)]
403pub struct BreakpadPublicRecord<'d> {
404 pub multiple: bool,
406 pub address: u64,
408 pub parameter_size: u64,
410 pub name: &'d str,
412}
413
414impl<'d> BreakpadPublicRecord<'d> {
415 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
417 let string = str::from_utf8(data)?;
418 Ok(parsing::public_record_final(string.trim())?)
419 }
420}
421
422#[derive(Clone, Debug)]
424pub struct BreakpadPublicRecords<'d> {
425 lines: Lines<'d>,
426 finished: bool,
427}
428
429impl<'d> Iterator for BreakpadPublicRecords<'d> {
430 type Item = Result<BreakpadPublicRecord<'d>, BreakpadError>;
431
432 fn next(&mut self) -> Option<Self::Item> {
433 if self.finished {
434 return None;
435 }
436
437 for line in &mut self.lines {
438 if line.starts_with(b"STACK ") {
441 break;
442 }
443
444 if !line.starts_with(b"PUBLIC ") {
445 continue;
446 }
447
448 return Some(BreakpadPublicRecord::parse(line));
449 }
450
451 self.finished = true;
452 None
453 }
454}
455
456#[derive(Clone, Default)]
462pub struct BreakpadFuncRecord<'d> {
463 pub multiple: bool,
465 pub address: u64,
467 pub size: u64,
469 pub parameter_size: u64,
471 pub name: &'d str,
473 lines: Lines<'d>,
474}
475
476impl<'d> BreakpadFuncRecord<'d> {
477 pub fn parse(data: &'d [u8], lines: Lines<'d>) -> Result<Self, BreakpadError> {
483 let string = str::from_utf8(data)?;
484 let mut record = parsing::func_record_final(string.trim())?;
485
486 record.lines = lines;
487 Ok(record)
488 }
489
490 pub fn lines(&self) -> BreakpadLineRecords<'d> {
492 BreakpadLineRecords {
493 lines: self.lines.clone(),
494 finished: false,
495 }
496 }
497
498 pub fn range(&self) -> Range<u64> {
500 self.address..self.address + self.size
501 }
502}
503
504impl PartialEq for BreakpadFuncRecord<'_> {
505 fn eq(&self, other: &BreakpadFuncRecord<'_>) -> bool {
506 self.multiple == other.multiple
507 && self.address == other.address
508 && self.size == other.size
509 && self.parameter_size == other.parameter_size
510 && self.name == other.name
511 }
512}
513
514impl Eq for BreakpadFuncRecord<'_> {}
515
516impl fmt::Debug for BreakpadFuncRecord<'_> {
517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518 f.debug_struct("BreakpadFuncRecord")
519 .field("multiple", &self.multiple)
520 .field("address", &self.address)
521 .field("size", &self.size)
522 .field("parameter_size", &self.parameter_size)
523 .field("name", &self.name)
524 .finish()
525 }
526}
527
528#[derive(Clone, Debug)]
530pub struct BreakpadFuncRecords<'d> {
531 lines: Lines<'d>,
532 finished: bool,
533}
534
535impl<'d> Iterator for BreakpadFuncRecords<'d> {
536 type Item = Result<BreakpadFuncRecord<'d>, BreakpadError>;
537
538 fn next(&mut self) -> Option<Self::Item> {
539 if self.finished {
540 return None;
541 }
542
543 for line in &mut self.lines {
544 if line.starts_with(b"STACK ") {
547 break;
548 }
549
550 if !line.starts_with(b"FUNC ") {
551 continue;
552 }
553
554 return Some(BreakpadFuncRecord::parse(line, self.lines.clone()));
555 }
556
557 self.finished = true;
558 None
559 }
560}
561
562#[derive(Clone, Debug, Default, Eq, PartialEq)]
573pub struct BreakpadLineRecord {
574 pub address: u64,
576 pub size: u64,
578 pub line: u64,
580 pub file_id: u64,
582}
583
584impl BreakpadLineRecord {
585 pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
587 let string = str::from_utf8(data)?;
588 Ok(parsing::line_record_final(string.trim())?)
589 }
590
591 pub fn filename<'d>(&self, file_map: &BreakpadFileMap<'d>) -> Option<&'d str> {
593 file_map.get(&self.file_id).cloned()
594 }
595
596 pub fn range(&self) -> Range<u64> {
598 self.address..self.address + self.size
599 }
600}
601
602#[derive(Clone, Debug)]
604pub struct BreakpadLineRecords<'d> {
605 lines: Lines<'d>,
606 finished: bool,
607}
608
609impl Iterator for BreakpadLineRecords<'_> {
610 type Item = Result<BreakpadLineRecord, BreakpadError>;
611
612 fn next(&mut self) -> Option<Self::Item> {
613 if self.finished {
614 return None;
615 }
616
617 for line in &mut self.lines {
618 if line.starts_with(b"FUNC ")
620 || line.starts_with(b"PUBLIC ")
621 || line.starts_with(b"STACK ")
622 {
623 break;
624 }
625
626 if line.is_empty() {
630 continue;
631 }
632
633 let record = match BreakpadLineRecord::parse(line) {
634 Ok(record) => record,
635 Err(error) => return Some(Err(error)),
636 };
637
638 if record.size > 0 {
640 return Some(Ok(record));
641 }
642 }
643
644 self.finished = true;
645 None
646 }
647}
648
649#[derive(Clone, Debug, Default, Eq, PartialEq)]
660pub struct BreakpadInlineRecord {
661 pub inline_depth: u64,
663 pub call_site_line: u64,
665 pub call_site_file_id: u64,
667 pub origin_id: u64,
669 pub address_ranges: Vec<BreakpadInlineAddressRange>,
672}
673
674impl BreakpadInlineRecord {
675 pub fn parse(data: &[u8]) -> Result<Self, BreakpadError> {
677 let string = str::from_utf8(data)?;
678 Ok(parsing::inline_record_final(string.trim())?)
679 }
680}
681
682#[derive(Clone, Debug, Default, Eq, PartialEq)]
685pub struct BreakpadInlineAddressRange {
686 pub address: u64,
688 pub size: u64,
690}
691
692impl BreakpadInlineAddressRange {
693 pub fn range(&self) -> Range<u64> {
695 self.address..self.address + self.size
696 }
697}
698
699#[derive(Clone, Debug, Eq, PartialEq, Default)]
701pub struct BreakpadStackCfiDeltaRecord<'d> {
702 pub address: u64,
704
705 pub rules: &'d str,
707}
708
709impl<'d> BreakpadStackCfiDeltaRecord<'d> {
710 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
712 let string = str::from_utf8(data)?;
713 Ok(parsing::stack_cfi_delta_record_final(string.trim())?)
714 }
715}
716
717#[derive(Clone, Debug, Default)]
722pub struct BreakpadStackCfiRecord<'d> {
723 pub start: u64,
725
726 pub size: u64,
728
729 pub init_rules: &'d str,
731
732 deltas: Lines<'d>,
734}
735
736impl<'d> BreakpadStackCfiRecord<'d> {
737 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
739 let string = str::from_utf8(data)?;
740 Ok(parsing::stack_cfi_record_final(string.trim())?)
741 }
742
743 pub fn deltas(&self) -> BreakpadStackCfiDeltaRecords<'d> {
745 BreakpadStackCfiDeltaRecords {
746 lines: self.deltas.clone(),
747 }
748 }
749
750 pub fn range(&self) -> Range<u64> {
752 self.start..self.start + self.size
753 }
754}
755
756impl PartialEq for BreakpadStackCfiRecord<'_> {
757 fn eq(&self, other: &Self) -> bool {
758 self.start == other.start && self.size == other.size && self.init_rules == other.init_rules
759 }
760}
761
762impl Eq for BreakpadStackCfiRecord<'_> {}
763
764#[derive(Clone, Debug, Default)]
767pub struct BreakpadStackCfiDeltaRecords<'d> {
768 lines: Lines<'d>,
769}
770
771impl<'d> Iterator for BreakpadStackCfiDeltaRecords<'d> {
772 type Item = Result<BreakpadStackCfiDeltaRecord<'d>, BreakpadError>;
773
774 fn next(&mut self) -> Option<Self::Item> {
775 if let Some(line) = self.lines.next() {
776 if line.starts_with(b"STACK CFI INIT") || !line.starts_with(b"STACK CFI") {
777 self.lines = Lines::default();
778 } else {
779 return Some(BreakpadStackCfiDeltaRecord::parse(line));
780 }
781 }
782
783 None
784 }
785}
786
787#[derive(Clone, Copy, Debug, Eq, PartialEq)]
791pub enum BreakpadStackWinRecordType {
792 Fpo = 0,
794
795 Trap = 1,
797
798 Tss = 2,
800
801 Standard = 3,
803
804 FrameData = 4,
806
807 Unknown = -1,
809}
810
811#[derive(Clone, Debug, Eq, PartialEq)]
817pub struct BreakpadStackWinRecord<'d> {
818 pub ty: BreakpadStackWinRecordType,
820
821 pub code_start: u32,
823
824 pub code_size: u32,
826
827 pub prolog_size: u16,
829
830 pub epilog_size: u16,
832
833 pub params_size: u32,
835
836 pub saved_regs_size: u16,
838
839 pub locals_size: u32,
841
842 pub max_stack_size: u32,
844
845 pub uses_base_pointer: bool,
849
850 pub program_string: Option<&'d str>,
854}
855
856impl<'d> BreakpadStackWinRecord<'d> {
857 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
859 let string = str::from_utf8(data)?;
860 Ok(parsing::stack_win_record_final(string.trim())?)
861 }
862
863 pub fn code_range(&self) -> Range<u32> {
865 self.code_start..self.code_start + self.code_size
866 }
867}
868
869#[derive(Clone, Debug, Eq, PartialEq)]
871pub enum BreakpadStackRecord<'d> {
872 Cfi(BreakpadStackCfiRecord<'d>),
874 Win(BreakpadStackWinRecord<'d>),
876}
877
878impl<'d> BreakpadStackRecord<'d> {
879 pub fn parse(data: &'d [u8]) -> Result<Self, BreakpadError> {
881 let string = str::from_utf8(data)?;
882 Ok(parsing::stack_record_final(string.trim())?)
883 }
884}
885
886#[derive(Clone, Debug)]
888pub struct BreakpadStackRecords<'d> {
889 lines: Lines<'d>,
890 finished: bool,
891}
892
893impl<'d> BreakpadStackRecords<'d> {
894 pub fn new(data: &'d [u8]) -> Self {
896 Self {
897 lines: Lines::new(data),
898 finished: false,
899 }
900 }
901}
902
903impl<'d> Iterator for BreakpadStackRecords<'d> {
904 type Item = Result<BreakpadStackRecord<'d>, BreakpadError>;
905
906 fn next(&mut self) -> Option<Self::Item> {
907 if self.finished {
908 return None;
909 }
910
911 while let Some(line) = self.lines.next() {
912 if line.starts_with(b"STACK WIN") {
913 return Some(BreakpadStackRecord::parse(line));
914 }
915
916 if line.starts_with(b"STACK CFI INIT") {
917 return Some(BreakpadStackCfiRecord::parse(line).map(|mut r| {
918 r.deltas = self.lines.clone();
919 BreakpadStackRecord::Cfi(r)
920 }));
921 }
922 }
923
924 self.finished = true;
925 None
926 }
927}
928
929pub struct BreakpadObject<'data> {
944 id: DebugId,
945 arch: Arch,
946 module: BreakpadModuleRecord<'data>,
947 data: &'data [u8],
948 max_inline_depth: Option<u32>,
949}
950
951impl<'data> BreakpadObject<'data> {
952 pub fn test(data: &[u8]) -> bool {
954 data.starts_with(b"MODULE ")
955 }
956
957 pub fn parse(data: &'data [u8]) -> Result<Self, BreakpadError> {
959 Self::parse_with_opts(data, Default::default())
960 }
961
962 fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result<Self, BreakpadError> {
964 let header = if data.len() > BREAKPAD_HEADER_CAP {
966 match str::from_utf8(&data[..BREAKPAD_HEADER_CAP]) {
967 Ok(_) => &data[..BREAKPAD_HEADER_CAP],
968 Err(e) => match e.error_len() {
969 None => &data[..e.valid_up_to()],
970 Some(_) => return Err(e.into()),
971 },
972 }
973 } else {
974 data
975 };
976
977 let first_line = header.split(|b| *b == b'\n').next().unwrap_or_default();
978 let module = BreakpadModuleRecord::parse(first_line)?;
979
980 Ok(BreakpadObject {
981 id: module
982 .id
983 .parse()
984 .map_err(|_| BreakpadErrorKind::InvalidModuleId)?,
985 arch: module
986 .arch
987 .parse()
988 .map_err(|_| BreakpadErrorKind::InvalidArchitecture)?,
989 module,
990 data,
991 max_inline_depth: opts.max_inline_depth,
992 })
993 }
994
995 pub fn file_format(&self) -> FileFormat {
997 FileFormat::Breakpad
998 }
999
1000 pub fn code_id(&self) -> Option<CodeId> {
1002 for result in self.info_records().flatten() {
1003 if let BreakpadInfoRecord::CodeId { code_id, .. } = result {
1004 if !code_id.is_empty() {
1005 return Some(CodeId::new(code_id.into()));
1006 }
1007 }
1008 }
1009
1010 None
1011 }
1012
1013 pub fn debug_id(&self) -> DebugId {
1015 self.id
1016 }
1017
1018 pub fn arch(&self) -> Arch {
1020 self.arch
1021 }
1022
1023 pub fn name(&self) -> &'data str {
1029 self.module.name
1030 }
1031
1032 pub fn kind(&self) -> ObjectKind {
1034 ObjectKind::Debug
1035 }
1036
1037 pub fn load_address(&self) -> u64 {
1042 0 }
1044
1045 pub fn has_symbols(&self) -> bool {
1047 self.public_records().next().is_some()
1048 }
1049
1050 pub fn symbols(&self) -> BreakpadSymbolIterator<'data> {
1052 BreakpadSymbolIterator {
1053 records: self.public_records(),
1054 }
1055 }
1056
1057 pub fn symbol_map(&self) -> SymbolMap<'data> {
1059 self.symbols().collect()
1060 }
1061
1062 pub fn has_debug_info(&self) -> bool {
1064 self.func_records().next().is_some()
1065 }
1066
1067 pub fn debug_session(&self) -> Result<BreakpadDebugSession<'data>, BreakpadError> {
1077 Ok(BreakpadDebugSession {
1078 file_map: self.file_map(),
1079 lines: Lines::new(self.data),
1080 max_inline_depth: self.max_inline_depth,
1081 })
1082 }
1083
1084 pub fn has_unwind_info(&self) -> bool {
1086 self.stack_records().next().is_some()
1087 }
1088
1089 pub fn has_sources(&self) -> bool {
1091 false
1092 }
1093
1094 pub fn is_malformed(&self) -> bool {
1096 false
1097 }
1098
1099 pub fn info_records(&self) -> BreakpadInfoRecords<'data> {
1101 BreakpadInfoRecords {
1102 lines: Lines::new(self.data),
1103 finished: false,
1104 }
1105 }
1106
1107 pub fn file_records(&self) -> BreakpadFileRecords<'data> {
1109 BreakpadFileRecords {
1110 lines: Lines::new(self.data),
1111 finished: false,
1112 }
1113 }
1114
1115 pub fn file_map(&self) -> BreakpadFileMap<'data> {
1117 self.file_records()
1118 .filter_map(Result::ok)
1119 .map(|file| (file.id, file.name))
1120 .collect()
1121 }
1122
1123 pub fn public_records(&self) -> BreakpadPublicRecords<'data> {
1125 BreakpadPublicRecords {
1126 lines: Lines::new(self.data),
1127 finished: false,
1128 }
1129 }
1130
1131 pub fn func_records(&self) -> BreakpadFuncRecords<'data> {
1133 BreakpadFuncRecords {
1134 lines: Lines::new(self.data),
1135 finished: false,
1136 }
1137 }
1138
1139 pub fn stack_records(&self) -> BreakpadStackRecords<'data> {
1141 BreakpadStackRecords {
1142 lines: Lines::new(self.data),
1143 finished: false,
1144 }
1145 }
1146
1147 pub fn data(&self) -> &'data [u8] {
1149 self.data
1150 }
1151}
1152
1153impl fmt::Debug for BreakpadObject<'_> {
1154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1155 f.debug_struct("BreakpadObject")
1156 .field("code_id", &self.code_id())
1157 .field("debug_id", &self.debug_id())
1158 .field("arch", &self.arch())
1159 .field("name", &self.name())
1160 .field("has_symbols", &self.has_symbols())
1161 .field("has_debug_info", &self.has_debug_info())
1162 .field("has_unwind_info", &self.has_unwind_info())
1163 .field("is_malformed", &self.is_malformed())
1164 .finish()
1165 }
1166}
1167
1168impl<'slf, 'data: 'slf> AsSelf<'slf> for BreakpadObject<'data> {
1169 type Ref = BreakpadObject<'slf>;
1170
1171 fn as_self(&'slf self) -> &'slf Self::Ref {
1172 self
1173 }
1174}
1175
1176impl<'data> Parse<'data> for BreakpadObject<'data> {
1177 type Error = BreakpadError;
1178
1179 fn test(data: &[u8]) -> bool {
1180 Self::test(data)
1181 }
1182
1183 fn parse_with_opts(data: &'data [u8], opts: ParseObjectOptions) -> Result<Self, Self::Error> {
1184 Self::parse_with_opts(data, opts)
1185 }
1186}
1187
1188impl<'data: 'object, 'object> ObjectLike<'data, 'object> for BreakpadObject<'data> {
1189 type Error = BreakpadError;
1190 type Session = BreakpadDebugSession<'data>;
1191 type SymbolIterator = BreakpadSymbolIterator<'data>;
1192
1193 fn file_format(&self) -> FileFormat {
1194 self.file_format()
1195 }
1196
1197 fn code_id(&self) -> Option<CodeId> {
1198 self.code_id()
1199 }
1200
1201 fn debug_id(&self) -> DebugId {
1202 self.debug_id()
1203 }
1204
1205 fn arch(&self) -> Arch {
1206 self.arch()
1207 }
1208
1209 fn kind(&self) -> ObjectKind {
1210 self.kind()
1211 }
1212
1213 fn load_address(&self) -> u64 {
1214 self.load_address()
1215 }
1216
1217 fn has_symbols(&self) -> bool {
1218 self.has_symbols()
1219 }
1220
1221 fn symbols(&self) -> Self::SymbolIterator {
1222 self.symbols()
1223 }
1224
1225 fn symbol_map(&self) -> SymbolMap<'data> {
1226 self.symbol_map()
1227 }
1228
1229 fn has_debug_info(&self) -> bool {
1230 self.has_debug_info()
1231 }
1232
1233 fn debug_session(&self) -> Result<Self::Session, Self::Error> {
1234 self.debug_session()
1235 }
1236
1237 fn has_unwind_info(&self) -> bool {
1238 self.has_unwind_info()
1239 }
1240
1241 fn has_sources(&self) -> bool {
1242 self.has_sources()
1243 }
1244
1245 fn is_malformed(&self) -> bool {
1246 self.is_malformed()
1247 }
1248}
1249
1250pub struct BreakpadSymbolIterator<'data> {
1254 records: BreakpadPublicRecords<'data>,
1255}
1256
1257impl<'data> Iterator for BreakpadSymbolIterator<'data> {
1258 type Item = Symbol<'data>;
1259
1260 fn next(&mut self) -> Option<Self::Item> {
1261 self.records.find_map(Result::ok).map(|record| Symbol {
1262 name: Some(Cow::Borrowed(record.name)),
1263 address: record.address,
1264 size: 0,
1265 })
1266 }
1267}
1268
1269pub struct BreakpadDebugSession<'data> {
1271 file_map: BreakpadFileMap<'data>,
1272 lines: Lines<'data>,
1273 max_inline_depth: Option<u32>,
1274}
1275
1276impl BreakpadDebugSession<'_> {
1277 pub fn functions(&self) -> BreakpadFunctionIterator<'_> {
1279 BreakpadFunctionIterator::new(&self.file_map, self.lines.clone(), self.max_inline_depth)
1280 }
1281
1282 pub fn files(&self) -> BreakpadFileIterator<'_> {
1284 BreakpadFileIterator {
1285 files: self.file_map.values(),
1286 }
1287 }
1288
1289 pub fn source_by_path(
1291 &self,
1292 _path: &str,
1293 ) -> Result<Option<SourceFileDescriptor<'_>>, BreakpadError> {
1294 Ok(None)
1295 }
1296}
1297
1298impl<'session> DebugSession<'session> for BreakpadDebugSession<'_> {
1299 type Error = BreakpadError;
1300 type FunctionIterator = BreakpadFunctionIterator<'session>;
1301 type FileIterator = BreakpadFileIterator<'session>;
1302
1303 fn functions(&'session self) -> Self::FunctionIterator {
1304 self.functions()
1305 }
1306
1307 fn files(&'session self) -> Self::FileIterator {
1308 self.files()
1309 }
1310
1311 fn source_by_path(&self, path: &str) -> Result<Option<SourceFileDescriptor<'_>>, Self::Error> {
1312 self.source_by_path(path)
1313 }
1314}
1315
1316pub struct BreakpadFileIterator<'s> {
1318 files: std::collections::btree_map::Values<'s, u64, &'s str>,
1319}
1320
1321impl<'s> Iterator for BreakpadFileIterator<'s> {
1322 type Item = Result<FileEntry<'s>, BreakpadError>;
1323
1324 fn next(&mut self) -> Option<Self::Item> {
1325 let path = self.files.next()?;
1326 Some(Ok(FileEntry::new(
1327 Cow::default(),
1328 FileInfo::from_path(path.as_bytes()),
1329 )))
1330 }
1331}
1332
1333pub struct BreakpadFunctionIterator<'s> {
1335 file_map: &'s BreakpadFileMap<'s>,
1336 next_line: Option<&'s [u8]>,
1337 inline_origin_map: BreakpadInlineOriginMap<'s>,
1338 lines: Lines<'s>,
1339 max_inline_depth: Option<u32>,
1340}
1341
1342impl<'s> BreakpadFunctionIterator<'s> {
1343 fn new(
1344 file_map: &'s BreakpadFileMap<'s>,
1345 mut lines: Lines<'s>,
1346 max_inline_depth: Option<u32>,
1347 ) -> Self {
1348 let next_line = lines.next();
1349 Self {
1350 file_map,
1351 next_line,
1352 inline_origin_map: Default::default(),
1353 lines,
1354 max_inline_depth,
1355 }
1356 }
1357}
1358
1359impl<'s> Iterator for BreakpadFunctionIterator<'s> {
1360 type Item = Result<Function<'s>, BreakpadError>;
1361
1362 fn next(&mut self) -> Option<Self::Item> {
1363 let line = loop {
1365 let line = self.next_line.take()?;
1366 if line.starts_with(b"FUNC ") {
1367 break line;
1368 }
1369
1370 if line.starts_with(b"STACK ") {
1373 return None;
1374 }
1375
1376 if line.starts_with(b"INLINE_ORIGIN ") {
1377 let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1378 Ok(record) => record,
1379 Err(e) => return Some(Err(e)),
1380 };
1381 self.inline_origin_map
1382 .insert(inline_origin_record.id, inline_origin_record.name);
1383 }
1384
1385 self.next_line = self.lines.next();
1386 };
1387
1388 let fun_record = match BreakpadFuncRecord::parse(line, Lines::new(&[])) {
1389 Ok(record) => record,
1390 Err(e) => return Some(Err(e)),
1391 };
1392
1393 let mut builder = FunctionBuilder::new(
1394 Name::new(fun_record.name, NameMangling::Unmangled, Language::Unknown),
1395 b"",
1396 fun_record.address,
1397 fun_record.size,
1398 )
1399 .max_inline_depth(self.max_inline_depth);
1400
1401 for line in self.lines.by_ref() {
1402 if line.starts_with(b"FUNC ")
1404 || line.starts_with(b"PUBLIC ")
1405 || line.starts_with(b"STACK ")
1406 {
1407 self.next_line = Some(line);
1408 break;
1409 }
1410
1411 if line.starts_with(b"INLINE_ORIGIN ") {
1412 let inline_origin_record = match BreakpadInlineOriginRecord::parse(line) {
1413 Ok(record) => record,
1414 Err(e) => return Some(Err(e)),
1415 };
1416 self.inline_origin_map
1417 .insert(inline_origin_record.id, inline_origin_record.name);
1418 continue;
1419 }
1420
1421 if line.starts_with(b"INLINE ") {
1422 let inline_record = match BreakpadInlineRecord::parse(line) {
1423 Ok(record) => record,
1424 Err(e) => return Some(Err(e)),
1425 };
1426
1427 let name = self
1428 .inline_origin_map
1429 .get(&inline_record.origin_id)
1430 .cloned()
1431 .unwrap_or_default();
1432
1433 for address_range in &inline_record.address_ranges {
1434 builder.add_inlinee(
1435 inline_record.inline_depth as u32,
1436 Name::new(name, NameMangling::Unmangled, Language::Unknown),
1437 address_range.address,
1438 address_range.size,
1439 FileInfo::from_path(
1440 self.file_map
1441 .get(&inline_record.call_site_file_id)
1442 .cloned()
1443 .unwrap_or_default()
1444 .as_bytes(),
1445 ),
1446 inline_record.call_site_line,
1447 );
1448 }
1449 continue;
1450 }
1451
1452 if line.is_empty() {
1456 continue;
1457 }
1458
1459 let line_record = match BreakpadLineRecord::parse(line) {
1460 Ok(line_record) => line_record,
1461 Err(e) => return Some(Err(e)),
1462 };
1463
1464 if line_record.size == 0 {
1466 continue;
1467 }
1468
1469 let filename = line_record.filename(self.file_map).unwrap_or_default();
1470
1471 builder.add_leaf_line(
1472 line_record.address,
1473 Some(line_record.size),
1474 FileInfo::from_path(filename.as_bytes()),
1475 line_record.line,
1476 );
1477 }
1478
1479 Some(Ok(builder.finish()))
1480 }
1481}
1482
1483impl std::iter::FusedIterator for BreakpadFunctionIterator<'_> {}
1484
1485mod parsing {
1486 use nom::branch::alt;
1487 use nom::bytes::complete::take_while;
1488 use nom::character::complete::{char, hex_digit1, multispace1};
1489 use nom::combinator::{cond, eof, map, rest};
1490 use nom::multi::many1;
1491 use nom::sequence::{pair, tuple};
1492 use nom::{IResult, Parser};
1493 use nom_supreme::error::ErrorTree;
1494 use nom_supreme::final_parser::{Location, RecreateContext};
1495 use nom_supreme::parser_ext::ParserExt;
1496 use nom_supreme::tag::complete::tag;
1497
1498 use super::*;
1499
1500 type ParseResult<'a, T> = IResult<&'a str, T, ErrorTree<&'a str>>;
1501 pub type ParseBreakpadError = ErrorTree<ErrorLine>;
1502
1503 #[derive(Clone, Debug, PartialEq, Eq)]
1526 pub struct ErrorLine {
1527 pub line: String,
1529
1530 pub column: usize,
1532 }
1533
1534 impl<'a> RecreateContext<&'a str> for ErrorLine {
1535 fn recreate_context(original_input: &'a str, tail: &'a str) -> Self {
1536 let Location { column, .. } = Location::recreate_context(original_input, tail);
1537 Self {
1538 line: original_input.to_string(),
1539 column,
1540 }
1541 }
1542 }
1543
1544 impl fmt::Display for ErrorLine {
1545 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1546 if f.alternate() {
1547 writeln!(f)?;
1548 }
1549
1550 write!(f, "\"{}\"", self.line)?;
1551
1552 if f.alternate() {
1553 writeln!(f, "\n{:>width$}", "^", width = self.column + 1)?;
1554 } else {
1555 write!(f, ", column {}", self.column)?;
1556 }
1557
1558 Ok(())
1559 }
1560 }
1561
1562 macro_rules! num_dec {
1564 ($ty:ty) => {
1565 nom::character::complete::digit1.map_res(|s: &str| s.parse::<$ty>())
1566 };
1567 }
1568
1569 macro_rules! num_hex {
1571 ($ty:ty) => {
1572 nom::character::complete::hex_digit1.map_res(|n| <$ty>::from_str_radix(n, 16))
1573 };
1574 }
1575
1576 fn non_whitespace(input: &str) -> ParseResult<'_, &str> {
1578 take_while(|c: char| !c.is_whitespace())(input)
1579 }
1580
1581 fn name(input: &str) -> ParseResult<'_, &str> {
1585 rest.map(|name: &str| if name.is_empty() { UNKNOWN_NAME } else { name })
1586 .parse(input)
1587 }
1588
1589 fn multiple(input: &str) -> ParseResult<'_, bool> {
1593 let (mut input, multiple) = char('m').opt().parse(input)?;
1594 let multiple = multiple.is_some();
1595 if multiple {
1596 input = multispace1(input)?.0;
1597 }
1598 Ok((input, multiple))
1599 }
1600
1601 fn line_num(input: &str) -> ParseResult<'_, u64> {
1603 pair(char('-').opt(), num_dec!(u64))
1604 .map(|(sign, num)| if sign.is_some() { 0 } else { num })
1605 .parse(input)
1606 }
1607
1608 fn stack_win_record_type(input: &str) -> ParseResult<'_, BreakpadStackWinRecordType> {
1610 alt((
1611 char('0').value(BreakpadStackWinRecordType::Fpo),
1612 char('1').value(BreakpadStackWinRecordType::Trap),
1613 char('2').value(BreakpadStackWinRecordType::Tss),
1614 char('3').value(BreakpadStackWinRecordType::Standard),
1615 char('4').value(BreakpadStackWinRecordType::FrameData),
1616 non_whitespace.value(BreakpadStackWinRecordType::Unknown),
1617 ))(input)
1618 }
1619
1620 fn module_record(input: &str) -> ParseResult<'_, BreakpadModuleRecord<'_>> {
1624 let (input, _) = tag("MODULE")
1625 .terminated(multispace1)
1626 .context("module record prefix")
1627 .parse(input)?;
1628 let (input, (os, arch, id, name)) = tuple((
1629 non_whitespace.terminated(multispace1).context("os"),
1630 non_whitespace.terminated(multispace1).context("arch"),
1631 hex_digit1
1632 .terminated(multispace1.or(eof))
1633 .context("module id"),
1634 name.context("module name"),
1635 ))
1636 .cut()
1637 .context("module record body")
1638 .parse(input)?;
1639
1640 Ok((input, BreakpadModuleRecord { os, arch, id, name }))
1641 }
1642
1643 pub fn module_record_final(
1648 input: &str,
1649 ) -> Result<BreakpadModuleRecord<'_>, ErrorTree<ErrorLine>> {
1650 nom_supreme::final_parser::final_parser(module_record)(input)
1651 }
1652
1653 fn info_code_id_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1657 let (input, _) = tag("CODE_ID")
1658 .terminated(multispace1)
1659 .context("info code_id record prefix")
1660 .parse(input)?;
1661
1662 let (input, (code_id, code_file)) = pair(
1663 hex_digit1
1664 .terminated(multispace1.or(eof))
1665 .context("code id"),
1666 name.context("file name"),
1667 )
1668 .cut()
1669 .context("info code_id record body")
1670 .parse(input)?;
1671
1672 Ok((input, BreakpadInfoRecord::CodeId { code_id, code_file }))
1673 }
1674
1675 fn info_other_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1679 let (input, (scope, info)) = pair(
1680 non_whitespace
1681 .terminated(multispace1.or(eof))
1682 .context("info scope"),
1683 rest,
1684 )
1685 .cut()
1686 .context("info other record body")
1687 .parse(input)?;
1688
1689 Ok((input, BreakpadInfoRecord::Other { scope, info }))
1690 }
1691
1692 fn info_record(input: &str) -> ParseResult<'_, BreakpadInfoRecord<'_>> {
1696 let (input, _) = tag("INFO")
1697 .terminated(multispace1)
1698 .context("info record prefix")
1699 .parse(input)?;
1700
1701 info_code_id_record
1702 .or(info_other_record)
1703 .cut()
1704 .context("info record body")
1705 .parse(input)
1706 }
1707
1708 pub fn info_record_final(input: &str) -> Result<BreakpadInfoRecord<'_>, ErrorTree<ErrorLine>> {
1713 nom_supreme::final_parser::final_parser(info_record)(input)
1714 }
1715
1716 fn file_record(input: &str) -> ParseResult<'_, BreakpadFileRecord<'_>> {
1720 let (input, _) = tag("FILE")
1721 .terminated(multispace1)
1722 .context("file record prefix")
1723 .parse(input)?;
1724
1725 let (input, (id, name)) = pair(
1726 num_dec!(u64)
1727 .terminated(multispace1.or(eof))
1728 .context("file id"),
1729 rest.context("file name"),
1730 )
1731 .cut()
1732 .context("file record body")
1733 .parse(input)?;
1734
1735 Ok((input, BreakpadFileRecord { id, name }))
1736 }
1737
1738 pub fn file_record_final(input: &str) -> Result<BreakpadFileRecord<'_>, ErrorTree<ErrorLine>> {
1743 nom_supreme::final_parser::final_parser(file_record)(input)
1744 }
1745
1746 fn inline_origin_record(input: &str) -> ParseResult<'_, BreakpadInlineOriginRecord<'_>> {
1750 let (input, _) = tag("INLINE_ORIGIN")
1751 .terminated(multispace1)
1752 .context("inline origin record prefix")
1753 .parse(input)?;
1754
1755 let (input, (id, name)) = pair(
1756 num_dec!(u64)
1757 .terminated(multispace1)
1758 .context("inline origin id"),
1759 rest.context("inline origin name"),
1760 )
1761 .cut()
1762 .context("inline origin record body")
1763 .parse(input)?;
1764
1765 Ok((input, BreakpadInlineOriginRecord { id, name }))
1766 }
1767
1768 pub fn inline_origin_record_final(
1773 input: &str,
1774 ) -> Result<BreakpadInlineOriginRecord<'_>, ErrorTree<ErrorLine>> {
1775 nom_supreme::final_parser::final_parser(inline_origin_record)(input)
1776 }
1777
1778 fn public_record(input: &str) -> ParseResult<'_, BreakpadPublicRecord<'_>> {
1782 let (input, _) = tag("PUBLIC")
1783 .terminated(multispace1)
1784 .context("public record prefix")
1785 .parse(input)?;
1786
1787 let (input, (multiple, address, parameter_size, name)) = tuple((
1788 multiple.context("multiple flag"),
1789 num_hex!(u64).terminated(multispace1).context("address"),
1790 num_hex!(u64)
1791 .terminated(multispace1.or(eof))
1792 .context("param size"),
1793 name.context("symbol name"),
1794 ))
1795 .cut()
1796 .context("public record body")
1797 .parse(input)?;
1798
1799 Ok((
1800 input,
1801 BreakpadPublicRecord {
1802 multiple,
1803 address,
1804 parameter_size,
1805 name,
1806 },
1807 ))
1808 }
1809
1810 pub fn public_record_final(
1815 input: &str,
1816 ) -> Result<BreakpadPublicRecord<'_>, ErrorTree<ErrorLine>> {
1817 nom_supreme::final_parser::final_parser(public_record)(input)
1818 }
1819
1820 fn func_record(input: &str) -> ParseResult<'_, BreakpadFuncRecord<'_>> {
1824 let (input, _) = tag("FUNC")
1825 .terminated(multispace1)
1826 .context("func record prefix")
1827 .parse(input)?;
1828
1829 let (input, (multiple, address, size, parameter_size, name)) = tuple((
1830 multiple.context("multiple flag"),
1831 num_hex!(u64).terminated(multispace1).context("address"),
1832 num_hex!(u64).terminated(multispace1).context("size"),
1833 num_hex!(u64)
1834 .terminated(multispace1.or(eof))
1835 .context("param size"),
1836 name.context("symbol name"),
1837 ))
1838 .cut()
1839 .context("func record body")
1840 .parse(input)?;
1841
1842 Ok((
1843 input,
1844 BreakpadFuncRecord {
1845 multiple,
1846 address,
1847 size,
1848 parameter_size,
1849 name,
1850 lines: Lines::default(),
1851 },
1852 ))
1853 }
1854
1855 pub fn func_record_final(input: &str) -> Result<BreakpadFuncRecord<'_>, ErrorTree<ErrorLine>> {
1860 nom_supreme::final_parser::final_parser(func_record)(input)
1861 }
1862
1863 fn line_record(input: &str) -> ParseResult<'_, BreakpadLineRecord> {
1867 let (input, (address, size, line, file_id)) = tuple((
1868 num_hex!(u64).terminated(multispace1).context("address"),
1869 num_hex!(u64).terminated(multispace1).context("size"),
1870 line_num.terminated(multispace1).context("line number"),
1871 num_dec!(u64).context("file id"),
1872 ))
1873 .context("line record")
1874 .parse(input)?;
1875
1876 Ok((
1877 input,
1878 BreakpadLineRecord {
1879 address,
1880 size,
1881 line,
1882 file_id,
1883 },
1884 ))
1885 }
1886
1887 pub fn line_record_final(input: &str) -> Result<BreakpadLineRecord, ErrorTree<ErrorLine>> {
1892 nom_supreme::final_parser::final_parser(line_record)(input)
1893 }
1894
1895 fn inline_record(input: &str) -> ParseResult<'_, BreakpadInlineRecord> {
1899 let (input, _) = tag("INLINE")
1900 .terminated(multispace1)
1901 .context("inline record prefix")
1902 .parse(input)?;
1903
1904 let (input, (inline_depth, call_site_line, call_site_file_id, origin_id)) = tuple((
1905 num_dec!(u64)
1906 .terminated(multispace1)
1907 .context("inline_nest_level"),
1908 num_dec!(u64)
1909 .terminated(multispace1)
1910 .context("call_site_line"),
1911 num_dec!(u64)
1912 .terminated(multispace1)
1913 .context("call_site_file_id"),
1914 num_dec!(u64).terminated(multispace1).context("origin_id"),
1915 ))
1916 .cut()
1917 .context("func record body")
1918 .parse(input)?;
1919
1920 let (input, address_ranges) = many1(map(
1921 pair(
1922 num_hex!(u64).terminated(multispace1).context("address"),
1923 num_hex!(u64)
1924 .terminated(multispace1.or(eof))
1925 .context("size"),
1926 ),
1927 |(address, size)| BreakpadInlineAddressRange { address, size },
1928 ))
1929 .cut()
1930 .context("inline record body")
1931 .parse(input)?;
1932
1933 Ok((
1934 input,
1935 BreakpadInlineRecord {
1936 inline_depth,
1937 call_site_line,
1938 call_site_file_id,
1939 origin_id,
1940 address_ranges,
1941 },
1942 ))
1943 }
1944
1945 pub fn inline_record_final(input: &str) -> Result<BreakpadInlineRecord, ErrorTree<ErrorLine>> {
1950 nom_supreme::final_parser::final_parser(inline_record)(input)
1951 }
1952
1953 fn stack_cfi_delta_record(input: &str) -> ParseResult<'_, BreakpadStackCfiDeltaRecord<'_>> {
1957 let (input, _) = tag("STACK CFI")
1958 .terminated(multispace1)
1959 .context("stack cfi prefix")
1960 .parse(input)?;
1961
1962 let (input, (address, rules)) = pair(
1963 num_hex!(u64).terminated(multispace1).context("address"),
1964 rest.context("rules"),
1965 )
1966 .cut()
1967 .context("stack cfi delta record body")
1968 .parse(input)?;
1969
1970 Ok((input, BreakpadStackCfiDeltaRecord { address, rules }))
1971 }
1972
1973 pub fn stack_cfi_delta_record_final(
1978 input: &str,
1979 ) -> Result<BreakpadStackCfiDeltaRecord<'_>, ErrorTree<ErrorLine>> {
1980 nom_supreme::final_parser::final_parser(stack_cfi_delta_record)(input)
1981 }
1982
1983 fn stack_cfi_record(input: &str) -> ParseResult<'_, BreakpadStackCfiRecord<'_>> {
1987 let (input, _) = tag("STACK CFI INIT")
1988 .terminated(multispace1)
1989 .context("stack cfi init prefix")
1990 .parse(input)?;
1991
1992 let (input, (start, size, init_rules)) = tuple((
1993 num_hex!(u64).terminated(multispace1).context("start"),
1994 num_hex!(u64).terminated(multispace1).context("size"),
1995 rest.context("rules"),
1996 ))
1997 .cut()
1998 .context("stack cfi record body")
1999 .parse(input)?;
2000
2001 Ok((
2002 input,
2003 BreakpadStackCfiRecord {
2004 start,
2005 size,
2006 init_rules,
2007 deltas: Lines::default(),
2008 },
2009 ))
2010 }
2011
2012 pub fn stack_cfi_record_final(
2017 input: &str,
2018 ) -> Result<BreakpadStackCfiRecord<'_>, ErrorTree<ErrorLine>> {
2019 nom_supreme::final_parser::final_parser(stack_cfi_record)(input)
2020 }
2021
2022 fn stack_win_record(input: &str) -> ParseResult<'_, BreakpadStackWinRecord<'_>> {
2027 let (input, _) = tag("STACK WIN")
2028 .terminated(multispace1)
2029 .context("stack win prefix")
2030 .parse(input)?;
2031
2032 let (
2033 input,
2034 (
2035 ty,
2036 code_start,
2037 code_size,
2038 prolog_size,
2039 epilog_size,
2040 params_size,
2041 saved_regs_size,
2042 locals_size,
2043 max_stack_size,
2044 has_program_string,
2045 ),
2046 ) = tuple((
2047 stack_win_record_type
2048 .terminated(multispace1)
2049 .context("record type"),
2050 num_hex!(u32).terminated(multispace1).context("code start"),
2051 num_hex!(u32).terminated(multispace1).context("code size"),
2052 num_hex!(u16).terminated(multispace1).context("prolog size"),
2053 num_hex!(u16).terminated(multispace1).context("epilog size"),
2054 num_hex!(u32).terminated(multispace1).context("params size"),
2055 num_hex!(u16)
2056 .terminated(multispace1)
2057 .context("saved regs size"),
2058 num_hex!(u32).terminated(multispace1).context("locals size"),
2059 num_hex!(u32)
2060 .terminated(multispace1)
2061 .context("max stack size"),
2062 non_whitespace
2063 .map(|s| s != "0")
2064 .terminated(multispace1)
2065 .context("has_program_string"),
2066 ))
2067 .cut()
2068 .context("stack win record body")
2069 .parse(input)?;
2070
2071 let (input, program_string) =
2072 cond(has_program_string, rest.context("program string"))(input)?;
2073 let (input, uses_base_pointer) =
2074 cond(!has_program_string, non_whitespace.map(|s| s != "0"))
2075 .map(|o| o.unwrap_or(false))
2076 .parse(input)?;
2077
2078 Ok((
2079 input,
2080 BreakpadStackWinRecord {
2081 ty,
2082 code_start,
2083 code_size,
2084 prolog_size,
2085 epilog_size,
2086 params_size,
2087 saved_regs_size,
2088 locals_size,
2089 max_stack_size,
2090 uses_base_pointer,
2091 program_string,
2092 },
2093 ))
2094 }
2095
2096 pub fn stack_win_record_final(
2102 input: &str,
2103 ) -> Result<BreakpadStackWinRecord<'_>, ErrorTree<ErrorLine>> {
2104 nom_supreme::final_parser::final_parser(stack_win_record)(input)
2105 }
2106
2107 pub fn stack_record_final(input: &str) -> Result<BreakpadStackRecord<'_>, ParseBreakpadError> {
2112 nom_supreme::final_parser::final_parser(alt((
2113 stack_cfi_record.map(BreakpadStackRecord::Cfi),
2114 stack_win_record.map(BreakpadStackRecord::Win),
2115 )))(input)
2116 }
2117}
2118#[cfg(test)]
2119mod tests {
2120 use super::*;
2121
2122 #[test]
2123 fn test_parse_module_record() -> Result<(), BreakpadError> {
2124 let string = b"MODULE Linux x86_64 492E2DD23CC306CA9C494EEF1533A3810 crash";
2125 let record = BreakpadModuleRecord::parse(string)?;
2126
2127 insta::assert_debug_snapshot!(record, @r#"
2128 BreakpadModuleRecord {
2129 os: "Linux",
2130 arch: "x86_64",
2131 id: "492E2DD23CC306CA9C494EEF1533A3810",
2132 name: "crash",
2133 }
2134 "#);
2135
2136 Ok(())
2137 }
2138
2139 #[test]
2140 fn test_parse_module_record_short_id() -> Result<(), BreakpadError> {
2141 let string = b"MODULE Linux x86_64 6216C672A8D33EC9CF4A1BAB8B29D00E libdispatch.so";
2143 let record = BreakpadModuleRecord::parse(string)?;
2144
2145 insta::assert_debug_snapshot!(record, @r#"
2146 BreakpadModuleRecord {
2147 os: "Linux",
2148 arch: "x86_64",
2149 id: "6216C672A8D33EC9CF4A1BAB8B29D00E",
2150 name: "libdispatch.so",
2151 }
2152 "#);
2153
2154 Ok(())
2155 }
2156
2157 #[test]
2158 fn test_parse_file_record() -> Result<(), BreakpadError> {
2159 let string = b"FILE 37 /usr/include/libkern/i386/_OSByteOrder.h";
2160 let record = BreakpadFileRecord::parse(string)?;
2161
2162 insta::assert_debug_snapshot!(record, @r#"
2163 BreakpadFileRecord {
2164 id: 37,
2165 name: "/usr/include/libkern/i386/_OSByteOrder.h",
2166 }
2167 "#);
2168
2169 Ok(())
2170 }
2171
2172 #[test]
2173 fn test_parse_file_record_space() -> Result<(), BreakpadError> {
2174 let string = b"FILE 38 /usr/local/src/filename with spaces.c";
2175 let record = BreakpadFileRecord::parse(string)?;
2176
2177 insta::assert_debug_snapshot!(record, @r#"
2178 BreakpadFileRecord {
2179 id: 38,
2180 name: "/usr/local/src/filename with spaces.c",
2181 }
2182 "#);
2183
2184 Ok(())
2185 }
2186
2187 #[test]
2188 fn test_parse_inline_origin_record() -> Result<(), BreakpadError> {
2189 let string = b"INLINE_ORIGIN 3529 LZ4F_initStream";
2190 let record = BreakpadInlineOriginRecord::parse(string)?;
2191
2192 insta::assert_debug_snapshot!(record, @r#"
2193 BreakpadInlineOriginRecord {
2194 id: 3529,
2195 name: "LZ4F_initStream",
2196 }
2197 "#);
2198
2199 Ok(())
2200 }
2201
2202 #[test]
2203 fn test_parse_inline_origin_record_space() -> Result<(), BreakpadError> {
2204 let string =
2205 b"INLINE_ORIGIN 3576 unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)";
2206 let record = BreakpadInlineOriginRecord::parse(string)?;
2207
2208 insta::assert_debug_snapshot!(record, @r#"
2209 BreakpadInlineOriginRecord {
2210 id: 3576,
2211 name: "unsigned int mozilla::AddToHash<char, 0>(unsigned int, char)",
2212 }
2213 "#);
2214
2215 Ok(())
2216 }
2217
2218 #[test]
2219 fn test_parse_func_record() -> Result<(), BreakpadError> {
2220 let string = b"FUNC 1730 1a 0 <name omitted>";
2222 let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2223
2224 insta::assert_debug_snapshot!(record, @r#"
2225 BreakpadFuncRecord {
2226 multiple: false,
2227 address: 5936,
2228 size: 26,
2229 parameter_size: 0,
2230 name: "<name omitted>",
2231 }
2232 "#);
2233
2234 Ok(())
2235 }
2236
2237 #[test]
2238 fn test_parse_func_record_multiple() -> Result<(), BreakpadError> {
2239 let string = b"FUNC m 1730 1a 0 <name omitted>";
2240 let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2241
2242 insta::assert_debug_snapshot!(record, @r#"
2243 BreakpadFuncRecord {
2244 multiple: true,
2245 address: 5936,
2246 size: 26,
2247 parameter_size: 0,
2248 name: "<name omitted>",
2249 }
2250 "#);
2251
2252 Ok(())
2253 }
2254
2255 #[test]
2256 fn test_parse_func_record_no_name() -> Result<(), BreakpadError> {
2257 let string = b"FUNC 0 f 0";
2258 let record = BreakpadFuncRecord::parse(string, Lines::default())?;
2259
2260 insta::assert_debug_snapshot!(record, @r#"
2261 BreakpadFuncRecord {
2262 multiple: false,
2263 address: 0,
2264 size: 15,
2265 parameter_size: 0,
2266 name: "<unknown>",
2267 }
2268 "#);
2269
2270 Ok(())
2271 }
2272
2273 #[test]
2274 fn test_parse_line_record() -> Result<(), BreakpadError> {
2275 let string = b"1730 6 93 20";
2276 let record = BreakpadLineRecord::parse(string)?;
2277
2278 insta::assert_debug_snapshot!(record, @r"
2279 BreakpadLineRecord {
2280 address: 5936,
2281 size: 6,
2282 line: 93,
2283 file_id: 20,
2284 }
2285 ");
2286
2287 Ok(())
2288 }
2289
2290 #[test]
2291 fn test_parse_line_record_negative_line() -> Result<(), BreakpadError> {
2292 let string = b"e0fd10 5 -376 2225";
2293 let record = BreakpadLineRecord::parse(string)?;
2294
2295 insta::assert_debug_snapshot!(record, @r"
2296 BreakpadLineRecord {
2297 address: 14744848,
2298 size: 5,
2299 line: 0,
2300 file_id: 2225,
2301 }
2302 ");
2303
2304 Ok(())
2305 }
2306
2307 #[test]
2308 fn test_parse_line_record_whitespace() -> Result<(), BreakpadError> {
2309 let string = b" 1000 1c 2972 2
2310";
2311 let record = BreakpadLineRecord::parse(string)?;
2312
2313 insta::assert_debug_snapshot!(
2314 record, @r"
2315 BreakpadLineRecord {
2316 address: 4096,
2317 size: 28,
2318 line: 2972,
2319 file_id: 2,
2320 }
2321 ");
2322
2323 Ok(())
2324 }
2325
2326 #[test]
2327 fn test_parse_public_record() -> Result<(), BreakpadError> {
2328 let string = b"PUBLIC 5180 0 __clang_call_terminate";
2329 let record = BreakpadPublicRecord::parse(string)?;
2330
2331 insta::assert_debug_snapshot!(record, @r#"
2332 BreakpadPublicRecord {
2333 multiple: false,
2334 address: 20864,
2335 parameter_size: 0,
2336 name: "__clang_call_terminate",
2337 }
2338 "#);
2339
2340 Ok(())
2341 }
2342
2343 #[test]
2344 fn test_parse_public_record_multiple() -> Result<(), BreakpadError> {
2345 let string = b"PUBLIC m 5180 0 __clang_call_terminate";
2346 let record = BreakpadPublicRecord::parse(string)?;
2347
2348 insta::assert_debug_snapshot!(record, @r#"
2349 BreakpadPublicRecord {
2350 multiple: true,
2351 address: 20864,
2352 parameter_size: 0,
2353 name: "__clang_call_terminate",
2354 }
2355 "#);
2356
2357 Ok(())
2358 }
2359
2360 #[test]
2361 fn test_parse_public_record_no_name() -> Result<(), BreakpadError> {
2362 let string = b"PUBLIC 5180 0";
2363 let record = BreakpadPublicRecord::parse(string)?;
2364
2365 insta::assert_debug_snapshot!(record, @r#"
2366 BreakpadPublicRecord {
2367 multiple: false,
2368 address: 20864,
2369 parameter_size: 0,
2370 name: "<unknown>",
2371 }
2372 "#);
2373
2374 Ok(())
2375 }
2376
2377 #[test]
2378 fn test_parse_inline_record() -> Result<(), BreakpadError> {
2379 let string = b"INLINE 0 3082 52 1410 49200 10";
2380 let record = BreakpadInlineRecord::parse(string)?;
2381
2382 insta::assert_debug_snapshot!(record, @r"
2383 BreakpadInlineRecord {
2384 inline_depth: 0,
2385 call_site_line: 3082,
2386 call_site_file_id: 52,
2387 origin_id: 1410,
2388 address_ranges: [
2389 BreakpadInlineAddressRange {
2390 address: 299520,
2391 size: 16,
2392 },
2393 ],
2394 }
2395 ");
2396
2397 Ok(())
2398 }
2399
2400 #[test]
2401 fn test_parse_inline_record_multiple() -> Result<(), BreakpadError> {
2402 let string = b"INLINE 6 642 8 207 8b110 18 8b154 18";
2403 let record = BreakpadInlineRecord::parse(string)?;
2404
2405 insta::assert_debug_snapshot!(record, @r"
2406 BreakpadInlineRecord {
2407 inline_depth: 6,
2408 call_site_line: 642,
2409 call_site_file_id: 8,
2410 origin_id: 207,
2411 address_ranges: [
2412 BreakpadInlineAddressRange {
2413 address: 569616,
2414 size: 24,
2415 },
2416 BreakpadInlineAddressRange {
2417 address: 569684,
2418 size: 24,
2419 },
2420 ],
2421 }
2422 ");
2423
2424 Ok(())
2425 }
2426
2427 #[test]
2428 fn test_parse_inline_record_err_missing_address_range() {
2429 let string = b"INLINE 6 642 8 207";
2430 let record = BreakpadInlineRecord::parse(string);
2431 assert!(record.is_err());
2432 }
2433
2434 #[test]
2435 fn test_parse_stack_cfi_init_record() -> Result<(), BreakpadError> {
2436 let string = b"STACK CFI INIT 1880 2d .cfa: $rsp 8 + .ra: .cfa -8 + ^";
2437 let record = BreakpadStackRecord::parse(string)?;
2438
2439 insta::assert_debug_snapshot!(record, @r#"
2440 Cfi(
2441 BreakpadStackCfiRecord {
2442 start: 6272,
2443 size: 45,
2444 init_rules: ".cfa: $rsp 8 + .ra: .cfa -8 + ^",
2445 deltas: Lines(
2446 LineOffsets {
2447 data: [],
2448 finished: true,
2449 index: 0,
2450 },
2451 ),
2452 },
2453 )
2454 "#);
2455
2456 Ok(())
2457 }
2458
2459 #[test]
2460 fn test_parse_stack_win_record() -> Result<(), BreakpadError> {
2461 let string =
2462 b"STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =";
2463 let record = BreakpadStackRecord::parse(string)?;
2464
2465 insta::assert_debug_snapshot!(record, @r#"
2466 Win(
2467 BreakpadStackWinRecord {
2468 ty: FrameData,
2469 code_start: 14106,
2470 code_size: 12,
2471 prolog_size: 0,
2472 epilog_size: 0,
2473 params_size: 0,
2474 saved_regs_size: 0,
2475 locals_size: 0,
2476 max_stack_size: 0,
2477 uses_base_pointer: false,
2478 program_string: Some(
2479 "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2480 ),
2481 },
2482 )
2483 "#);
2484
2485 Ok(())
2486 }
2487
2488 #[test]
2489 fn test_parse_stack_win_record_type_3() -> Result<(), BreakpadError> {
2490 let string = b"STACK WIN 3 8a10b ec b 0 c c 4 0 0 1";
2491 let record = BreakpadStackWinRecord::parse(string)?;
2492
2493 insta::assert_debug_snapshot!(record, @r"
2494 BreakpadStackWinRecord {
2495 ty: Standard,
2496 code_start: 565515,
2497 code_size: 236,
2498 prolog_size: 11,
2499 epilog_size: 0,
2500 params_size: 12,
2501 saved_regs_size: 12,
2502 locals_size: 4,
2503 max_stack_size: 0,
2504 uses_base_pointer: true,
2505 program_string: None,
2506 }
2507 ");
2508
2509 Ok(())
2510 }
2511
2512 #[test]
2513 fn test_parse_stack_win_whitespace() -> Result<(), BreakpadError> {
2514 let string =
2515 b" STACK WIN 4 371a c 0 0 0 0 0 0 1 $T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =
2516 ";
2517 let record = BreakpadStackRecord::parse(string)?;
2518
2519 insta::assert_debug_snapshot!(record, @r#"
2520 Win(
2521 BreakpadStackWinRecord {
2522 ty: FrameData,
2523 code_start: 14106,
2524 code_size: 12,
2525 prolog_size: 0,
2526 epilog_size: 0,
2527 params_size: 0,
2528 saved_regs_size: 0,
2529 locals_size: 0,
2530 max_stack_size: 0,
2531 uses_base_pointer: false,
2532 program_string: Some(
2533 "$T0 .raSearch = $eip $T0 ^ = $esp $T0 4 + =",
2534 ),
2535 },
2536 )
2537 "#);
2538 Ok(())
2539 }
2540
2541 use similar_asserts::assert_eq;
2542
2543 #[test]
2544 fn test_lineoffsets_fused() {
2545 let data = b"";
2546 let mut offsets = LineOffsets::new(data);
2547
2548 offsets.next();
2549 assert_eq!(None, offsets.next());
2550 assert_eq!(None, offsets.next());
2551 assert_eq!(None, offsets.next());
2552 }
2553
2554 macro_rules! test_lineoffsets {
2555 ($name:ident, $data:literal, $( ($index:literal, $line:literal) ),*) => {
2556 #[test]
2557 fn $name() {
2558 let mut offsets = LineOffsets::new($data);
2559
2560 $(
2561 assert_eq!(Some(($index, &$line[..])), offsets.next());
2562 )*
2563 assert_eq!(None, offsets.next());
2564 }
2565 };
2566 }
2567
2568 test_lineoffsets!(test_lineoffsets_empty, b"", (0, b""));
2569 test_lineoffsets!(test_lineoffsets_oneline, b"hello", (0, b"hello"));
2570 test_lineoffsets!(
2571 test_lineoffsets_trailing_n,
2572 b"hello\n",
2573 (0, b"hello"),
2574 (6, b"")
2575 );
2576 test_lineoffsets!(
2577 test_lineoffsets_trailing_rn,
2578 b"hello\r\n",
2579 (0, b"hello"),
2580 (7, b"")
2581 );
2582 test_lineoffsets!(
2583 test_lineoffsets_n,
2584 b"hello\nworld\nyo",
2585 (0, b"hello"),
2586 (6, b"world"),
2587 (12, b"yo")
2588 );
2589 test_lineoffsets!(
2590 test_lineoffsets_rn,
2591 b"hello\r\nworld\r\nyo",
2592 (0, b"hello"),
2593 (7, b"world"),
2594 (14, b"yo")
2595 );
2596 test_lineoffsets!(
2597 test_lineoffsets_mixed,
2598 b"hello\r\nworld\nyo",
2599 (0, b"hello"),
2600 (7, b"world"),
2601 (13, b"yo")
2602 );
2603}