1use std::borrow::{Borrow, Cow};
7use std::cell::RefCell;
8use std::collections::{HashMap, HashSet};
9use std::io;
10use std::io::prelude::*;
11use std::time::SystemTime;
12
13use crate::op_analysis::{InstructionPointerUpdate, InstructionProperties, MemoryAccessList};
14use minidump::system_info::PointerWidth;
15use minidump::*;
16use minidump_common::utils::basename;
17use minidump_unwind::{CallStack, CallStackInfo, SymbolStats, SystemInfo};
18use serde_json::json;
19
20#[derive(Default)]
21struct SerializationContext {
22 pub pointer_width: Option<PointerWidth>,
23}
24
25std::thread_local! {
26 static SERIALIZATION_CONTEXT: RefCell<SerializationContext> = Default::default();
27}
28
29#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
30#[serde(into = "String")]
31pub struct Address(pub u64);
32
33impl From<u64> for Address {
34 fn from(v: u64) -> Self {
35 Address(v)
36 }
37}
38
39impl From<Address> for u64 {
40 fn from(a: Address) -> Self {
41 a.0
42 }
43}
44
45impl From<Address> for String {
46 fn from(a: Address) -> Self {
47 a.to_string()
48 }
49}
50
51impl std::ops::Deref for Address {
52 type Target = u64;
53
54 fn deref(&self) -> &Self::Target {
55 &self.0
56 }
57}
58
59impl std::ops::DerefMut for Address {
60 fn deref_mut(&mut self) -> &mut Self::Target {
61 &mut self.0
62 }
63}
64
65impl std::fmt::Display for Address {
66 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
67 let pointer_width = SERIALIZATION_CONTEXT
68 .with(|ctx| ctx.borrow().pointer_width.unwrap_or(PointerWidth::Unknown));
69 match pointer_width {
70 PointerWidth::Bits32 => write!(f, "{:#010x}", self.0),
71 _ => write!(f, "{:#018x}", self.0),
72 }
73 }
74}
75
76pub type AddressOffset = Address;
77
78#[derive(Debug, Clone, Default)]
79pub struct LinuxStandardBase {
80 pub id: String,
81 pub release: String,
82 pub codename: String,
83 pub description: String,
84}
85
86impl From<MinidumpLinuxLsbRelease<'_>> for LinuxStandardBase {
87 fn from(linux_standard_base: MinidumpLinuxLsbRelease) -> Self {
88 let mut lsb = LinuxStandardBase::default();
89 for (key, val) in linux_standard_base.iter() {
90 match key.as_bytes() {
91 b"DISTRIB_ID" | b"ID" => lsb.id = val.to_string_lossy().into_owned(),
92 b"DISTRIB_RELEASE" | b"VERSION_ID" => {
93 lsb.release = val.to_string_lossy().into_owned()
94 }
95 b"DISTRIB_CODENAME" | b"VERSION_CODENAME" => {
96 lsb.codename = val.to_string_lossy().into_owned()
97 }
98 b"DISTRIB_DESCRIPTION" | b"PRETTY_NAME" => {
99 lsb.description = val.to_string_lossy().into_owned()
100 }
101 _ => {}
102 }
103 }
104 lsb
105 }
106}
107
108#[derive(Debug, Clone)]
109pub struct LinuxProcStatus {
110 pub pid: u32,
111}
112
113impl From<MinidumpLinuxProcStatus<'_>> for LinuxProcStatus {
114 fn from(status: MinidumpLinuxProcStatus) -> Self {
115 let pid = status
116 .iter()
117 .find(|entry| entry.0.as_bytes() == b"Pid")
118 .map_or(0, |key_val| {
119 key_val.1.to_string_lossy().parse::<u32>().unwrap_or(0)
120 });
121 LinuxProcStatus { pid }
122 }
123}
124
125#[derive(Debug, Clone, PartialEq)]
126pub enum Limit {
127 Error,
128 Unlimited,
129 Limited(u64),
130}
131
132impl serde::Serialize for Limit {
133 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134 where
135 S: serde::Serializer,
136 {
137 match *self {
138 Limit::Error => serializer.serialize_str("err"),
139 Limit::Unlimited => serializer.serialize_str("unlimited"),
140 Limit::Limited(val) => serializer.serialize_u64(val),
141 }
142 }
143}
144
145#[derive(Debug, Clone)]
146pub struct LinuxProcLimit {
147 pub soft: Limit,
148 pub hard: Limit,
149 pub unit: String,
150}
151
152#[derive(Debug, Clone)]
153pub struct LinuxProcLimits {
154 pub limits: HashMap<String, LinuxProcLimit>,
155}
156
157fn parse_limit(s: &str) -> Limit {
158 match s.trim() {
159 "unlimited" => Limit::Unlimited,
160 val => Limit::Limited(val.parse::<u64>().unwrap_or(0)),
161 }
162}
163
164impl From<MinidumpLinuxProcLimits<'_>> for LinuxProcLimits {
165 fn from(limits: MinidumpLinuxProcLimits) -> Self {
166 let hash: HashMap<String, LinuxProcLimit> = limits
167 .iter()
168 .filter(|l| !l.is_empty())
169 .skip(1) .map(|line: &strings::LinuxOsStr| line.to_string_lossy())
171 .map(|l| {
172 l.split(" ")
173 .filter(|x| !x.is_empty())
174 .map(|x| x.to_string())
175 .collect::<Vec<String>>()
176 })
177 .filter_map(|m| {
178 let unit = m
179 .get(3)
180 .map(|u| u.trim().to_owned())
181 .unwrap_or_else(|| "n/a".to_owned());
182
183 let name = m.first()?.trim().to_owned();
184 let lim = LinuxProcLimit {
185 soft: parse_limit(m.get(1)?),
186 hard: parse_limit(m.get(2)?),
187 unit,
188 };
189
190 Some((name, lim))
191 })
192 .collect();
193
194 LinuxProcLimits { limits: hash }
195 }
196}
197
198#[derive(Debug, Clone)]
203pub struct ExceptionInfo {
204 pub reason: CrashReason,
206 pub address: Address,
213 pub adjusted_address: Option<AdjustedAddress>,
217 pub instruction_str: Option<String>,
219 pub instruction_properties: Option<InstructionProperties>,
221 pub memory_access_list: Option<MemoryAccessList>,
223 pub instruction_pointer_update: Option<InstructionPointerUpdate>,
225 pub possible_bit_flips: Vec<PossibleBitFlip>,
229 pub inconsistencies: Vec<CrashInconsistency>,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq)]
242pub enum AdjustedAddress {
243 NonCanonical(Address),
245 NullPointerWithOffset(AddressOffset),
247}
248
249#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
250pub struct BitFlipDetails {
251 pub was_non_canonical: bool,
253 pub is_null: bool,
255 pub was_low: bool,
261 pub nearby_registers: u32,
266 pub poison_registers: bool,
270}
271
272mod confidence {
273 const HIGH: f32 = 0.90;
283 const MEDIUM: f32 = 0.50;
284 const LOW: f32 = 0.25;
285
286 pub fn combine(values: &[f32]) -> f32 {
287 1.0f32 - values.iter().map(|v| 1.0f32 - v).product::<f32>()
288 }
289
290 pub const BASELINE: f32 = LOW;
294
295 pub const NON_CANONICAL: f32 = HIGH;
296 pub const NULL: f32 = MEDIUM;
297 pub const NEARBY_REGISTER: [f32; 4] = [MEDIUM, MEDIUM + 0.05, MEDIUM + 0.1, MEDIUM + 0.15];
298
299 pub const POISON: f32 = MEDIUM;
301 pub const ORIGINAL_LOW: f32 = MEDIUM;
302}
303
304impl BitFlipDetails {
305 pub fn confidence(&self) -> f32 {
307 use confidence::*;
308 let mut values = Vec::with_capacity(4);
309 values.push(BASELINE);
310
311 if self.was_non_canonical {
312 values.push(NON_CANONICAL);
313 }
314
315 if self.is_null {
316 let mut val = NULL;
317 if self.was_low {
318 val *= ORIGINAL_LOW;
319 }
320 values.push(val);
321 }
322
323 if self.nearby_registers > 0 {
324 let nearby = std::cmp::min(self.nearby_registers as usize, NEARBY_REGISTER.len()) - 1;
325 values.push(NEARBY_REGISTER[nearby]);
326 }
327
328 let mut ret = combine(&values);
329
330 if self.poison_registers {
331 ret *= POISON;
332 }
333 ret
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, serde::Serialize)]
338pub struct PossibleBitFlip {
339 pub address: Address,
341 pub source_register: Option<&'static str>,
343 pub details: BitFlipDetails,
345 pub confidence: Option<f32>,
347}
348
349const NEARBY_REGISTER_DISTANCE: u64 = 1 << 12;
352
353const LOW_ADDRESS_CUTOFF: u64 = NEARBY_REGISTER_DISTANCE * 2;
355
356impl PossibleBitFlip {
357 pub fn new(address: u64, source_register: Option<&'static str>) -> Self {
358 PossibleBitFlip {
359 address: address.into(),
360 source_register,
361 details: Default::default(),
362 confidence: None,
363 }
364 }
365
366 pub fn calculate_heuristics(
367 &mut self,
368 original_address: u64,
369 was_non_canonical: bool,
370 context: Option<&MinidumpContext>,
371 ) {
372 self.details.is_null = self.address.0 == 0;
373 self.details.was_low = self.details.is_null && original_address <= LOW_ADDRESS_CUTOFF;
374 self.details.was_non_canonical = was_non_canonical;
375
376 self.details.nearby_registers = 0;
377 self.details.poison_registers = false;
378 if let Some(context) = context {
379 let register_size = context.register_size();
380
381 let is_repeated = match register_size {
382 2 => |addr: u64| addr == (addr & 0xff) * 0x0101,
383 4 => |addr: u64| addr == (addr & 0xff) * 0x01010101,
384 8 => |addr: u64| addr == (addr & 0xff) * 0x0101010101010101,
385 other => {
386 tracing::warn!("unsupported register size: {other}");
387 |_| false
388 }
389 };
390
391 let should_calculate_nearby_registers = self.address.0 > LOW_ADDRESS_CUTOFF;
394
395 for (_, addr) in context.valid_registers() {
396 if should_calculate_nearby_registers
397 && self.address.0.abs_diff(addr) <= NEARBY_REGISTER_DISTANCE
398 {
399 self.details.nearby_registers += 1;
400 }
401
402 if !self.details.poison_registers && is_repeated(addr) {
403 match (addr & 0xff) as u8 {
410 0x2b | 0x2d | 0x2f | 0x49 | 0x4b | 0x4d | 0x4f | 0x6b | 0x8b | 0x9b
411 | 0x9f | 0xa5 | 0xbb | 0xcc | 0xcd | 0xce | 0xdb | 0xe5 => {
412 self.details.poison_registers = true;
413 }
414 _ => (),
415 }
416 }
417 }
418 }
419
420 self.confidence = Some(self.details.confidence());
421 }
422}
423
424#[derive(serde::Serialize, Debug, Clone)]
425#[serde(rename_all = "snake_case")]
426pub enum CrashInconsistency {
427 IntDivByZeroNotPossible,
428 PrivInstructionCrashWithoutPrivInstruction,
429 NonCanonicalAddressFalselyReported,
430 AccessViolationWhenAccessAllowed,
431 CrashingAccessNotFoundInMemoryAccesses,
432}
433
434impl std::fmt::Display for CrashInconsistency {
435 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436 match self {
437 CrashInconsistency::IntDivByZeroNotPossible => {
438 f.write_str("Crash reason is an integer division by zero but the crashing instruction is not a division")
439 }
440 CrashInconsistency::PrivInstructionCrashWithoutPrivInstruction => {
441 f.write_str("Crash reason is a privileged instruction but crashing instruction is not a privileged one")
442 }
443 CrashInconsistency::NonCanonicalAddressFalselyReported => {
444 f.write_str("Crash address is reported as a non-canonical x86-64 address but the actual address is a canonical one")
445 }
446 CrashInconsistency::AccessViolationWhenAccessAllowed => {
447 f.write_str("Crash reason is access violation exception but access is allowed")
448 }
449 CrashInconsistency::CrashingAccessNotFoundInMemoryAccesses => f.write_str(
450 "Crash address not found among the memory accesses of the crashing instruction",
451 ),
452 }
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct ProcessState {
459 pub process_id: Option<u32>,
461 pub time: SystemTime,
463 pub process_create_time: Option<SystemTime>,
465 pub cert_info: HashMap<String, String>,
467 pub exception_info: Option<ExceptionInfo>,
469 pub assertion: Option<String>,
471 pub requesting_thread: Option<usize>,
480 pub threads: Vec<CallStack>,
483 pub system_info: SystemInfo,
487 pub linux_standard_base: Option<LinuxStandardBase>,
489 pub linux_proc_limits: Option<LinuxProcLimits>,
491 pub mac_crash_info: Option<Vec<RawMacCrashInfo>>,
492 pub mac_boot_args: Option<MinidumpMacBootargs>,
493 pub modules: MinidumpModuleList,
496 pub unloaded_modules: MinidumpUnloadedModuleList,
497 pub handles: Option<MinidumpHandleDataStream>,
498 pub unknown_streams: Vec<MinidumpUnknownStream>,
502 pub unimplemented_streams: Vec<MinidumpUnimplementedStream>,
503 pub symbol_stats: HashMap<String, SymbolStats>,
504 pub linux_memory_map_count: Option<usize>,
505 pub soft_errors: Option<serde_json::Value>,
506}
507
508fn json_registers(ctx: &MinidumpContext) -> serde_json::Value {
509 let registers: Cow<HashSet<&str>> = match ctx.valid {
510 MinidumpContextValidity::All => {
511 let gpr = ctx.general_purpose_registers();
512 let set: HashSet<&str> = gpr.iter().cloned().collect();
513 Cow::Owned(set)
514 }
515 MinidumpContextValidity::Some(ref which) => Cow::Borrowed(which),
516 };
517
518 let mut output = serde_json::Map::new();
519 for ® in ctx.general_purpose_registers() {
520 if registers.contains(reg) {
521 let reg_val = ctx.format_register(reg);
522 output.insert(String::from(reg), json!(reg_val));
523 }
524 }
525 json!(output)
526}
527
528fn eq_some<T: PartialEq>(opt: Option<T>, val: T) -> bool {
529 match opt {
530 Some(v) => v == val,
531 None => false,
532 }
533}
534
535impl ProcessState {
536 pub fn crashed(&self) -> bool {
538 self.exception_info.is_some()
539 }
540 pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
545 self.print_internal(f, false)
546 }
547
548 pub fn print_brief<T: Write>(&self, f: &mut T) -> io::Result<()> {
552 self.print_internal(f, true)
553 }
554
555 fn print_internal<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
556 self.set_print_context();
557
558 writeln!(f, "Operating system: {}", self.system_info.os.long_name())?;
559 if let Some(ref ver) = self.system_info.format_os_version() {
560 writeln!(f, " {ver}")?;
561 }
562 writeln!(f, "CPU: {}", self.system_info.cpu)?;
563 if let Some(ref info) = self.system_info.cpu_info {
564 writeln!(f, " {info}")?;
565 }
566 writeln!(
567 f,
568 " {} CPU{}",
569 self.system_info.cpu_count,
570 if self.system_info.cpu_count > 1 {
571 "s"
572 } else {
573 ""
574 }
575 )?;
576 if let Some(ref lsb) = self.linux_standard_base {
577 writeln!(
578 f,
579 "Linux {} {} - {} ({})",
580 lsb.id, lsb.release, lsb.codename, lsb.description
581 )?;
582 }
583 writeln!(f)?;
584
585 if let Some(ref crash_info) = self.exception_info {
586 writeln!(f, "Crash reason: {}", crash_info.reason)?;
587
588 if let Some(adjusted_address) = &crash_info.adjusted_address {
589 writeln!(f, "Crash address: {} **", crash_info.address)?;
590 match adjusted_address {
591 AdjustedAddress::NonCanonical(address) => {
592 writeln!(f, " ** Non-canonical address detected: {address}")?
593 }
594 AdjustedAddress::NullPointerWithOffset(offset) => {
595 writeln!(f, " ** Null pointer detected with offset: {offset}")?
596 }
597 }
598 } else {
599 writeln!(f, "Crash address: {}", crash_info.address)?;
600 }
601
602 if let Some(ref crashing_instruction_str) = crash_info.instruction_str {
603 writeln!(f, "Crashing instruction: `{crashing_instruction_str}`")?;
604 }
605
606 if let Some(ref access_list) = crash_info.memory_access_list {
607 if !access_list.is_empty() {
608 writeln!(f, "Memory accessed by instruction:")?;
609 for (idx, access) in access_list.iter().enumerate() {
610 writeln!(
611 f,
612 " {idx}. Address: {}",
613 Address(access.address_info.address)
614 )?;
615 if let Some(size) = access.size {
616 writeln!(f, " Size: {size}")?;
617 } else {
618 writeln!(f, " Size: Unknown")?;
619 }
620 if access.address_info.is_likely_guard_page {
621 writeln!(f, " This address falls in a likely guard page.")?;
622 }
623 if access.access_type.is_read_or_write() {
624 writeln!(f, " Access type: {}", access.access_type)?;
625 }
626 }
627 } else {
628 writeln!(f, "No memory accessed by instruction")?;
629 }
630 }
631
632 if let Some(ref rip_update) = crash_info.instruction_pointer_update {
633 match rip_update {
634 InstructionPointerUpdate::Update { address_info } => {
635 writeln!(f, "Instruction pointer update done by instruction:")?;
636 writeln!(f, " Address: {}", Address(address_info.address))?;
637 if address_info.is_likely_guard_page {
638 writeln!(f, " This address falls in a likely guard page.")?;
639 }
640 }
641 InstructionPointerUpdate::NoUpdate => {
642 writeln!(f, "No instruction pointer update by instruction")?;
643 }
644 }
645 }
646
647 if !crash_info.possible_bit_flips.is_empty() {
648 writeln!(f, "Crashing address may be the result of a flipped bit:")?;
649 let mut bit_flips_with_confidence = crash_info
650 .possible_bit_flips
651 .iter()
652 .map(|b| (b.confidence.unwrap_or_default(), b))
653 .collect::<Vec<_>>();
654 bit_flips_with_confidence.sort_unstable_by(|(conf_a, bf_a), (conf_b, bf_b)| {
656 conf_a
657 .total_cmp(conf_b)
658 .reverse()
659 .then_with(|| bf_a.address.cmp(&bf_b.address))
660 });
661 for (idx, (confidence, b)) in bit_flips_with_confidence.iter().enumerate() {
662 writeln!(
663 f,
664 " {idx}. Valid address: {register}{addr} ({confidence:.3})",
665 addr = b.address,
666 register = match b.source_register {
667 None => Default::default(),
668 Some(name) => format!("{name}="),
669 }
670 )?;
671 }
672 }
673 if !crash_info.inconsistencies.is_empty() {
674 writeln!(f, "Crash is inconsistent:")?;
675 for inconsistency in &crash_info.inconsistencies {
676 writeln!(f, " {inconsistency}")?;
677 }
678 }
679 } else {
680 writeln!(f, "No crash")?;
681 }
682
683 if let Some(ref assertion) = self.assertion {
684 writeln!(f, "Assertion: {assertion}")?;
685 }
686 if let Some(ref info) = self.mac_crash_info {
687 writeln!(f, "Mac Crash Info:")?;
688 for (idx, record) in info.iter().enumerate() {
689 writeln!(f, " Record {idx}")?;
690 if let Some(val) = record.thread() {
691 writeln!(f, " thread: 0x{val}")?;
692 }
693 if let Some(val) = record.dialog_mode() {
694 writeln!(f, " dialog mode: 0x{val}")?;
695 }
696 if let Some(val) = record.abort_cause() {
697 writeln!(f, " abort_cause: 0x{val}")?;
698 }
699
700 if let Some(val) = record.module_path() {
701 writeln!(f, " module: {val}")?;
702 }
703 if let Some(val) = record.message() {
704 writeln!(f, " message: {val}")?;
705 }
706 if let Some(val) = record.signature_string() {
707 writeln!(f, " signature string: {val}")?;
708 }
709 if let Some(val) = record.backtrace() {
710 writeln!(f, " backtrace: {val}")?;
711 }
712 if let Some(val) = record.message2() {
713 writeln!(f, " message2: {val}")?;
714 }
715 }
716 writeln!(f)?;
717 }
718 if let Some(ref info) = self.mac_boot_args {
719 writeln!(
720 f,
721 "Mac Boot Args: {}",
722 info.bootargs.as_deref().unwrap_or("")
723 )?;
724 writeln!(f)?;
725 }
726 if let Some(ref time) = self.process_create_time {
727 let uptime = self.time.duration_since(*time).unwrap_or_default();
728 writeln!(f, "Process uptime: {} seconds", uptime.as_secs())?;
729 } else {
730 writeln!(f, "Process uptime: not available")?;
731 }
732 writeln!(f)?;
733
734 if let Some(linux_memory_map_count) = self.linux_memory_map_count {
735 writeln!(f, "Linux memory map count: {linux_memory_map_count}")?;
736 writeln!(f)?;
737 }
738
739 if let Some(requesting_thread) = self.requesting_thread {
740 let stack = &self.threads[requesting_thread];
741 writeln!(
742 f,
743 "Thread {} {} ({}) - tid: {}",
744 requesting_thread,
745 stack.thread_name.as_deref().unwrap_or(""),
746 if self.crashed() {
747 "crashed"
748 } else {
749 "requested dump, did not crash"
750 },
751 stack.thread_id
752 )?;
753 stack.print(f)?;
754 writeln!(f)?;
755 }
756
757 if brief {
759 return Ok(());
760 }
761
762 for (i, stack) in self.threads.iter().enumerate() {
763 if eq_some(self.requesting_thread, i) {
764 continue;
766 }
767 if stack.info == CallStackInfo::DumpThreadSkipped {
768 continue;
769 }
770 writeln!(
771 f,
772 "Thread {} {} - tid: {}",
773 i,
774 stack.thread_name.as_deref().unwrap_or(""),
775 stack.thread_id
776 )?;
777 stack.print(f)?;
778 }
779 write!(
780 f,
781 "
782Loaded modules:
783"
784 )?;
785 let main_address = self.modules.main_module().map(|m| m.base_address());
786 for module in self.modules.by_addr() {
787 let full_name = module.code_file();
789 let name = basename(&full_name);
790 write!(
791 f,
792 "{:#010x} - {:#010x} {} {}",
793 module.base_address(),
794 module.base_address() + module.size() - 1,
795 name,
796 module.version().unwrap_or(Cow::Borrowed("???"))
797 )?;
798 if eq_some(main_address, module.base_address()) {
799 write!(f, " (main)")?;
800 }
801 if let Some(cert) = self.cert_info.get(name) {
802 write!(f, " ({cert})")?;
803 }
804 writeln!(f)?;
805 }
806 write!(
807 f,
808 "
809Unloaded modules:
810"
811 )?;
812 for module in self.unloaded_modules.by_addr() {
813 let full_name = module.code_file();
814 let name = basename(&full_name);
815 write!(
816 f,
817 "{:#010x} - {:#010x} {}",
818 module.base_address(),
819 module.base_address() + module.size() - 1,
820 basename(&module.code_file()),
821 )?;
822 if let Some(cert) = self.cert_info.get(name) {
823 write!(f, " ({cert})")?;
824 }
825 writeln!(f)?;
826 }
827 if !self.unimplemented_streams.is_empty() {
828 write!(
829 f,
830 "
831Unimplemented streams encountered:
832"
833 )?;
834 for stream in &self.unimplemented_streams {
835 writeln!(
836 f,
837 "Stream 0x{:08x} {:?} ({}) @ 0x{:08x}",
838 stream.stream_type as u32,
839 stream.stream_type,
840 stream.vendor,
841 stream.location.rva,
842 )?;
843 }
844 }
845 if !self.unknown_streams.is_empty() {
846 write!(
847 f,
848 "
849Unknown streams encountered:
850"
851 )?;
852 for stream in &self.unknown_streams {
853 writeln!(
854 f,
855 "Stream 0x{:08x} ({}) @ 0x{:08x}",
856 stream.stream_type, stream.vendor, stream.location.rva,
857 )?;
858 }
859 }
860
861 if let Some(soft_errors) = self.soft_errors.as_ref() {
862 if soft_errors.as_array().is_some_and(|a| !a.is_empty()) {
863 writeln!(
864 f,
865 "\nSoft errors were encountered when minidump was written:"
866 )?;
867 writeln!(f, "{soft_errors:#}")?;
868 }
869 }
870 Ok(())
871 }
872
873 pub fn print_json<T: Write>(&self, f: &mut T, pretty: bool) -> Result<(), serde_json::Error> {
877 self.set_print_context();
880
881 let sys = &self.system_info;
882
883 fn json_hex(address: u64) -> String {
884 Address(address).to_string()
885 }
886
887 let mut output = json!({
888 "status": "OK",
891 "system_info": {
892 "os": sys.os.long_name(),
894 "os_ver": sys.format_os_version(),
895 "cpu_arch": sys.cpu.to_string(),
897 "cpu_info": sys.cpu_info,
898 "cpu_count": sys.cpu_count,
899 "cpu_microcode_version": sys.cpu_microcode_version.map(|num| format!("{num:#x}")),
901 },
902 "crash_info": {
903 "type": self.exception_info.as_ref().map(|info| info.reason).map(|reason| reason.to_string()),
904 "address": self.exception_info.as_ref().map(|info| info.address),
905 "adjusted_address": self.exception_info.as_ref().map(|info| {
906 info.adjusted_address.as_ref().map(|adjusted| match adjusted {
907 AdjustedAddress::NonCanonical(address) => json!({
908 "kind": "non-canonical",
909 "address": address,
910 }),
911 AdjustedAddress::NullPointerWithOffset(offset) => json!({
912 "kind": "null-pointer",
913 "offset": offset,
914 }),
915 })
916 }),
917 "instruction": self.exception_info.as_ref().map(|info| info.instruction_str.as_ref()),
918 "memory_accesses": self.exception_info.as_ref().and_then(|info| {
919 info.memory_access_list.as_ref().map(|access_list| {
920 access_list.iter().map(|access| {
921 let mut map = json!({
922 "address": json_hex(access.address_info.address),
923 "size": access.size,
924 });
925 if access.address_info.is_likely_guard_page {
927 map["is_likely_guard_page"] = true.into();
928 }
929 if access.access_type.is_read_or_write() {
930 map["access_type"] = access.access_type.to_string().to_lowercase().into();
931 }
932 map
933 }).collect::<Vec<_>>()
934 })
935 }),
936 "instruction_pointer_update": self.exception_info.as_ref().and_then(|info| {
937 info.instruction_pointer_update.as_ref().map(|update| {
938 match update {
939 InstructionPointerUpdate::Update { address_info } => {
940 let mut map = json!({
941 "address": json_hex(address_info.address),
942 });
943 if address_info.is_likely_guard_page {
944 map["is_likely_guard_page"] = true.into();
945 }
946 map
947 }
948 InstructionPointerUpdate::NoUpdate => {
949 json!(null)
950 }
951 }
952 })
953 }),
954 "possible_bit_flips": self.exception_info.as_ref().and_then(|info| {
955 (!info.possible_bit_flips.is_empty()).then_some(&info.possible_bit_flips)
956 }),
957 "crash_inconsistencies": self.exception_info.as_ref().map(|info| {
958 &info.inconsistencies
959 }),
960 "crashing_thread": self.requesting_thread,
962 "assertion": self.assertion,
963 },
964 "lsb_release": self.linux_standard_base.as_ref().map(|lsb| json!({
966 "id": lsb.id,
967 "release": lsb.release,
968 "codename": lsb.codename,
969 "description": lsb.description,
970 })),
971 "proc_limits": self.linux_proc_limits.as_ref().map(|limits| json!({
973 "limits": limits.limits.iter().map(|limit| json!({
974 "name": limit.0,
975 "soft": limit.1.soft,
976 "hard": limit.1.hard,
977 "unit": limit.1.unit,
978 })).collect::<Vec<_>>()
979 })),
980 "soft_errors": self.soft_errors.as_ref(),
981 "mac_crash_info": self.mac_crash_info.as_ref().map(|info| json!({
983 "num_records": info.len(),
984 "records": info.iter().map(|record| json!({
986 "thread": record.thread().copied().map(json_hex),
987 "dialog_mode": record.dialog_mode().copied().map(json_hex),
988 "abort_cause": record.abort_cause().copied().map(json_hex),
989
990 "module": record.module_path(),
991 "message": record.message(),
992 "signature_string": record.signature_string(),
993 "backtrace": record.backtrace(),
994 "message2": record.message2(),
995 })).collect::<Vec<_>>()
996 })),
997 "mac_boot_args": self.mac_boot_args.as_ref().map(|info| info.bootargs.as_ref()),
999
1000 "linux_memory_map_count": self.linux_memory_map_count,
1002
1003 "main_module": 0,
1005 "modules_contains_cert_info": !self.cert_info.is_empty(),
1007 "modules": self.modules.iter().map(|module| {
1008 let full_name = module.code_file();
1009 let name = basename(&full_name);
1010
1011 let stats = self.symbol_stats.get(name);
1013 let had_stats = stats.is_some();
1014 let default = SymbolStats::default();
1015 let stats = stats.unwrap_or(&default);
1016 let debug_file;
1018 let debug_id;
1019 let debug_file_cow = module.debug_file().unwrap_or(Cow::Borrowed(""));
1020 if let Some(debug_info) = &stats.extra_debug_info {
1021 debug_file = debug_info.debug_file.as_str();
1022 debug_id = debug_info.debug_identifier;
1023 } else {
1024 debug_file = debug_file_cow.borrow();
1025 debug_id = module.debug_identifier().unwrap_or_default();
1026 }
1027 let missing_symbols = had_stats && !stats.loaded_symbols;
1031 json!({
1032 "base_addr": json_hex(module.raw.base_of_image),
1033 "debug_file": basename(debug_file),
1035 "debug_id": debug_id.breakpad().to_string(),
1037 "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
1038 "filename": &name,
1039 "code_id": module.code_identifier().unwrap_or_default().as_str(),
1040 "version": module.version(),
1041 "cert_subject": self.cert_info.get(name),
1043
1044 "missing_symbols": missing_symbols,
1048 "loaded_symbols": stats.loaded_symbols,
1050 "corrupt_symbols": stats.corrupt_symbols,
1052 "symbol_url": stats.symbol_url,
1054 })
1055 }).collect::<Vec<_>>(),
1056 "pid": self.process_id,
1057 "process_uptime": self.process_create_time.map(|time| {
1058 self.time.duration_since(time).unwrap_or_default().as_secs()
1059 }),
1060 "thread_count": self.threads.len(),
1061 "threads": self.threads.iter().map(|thread| json!({
1062 "frame_count": thread.frames.len(),
1063 "last_error_value": thread.last_error_value.map(|error| error.to_string()),
1065 "thread_name": thread.thread_name,
1067 "thread_id" : thread.thread_id,
1068 "frames": thread.frames.iter().enumerate().map(|(idx, frame)| json!({
1069 "frame": idx,
1070 "module": frame.module.as_ref().map(|module| basename(&module.name)),
1072 "function": frame.function_name,
1074 "file": frame.source_file_name,
1076 "line": frame.source_line,
1078 "offset": json_hex(frame.instruction),
1079 "inlines": if !frame.inlines.is_empty() {
1081 Some(frame.inlines.iter().map(|frame| {
1082 json!({
1083 "function": frame.function_name,
1084 "file": frame.source_file_name,
1085 "line": frame.source_line,
1086 })
1087 }).collect::<Vec<_>>())
1088 } else {
1089 None
1090 },
1091 "module_offset": frame
1093 .module
1094 .as_ref()
1095 .map(|module| frame.instruction - module.raw.base_of_image)
1096 .map(json_hex),
1097 "unloaded_modules": if frame.unloaded_modules.is_empty() {
1099 None
1100 } else {
1101 Some(frame.unloaded_modules.iter().map(|(module, offsets)| json!({
1102 "module": module,
1103 "offsets": offsets.iter().copied().map(json_hex).collect::<Vec<_>>(),
1104 })).collect::<Vec<_>>())
1105 },
1106 "function_offset": frame
1108 .function_base
1109 .map(|func_base| frame.instruction - func_base)
1110 .map(json_hex),
1111 "missing_symbols": frame.function_name.is_none(),
1112 "trust": frame.trust.as_str()
1114 })).collect::<Vec<_>>(),
1115 })).collect::<Vec<_>>(),
1116
1117 "unloaded_modules": self.unloaded_modules.iter().map(|module| json!({
1118 "base_addr": json_hex(module.raw.base_of_image),
1119 "code_id": module.code_identifier().unwrap_or_default().as_str(),
1120 "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
1121 "filename": module.name,
1122 "cert_subject": self.cert_info.get(&module.name),
1123 })).collect::<Vec<_>>(),
1124 "handles": self.handles.as_ref().map(|handles| handles.iter().map(|handle| json!({
1125 "handle": handle.raw.handle(),
1126 "type_name": handle.type_name,
1127 "object_name": handle.object_name
1128 })).collect::<Vec<_>>()),
1129 });
1130
1131 if let Some(requesting_thread) = self.requesting_thread {
1132 if let Some(f) = self.threads[requesting_thread].frames.first() {
1142 let registers = json_registers(&f.context);
1143
1144 let mut thread = output.get_mut("threads").unwrap().as_array().unwrap()
1146 [requesting_thread]
1147 .clone();
1148 let thread_obj = thread.as_object_mut().unwrap();
1149 let frames = thread_obj
1150 .get_mut("frames")
1151 .unwrap()
1152 .as_array_mut()
1153 .unwrap();
1154 let frame = frames[0].as_object_mut().unwrap();
1155
1156 frame.insert(String::from("registers"), registers);
1157 thread_obj.insert(String::from("threads_index"), json!(requesting_thread));
1158
1159 output
1160 .as_object_mut()
1161 .unwrap()
1162 .insert(String::from("crashing_thread"), thread);
1163 }
1164 }
1165
1166 if pretty {
1167 serde_json::to_writer_pretty(f, &output)
1168 } else {
1169 serde_json::to_writer(f, &output)
1170 }
1171 }
1172
1173 fn set_print_context(&self) {
1174 SERIALIZATION_CONTEXT.with(|ctx| {
1175 ctx.borrow_mut().pointer_width = Some(self.system_info.cpu.pointer_width());
1176 });
1177 }
1178}