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