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 .map(|m| {
178 let u = if m.len() == 3 {
179 "n/a".to_string()
180 } else {
181 m[3].trim().to_string()
182 };
183
184 let name = m[0].trim().to_string();
185 let lim = LinuxProcLimit {
186 soft: parse_limit(&m[1]),
187 hard: parse_limit(&m[2]),
188 unit: u,
189 };
190
191 (name, lim)
192 })
193 .collect();
194
195 LinuxProcLimits { limits: hash }
196 }
197}
198
199#[derive(Debug, Clone)]
204pub struct ExceptionInfo {
205 pub reason: CrashReason,
207 pub address: Address,
214 pub adjusted_address: Option<AdjustedAddress>,
218 pub instruction_str: Option<String>,
220 pub instruction_properties: Option<InstructionProperties>,
222 pub memory_access_list: Option<MemoryAccessList>,
224 pub instruction_pointer_update: Option<InstructionPointerUpdate>,
226 pub possible_bit_flips: Vec<PossibleBitFlip>,
230 pub inconsistencies: Vec<CrashInconsistency>,
232}
233
234#[derive(Debug, Clone, PartialEq, Eq)]
243pub enum AdjustedAddress {
244 NonCanonical(Address),
246 NullPointerWithOffset(AddressOffset),
248}
249
250#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
251pub struct BitFlipDetails {
252 pub was_non_canonical: bool,
254 pub is_null: bool,
256 pub was_low: bool,
262 pub nearby_registers: u32,
267 pub poison_registers: bool,
271}
272
273mod confidence {
274 const HIGH: f32 = 0.90;
284 const MEDIUM: f32 = 0.50;
285 const LOW: f32 = 0.25;
286
287 pub fn combine(values: &[f32]) -> f32 {
288 1.0f32 - values.iter().map(|v| 1.0f32 - v).product::<f32>()
289 }
290
291 pub const BASELINE: f32 = LOW;
295
296 pub const NON_CANONICAL: f32 = HIGH;
297 pub const NULL: f32 = MEDIUM;
298 pub const NEARBY_REGISTER: [f32; 4] = [MEDIUM, MEDIUM + 0.05, MEDIUM + 0.1, MEDIUM + 0.15];
299
300 pub const POISON: f32 = MEDIUM;
302 pub const ORIGINAL_LOW: f32 = MEDIUM;
303}
304
305impl BitFlipDetails {
306 pub fn confidence(&self) -> f32 {
308 use confidence::*;
309 let mut values = Vec::with_capacity(4);
310 values.push(BASELINE);
311
312 if self.was_non_canonical {
313 values.push(NON_CANONICAL);
314 }
315
316 if self.is_null {
317 let mut val = NULL;
318 if self.was_low {
319 val *= ORIGINAL_LOW;
320 }
321 values.push(val);
322 }
323
324 if self.nearby_registers > 0 {
325 let nearby = std::cmp::min(self.nearby_registers as usize, NEARBY_REGISTER.len()) - 1;
326 values.push(NEARBY_REGISTER[nearby]);
327 }
328
329 let mut ret = combine(&values);
330
331 if self.poison_registers {
332 ret *= POISON;
333 }
334 ret
335 }
336}
337
338#[derive(Debug, Clone, PartialEq, serde::Serialize)]
339pub struct PossibleBitFlip {
340 pub address: Address,
342 pub source_register: Option<&'static str>,
344 pub details: BitFlipDetails,
346 pub confidence: Option<f32>,
348}
349
350const NEARBY_REGISTER_DISTANCE: u64 = 1 << 12;
353
354const LOW_ADDRESS_CUTOFF: u64 = NEARBY_REGISTER_DISTANCE * 2;
356
357impl PossibleBitFlip {
358 pub fn new(address: u64, source_register: Option<&'static str>) -> Self {
359 PossibleBitFlip {
360 address: address.into(),
361 source_register,
362 details: Default::default(),
363 confidence: None,
364 }
365 }
366
367 pub fn calculate_heuristics(
368 &mut self,
369 original_address: u64,
370 was_non_canonical: bool,
371 context: Option<&MinidumpContext>,
372 ) {
373 self.details.is_null = self.address.0 == 0;
374 self.details.was_low = self.details.is_null && original_address <= LOW_ADDRESS_CUTOFF;
375 self.details.was_non_canonical = was_non_canonical;
376
377 self.details.nearby_registers = 0;
378 self.details.poison_registers = false;
379 if let Some(context) = context {
380 let register_size = context.register_size();
381
382 let is_repeated = match register_size {
383 2 => |addr: u64| addr == (addr & 0xff) * 0x0101,
384 4 => |addr: u64| addr == (addr & 0xff) * 0x01010101,
385 8 => |addr: u64| addr == (addr & 0xff) * 0x0101010101010101,
386 other => {
387 tracing::warn!("unsupported register size: {other}");
388 |_| false
389 }
390 };
391
392 let should_calculate_nearby_registers = self.address.0 > LOW_ADDRESS_CUTOFF;
395
396 for (_, addr) in context.valid_registers() {
397 if should_calculate_nearby_registers
398 && self.address.0.abs_diff(addr) <= NEARBY_REGISTER_DISTANCE
399 {
400 self.details.nearby_registers += 1;
401 }
402
403 if !self.details.poison_registers && is_repeated(addr) {
404 match (addr & 0xff) as u8 {
411 0x2b | 0x2d | 0x2f | 0x49 | 0x4b | 0x4d | 0x4f | 0x6b | 0x8b | 0x9b
412 | 0x9f | 0xa5 | 0xbb | 0xcc | 0xcd | 0xce | 0xdb | 0xe5 => {
413 self.details.poison_registers = true;
414 }
415 _ => (),
416 }
417 }
418 }
419 }
420
421 self.confidence = Some(self.details.confidence());
422 }
423}
424
425#[derive(serde::Serialize, Debug, Clone)]
426#[serde(rename_all = "snake_case")]
427pub enum CrashInconsistency {
428 IntDivByZeroNotPossible,
429 PrivInstructionCrashWithoutPrivInstruction,
430 NonCanonicalAddressFalselyReported,
431 AccessViolationWhenAccessAllowed,
432 CrashingAccessNotFoundInMemoryAccesses,
433}
434
435impl std::fmt::Display for CrashInconsistency {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 match self {
438 CrashInconsistency::IntDivByZeroNotPossible => {
439 f.write_str("Crash reason is an integer division by zero but the crashing instruction is not a division")
440 }
441 CrashInconsistency::PrivInstructionCrashWithoutPrivInstruction => {
442 f.write_str("Crash reason is a privileged instruction but crashing instruction is not a privileged one")
443 }
444 CrashInconsistency::NonCanonicalAddressFalselyReported => {
445 f.write_str("Crash address is reported as a non-canonical x86-64 address but the actual address is a canonical one")
446 }
447 CrashInconsistency::AccessViolationWhenAccessAllowed => {
448 f.write_str("Crash reason is access violation exception but access is allowed")
449 }
450 CrashInconsistency::CrashingAccessNotFoundInMemoryAccesses => f.write_str(
451 "Crash address not found among the memory accesses of the crashing instruction",
452 ),
453 }
454 }
455}
456
457#[derive(Debug, Clone)]
459pub struct ProcessState {
460 pub process_id: Option<u32>,
462 pub time: SystemTime,
464 pub process_create_time: Option<SystemTime>,
466 pub cert_info: HashMap<String, String>,
468 pub exception_info: Option<ExceptionInfo>,
470 pub assertion: Option<String>,
472 pub requesting_thread: Option<usize>,
481 pub threads: Vec<CallStack>,
484 pub system_info: SystemInfo,
488 pub linux_standard_base: Option<LinuxStandardBase>,
490 pub linux_proc_limits: Option<LinuxProcLimits>,
492 pub mac_crash_info: Option<Vec<RawMacCrashInfo>>,
493 pub mac_boot_args: Option<MinidumpMacBootargs>,
494 pub modules: MinidumpModuleList,
497 pub unloaded_modules: MinidumpUnloadedModuleList,
498 pub handles: Option<MinidumpHandleDataStream>,
499 pub unknown_streams: Vec<MinidumpUnknownStream>,
503 pub unimplemented_streams: Vec<MinidumpUnimplementedStream>,
504 pub symbol_stats: HashMap<String, SymbolStats>,
505 pub linux_memory_map_count: Option<usize>,
506 pub soft_errors: Option<serde_json::Value>,
507}
508
509fn json_registers(ctx: &MinidumpContext) -> serde_json::Value {
510 let registers: Cow<HashSet<&str>> = match ctx.valid {
511 MinidumpContextValidity::All => {
512 let gpr = ctx.general_purpose_registers();
513 let set: HashSet<&str> = gpr.iter().cloned().collect();
514 Cow::Owned(set)
515 }
516 MinidumpContextValidity::Some(ref which) => Cow::Borrowed(which),
517 };
518
519 let mut output = serde_json::Map::new();
520 for ® in ctx.general_purpose_registers() {
521 if registers.contains(reg) {
522 let reg_val = ctx.format_register(reg);
523 output.insert(String::from(reg), json!(reg_val));
524 }
525 }
526 json!(output)
527}
528
529fn eq_some<T: PartialEq>(opt: Option<T>, val: T) -> bool {
530 match opt {
531 Some(v) => v == val,
532 None => false,
533 }
534}
535
536impl ProcessState {
537 pub fn crashed(&self) -> bool {
539 self.exception_info.is_some()
540 }
541 pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
546 self.print_internal(f, false)
547 }
548
549 pub fn print_brief<T: Write>(&self, f: &mut T) -> io::Result<()> {
553 self.print_internal(f, true)
554 }
555
556 fn print_internal<T: Write>(&self, f: &mut T, brief: bool) -> io::Result<()> {
557 self.set_print_context();
558
559 writeln!(f, "Operating system: {}", self.system_info.os.long_name())?;
560 if let Some(ref ver) = self.system_info.format_os_version() {
561 writeln!(f, " {ver}")?;
562 }
563 writeln!(f, "CPU: {}", self.system_info.cpu)?;
564 if let Some(ref info) = self.system_info.cpu_info {
565 writeln!(f, " {info}")?;
566 }
567 writeln!(
568 f,
569 " {} CPU{}",
570 self.system_info.cpu_count,
571 if self.system_info.cpu_count > 1 {
572 "s"
573 } else {
574 ""
575 }
576 )?;
577 if let Some(ref lsb) = self.linux_standard_base {
578 writeln!(
579 f,
580 "Linux {} {} - {} ({})",
581 lsb.id, lsb.release, lsb.codename, lsb.description
582 )?;
583 }
584 writeln!(f)?;
585
586 if let Some(ref crash_info) = self.exception_info {
587 writeln!(f, "Crash reason: {}", crash_info.reason)?;
588
589 if let Some(adjusted_address) = &crash_info.adjusted_address {
590 writeln!(f, "Crash address: {} **", crash_info.address)?;
591 match adjusted_address {
592 AdjustedAddress::NonCanonical(address) => {
593 writeln!(f, " ** Non-canonical address detected: {address}")?
594 }
595 AdjustedAddress::NullPointerWithOffset(offset) => {
596 writeln!(f, " ** Null pointer detected with offset: {offset}")?
597 }
598 }
599 } else {
600 writeln!(f, "Crash address: {}", crash_info.address)?;
601 }
602
603 if let Some(ref crashing_instruction_str) = crash_info.instruction_str {
604 writeln!(f, "Crashing instruction: `{crashing_instruction_str}`")?;
605 }
606
607 if let Some(ref access_list) = crash_info.memory_access_list {
608 if !access_list.is_empty() {
609 writeln!(f, "Memory accessed by instruction:")?;
610 for (idx, access) in access_list.iter().enumerate() {
611 writeln!(
612 f,
613 " {idx}. Address: {}",
614 Address(access.address_info.address)
615 )?;
616 if let Some(size) = access.size {
617 writeln!(f, " Size: {size}")?;
618 } else {
619 writeln!(f, " Size: Unknown")?;
620 }
621 if access.address_info.is_likely_guard_page {
622 writeln!(f, " This address falls in a likely guard page.")?;
623 }
624 if access.access_type.is_read_or_write() {
625 writeln!(f, " Access type: {}", access.access_type)?;
626 }
627 }
628 } else {
629 writeln!(f, "No memory accessed by instruction")?;
630 }
631 }
632
633 if let Some(ref rip_update) = crash_info.instruction_pointer_update {
634 match rip_update {
635 InstructionPointerUpdate::Update { address_info } => {
636 writeln!(f, "Instruction pointer update done by instruction:")?;
637 writeln!(f, " Address: {}", Address(address_info.address))?;
638 if address_info.is_likely_guard_page {
639 writeln!(f, " This address falls in a likely guard page.")?;
640 }
641 }
642 InstructionPointerUpdate::NoUpdate => {
643 writeln!(f, "No instruction pointer update by instruction")?;
644 }
645 }
646 }
647
648 if !crash_info.possible_bit_flips.is_empty() {
649 writeln!(f, "Crashing address may be the result of a flipped bit:")?;
650 let mut bit_flips_with_confidence = crash_info
651 .possible_bit_flips
652 .iter()
653 .map(|b| (b.confidence.unwrap_or_default(), b))
654 .collect::<Vec<_>>();
655 bit_flips_with_confidence.sort_unstable_by(|(conf_a, bf_a), (conf_b, bf_b)| {
657 conf_a
658 .total_cmp(conf_b)
659 .reverse()
660 .then_with(|| bf_a.address.cmp(&bf_b.address))
661 });
662 for (idx, (confidence, b)) in bit_flips_with_confidence.iter().enumerate() {
663 writeln!(
664 f,
665 " {idx}. Valid address: {register}{addr} ({confidence:.3})",
666 addr = b.address,
667 register = match b.source_register {
668 None => Default::default(),
669 Some(name) => format!("{name}="),
670 }
671 )?;
672 }
673 }
674 if !crash_info.inconsistencies.is_empty() {
675 writeln!(f, "Crash is inconsistent:")?;
676 for inconsistency in &crash_info.inconsistencies {
677 writeln!(f, " {}", inconsistency)?;
678 }
679 }
680 } else {
681 writeln!(f, "No crash")?;
682 }
683
684 if let Some(ref assertion) = self.assertion {
685 writeln!(f, "Assertion: {assertion}")?;
686 }
687 if let Some(ref info) = self.mac_crash_info {
688 writeln!(f, "Mac Crash Info:")?;
689 for (idx, record) in info.iter().enumerate() {
690 writeln!(f, " Record {idx}")?;
691 if let Some(val) = record.thread() {
692 writeln!(f, " thread: 0x{val}")?;
693 }
694 if let Some(val) = record.dialog_mode() {
695 writeln!(f, " dialog mode: 0x{val}")?;
696 }
697 if let Some(val) = record.abort_cause() {
698 writeln!(f, " abort_cause: 0x{val}")?;
699 }
700
701 if let Some(val) = record.module_path() {
702 writeln!(f, " module: {val}")?;
703 }
704 if let Some(val) = record.message() {
705 writeln!(f, " message: {val}")?;
706 }
707 if let Some(val) = record.signature_string() {
708 writeln!(f, " signature string: {val}")?;
709 }
710 if let Some(val) = record.backtrace() {
711 writeln!(f, " backtrace: {val}")?;
712 }
713 if let Some(val) = record.message2() {
714 writeln!(f, " message2: {val}")?;
715 }
716 }
717 writeln!(f)?;
718 }
719 if let Some(ref info) = self.mac_boot_args {
720 writeln!(
721 f,
722 "Mac Boot Args: {}",
723 info.bootargs.as_deref().unwrap_or("")
724 )?;
725 writeln!(f)?;
726 }
727 if let Some(ref time) = self.process_create_time {
728 let uptime = self.time.duration_since(*time).unwrap_or_default();
729 writeln!(f, "Process uptime: {} seconds", uptime.as_secs())?;
730 } else {
731 writeln!(f, "Process uptime: not available")?;
732 }
733 writeln!(f)?;
734
735 if let Some(linux_memory_map_count) = self.linux_memory_map_count {
736 writeln!(f, "Linux memory map count: {}", linux_memory_map_count)?;
737 writeln!(f)?;
738 }
739
740 if let Some(requesting_thread) = self.requesting_thread {
741 let stack = &self.threads[requesting_thread];
742 writeln!(
743 f,
744 "Thread {} {} ({}) - tid: {}",
745 requesting_thread,
746 stack.thread_name.as_deref().unwrap_or(""),
747 if self.crashed() {
748 "crashed"
749 } else {
750 "requested dump, did not crash"
751 },
752 stack.thread_id
753 )?;
754 stack.print(f)?;
755 writeln!(f)?;
756 }
757
758 if brief {
760 return Ok(());
761 }
762
763 for (i, stack) in self.threads.iter().enumerate() {
764 if eq_some(self.requesting_thread, i) {
765 continue;
767 }
768 if stack.info == CallStackInfo::DumpThreadSkipped {
769 continue;
770 }
771 writeln!(
772 f,
773 "Thread {} {} - tid: {}",
774 i,
775 stack.thread_name.as_deref().unwrap_or(""),
776 stack.thread_id
777 )?;
778 stack.print(f)?;
779 }
780 write!(
781 f,
782 "
783Loaded modules:
784"
785 )?;
786 let main_address = self.modules.main_module().map(|m| m.base_address());
787 for module in self.modules.by_addr() {
788 let full_name = module.code_file();
790 let name = basename(&full_name);
791 write!(
792 f,
793 "{:#010x} - {:#010x} {} {}",
794 module.base_address(),
795 module.base_address() + module.size() - 1,
796 name,
797 module.version().unwrap_or(Cow::Borrowed("???"))
798 )?;
799 if eq_some(main_address, module.base_address()) {
800 write!(f, " (main)")?;
801 }
802 if let Some(cert) = self.cert_info.get(name) {
803 write!(f, " ({cert})")?;
804 }
805 writeln!(f)?;
806 }
807 write!(
808 f,
809 "
810Unloaded modules:
811"
812 )?;
813 for module in self.unloaded_modules.by_addr() {
814 let full_name = module.code_file();
815 let name = basename(&full_name);
816 write!(
817 f,
818 "{:#010x} - {:#010x} {}",
819 module.base_address(),
820 module.base_address() + module.size() - 1,
821 basename(&module.code_file()),
822 )?;
823 if let Some(cert) = self.cert_info.get(name) {
824 write!(f, " ({cert})")?;
825 }
826 writeln!(f)?;
827 }
828 if !self.unimplemented_streams.is_empty() {
829 write!(
830 f,
831 "
832Unimplemented streams encountered:
833"
834 )?;
835 for stream in &self.unimplemented_streams {
836 writeln!(
837 f,
838 "Stream 0x{:08x} {:?} ({}) @ 0x{:08x}",
839 stream.stream_type as u32,
840 stream.stream_type,
841 stream.vendor,
842 stream.location.rva,
843 )?;
844 }
845 }
846 if !self.unknown_streams.is_empty() {
847 write!(
848 f,
849 "
850Unknown streams encountered:
851"
852 )?;
853 for stream in &self.unknown_streams {
854 writeln!(
855 f,
856 "Stream 0x{:08x} ({}) @ 0x{:08x}",
857 stream.stream_type, stream.vendor, stream.location.rva,
858 )?;
859 }
860 }
861
862 if let Some(soft_errors) = self.soft_errors.as_ref() {
863 if soft_errors.as_array().is_some_and(|a| !a.is_empty()) {
864 writeln!(
865 f,
866 "\nSoft errors were encountered when minidump was written:"
867 )?;
868 writeln!(f, "{soft_errors:#}")?;
869 }
870 }
871 Ok(())
872 }
873
874 pub fn print_json<T: Write>(&self, f: &mut T, pretty: bool) -> Result<(), serde_json::Error> {
878 self.set_print_context();
881
882 let sys = &self.system_info;
883
884 fn json_hex(address: u64) -> String {
885 Address(address).to_string()
886 }
887
888 let mut output = json!({
889 "status": "OK",
892 "system_info": {
893 "os": sys.os.long_name(),
895 "os_ver": sys.format_os_version(),
896 "cpu_arch": sys.cpu.to_string(),
898 "cpu_info": sys.cpu_info,
899 "cpu_count": sys.cpu_count,
900 "cpu_microcode_version": sys.cpu_microcode_version.map(|num| format!("{num:#x}")),
902 },
903 "crash_info": {
904 "type": self.exception_info.as_ref().map(|info| info.reason).map(|reason| reason.to_string()),
905 "address": self.exception_info.as_ref().map(|info| info.address),
906 "adjusted_address": self.exception_info.as_ref().map(|info| {
907 info.adjusted_address.as_ref().map(|adjusted| match adjusted {
908 AdjustedAddress::NonCanonical(address) => json!({
909 "kind": "non-canonical",
910 "address": address,
911 }),
912 AdjustedAddress::NullPointerWithOffset(offset) => json!({
913 "kind": "null-pointer",
914 "offset": offset,
915 }),
916 })
917 }),
918 "instruction": self.exception_info.as_ref().map(|info| info.instruction_str.as_ref()),
919 "memory_accesses": self.exception_info.as_ref().and_then(|info| {
920 info.memory_access_list.as_ref().map(|access_list| {
921 access_list.iter().map(|access| {
922 let mut map = json!({
923 "address": json_hex(access.address_info.address),
924 "size": access.size,
925 });
926 if access.address_info.is_likely_guard_page {
928 map["is_likely_guard_page"] = true.into();
929 }
930 if access.access_type.is_read_or_write() {
931 map["access_type"] = access.access_type.to_string().to_lowercase().into();
932 }
933 map
934 }).collect::<Vec<_>>()
935 })
936 }),
937 "instruction_pointer_update": self.exception_info.as_ref().and_then(|info| {
938 info.instruction_pointer_update.as_ref().map(|update| {
939 match update {
940 InstructionPointerUpdate::Update { address_info } => {
941 let mut map = json!({
942 "address": json_hex(address_info.address),
943 });
944 if address_info.is_likely_guard_page {
945 map["is_likely_guard_page"] = true.into();
946 }
947 map
948 }
949 InstructionPointerUpdate::NoUpdate => {
950 json!(null)
951 }
952 }
953 })
954 }),
955 "possible_bit_flips": self.exception_info.as_ref().and_then(|info| {
956 (!info.possible_bit_flips.is_empty()).then_some(&info.possible_bit_flips)
957 }),
958 "crash_inconsistencies": self.exception_info.as_ref().map(|info| {
959 &info.inconsistencies
960 }),
961 "crashing_thread": self.requesting_thread,
963 "assertion": self.assertion,
964 },
965 "lsb_release": self.linux_standard_base.as_ref().map(|lsb| json!({
967 "id": lsb.id,
968 "release": lsb.release,
969 "codename": lsb.codename,
970 "description": lsb.description,
971 })),
972 "proc_limits": self.linux_proc_limits.as_ref().map(|limits| json!({
974 "limits": limits.limits.iter().map(|limit| json!({
975 "name": limit.0,
976 "soft": limit.1.soft,
977 "hard": limit.1.hard,
978 "unit": limit.1.unit,
979 })).collect::<Vec<_>>()
980 })),
981 "soft_errors": self.soft_errors.as_ref(),
982 "mac_crash_info": self.mac_crash_info.as_ref().map(|info| json!({
984 "num_records": info.len(),
985 "records": info.iter().map(|record| json!({
987 "thread": record.thread().copied().map(json_hex),
988 "dialog_mode": record.dialog_mode().copied().map(json_hex),
989 "abort_cause": record.abort_cause().copied().map(json_hex),
990
991 "module": record.module_path(),
992 "message": record.message(),
993 "signature_string": record.signature_string(),
994 "backtrace": record.backtrace(),
995 "message2": record.message2(),
996 })).collect::<Vec<_>>()
997 })),
998 "mac_boot_args": self.mac_boot_args.as_ref().map(|info| info.bootargs.as_ref()),
1000
1001 "linux_memory_map_count": self.linux_memory_map_count,
1003
1004 "main_module": 0,
1006 "modules_contains_cert_info": !self.cert_info.is_empty(),
1008 "modules": self.modules.iter().map(|module| {
1009 let full_name = module.code_file();
1010 let name = basename(&full_name);
1011
1012 let stats = self.symbol_stats.get(name);
1014 let had_stats = stats.is_some();
1015 let default = SymbolStats::default();
1016 let stats = stats.unwrap_or(&default);
1017 let debug_file;
1019 let debug_id;
1020 let debug_file_cow = module.debug_file().unwrap_or(Cow::Borrowed(""));
1021 if let Some(debug_info) = &stats.extra_debug_info {
1022 debug_file = debug_info.debug_file.as_str();
1023 debug_id = debug_info.debug_identifier;
1024 } else {
1025 debug_file = debug_file_cow.borrow();
1026 debug_id = module.debug_identifier().unwrap_or_default();
1027 }
1028 let missing_symbols = had_stats && !stats.loaded_symbols;
1032 json!({
1033 "base_addr": json_hex(module.raw.base_of_image),
1034 "debug_file": basename(debug_file),
1036 "debug_id": debug_id.breakpad().to_string(),
1038 "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
1039 "filename": &name,
1040 "code_id": module.code_identifier().unwrap_or_default().as_str(),
1041 "version": module.version(),
1042 "cert_subject": self.cert_info.get(name),
1044
1045 "missing_symbols": missing_symbols,
1049 "loaded_symbols": stats.loaded_symbols,
1051 "corrupt_symbols": stats.corrupt_symbols,
1053 "symbol_url": stats.symbol_url,
1055 })
1056 }).collect::<Vec<_>>(),
1057 "pid": self.process_id,
1058 "thread_count": self.threads.len(),
1059 "threads": self.threads.iter().map(|thread| json!({
1060 "frame_count": thread.frames.len(),
1061 "last_error_value": thread.last_error_value.map(|error| error.to_string()),
1063 "thread_name": thread.thread_name,
1065 "thread_id" : thread.thread_id,
1066 "frames": thread.frames.iter().enumerate().map(|(idx, frame)| json!({
1067 "frame": idx,
1068 "module": frame.module.as_ref().map(|module| basename(&module.name)),
1070 "function": frame.function_name,
1072 "file": frame.source_file_name,
1074 "line": frame.source_line,
1076 "offset": json_hex(frame.instruction),
1077 "inlines": if !frame.inlines.is_empty() {
1079 Some(frame.inlines.iter().map(|frame| {
1080 json!({
1081 "function": frame.function_name,
1082 "file": frame.source_file_name,
1083 "line": frame.source_line,
1084 })
1085 }).collect::<Vec<_>>())
1086 } else {
1087 None
1088 },
1089 "module_offset": frame
1091 .module
1092 .as_ref()
1093 .map(|module| frame.instruction - module.raw.base_of_image)
1094 .map(json_hex),
1095 "unloaded_modules": if frame.unloaded_modules.is_empty() {
1097 None
1098 } else {
1099 Some(frame.unloaded_modules.iter().map(|(module, offsets)| json!({
1100 "module": module,
1101 "offsets": offsets.iter().copied().map(json_hex).collect::<Vec<_>>(),
1102 })).collect::<Vec<_>>())
1103 },
1104 "function_offset": frame
1106 .function_base
1107 .map(|func_base| frame.instruction - func_base)
1108 .map(json_hex),
1109 "missing_symbols": frame.function_name.is_none(),
1110 "trust": frame.trust.as_str()
1112 })).collect::<Vec<_>>(),
1113 })).collect::<Vec<_>>(),
1114
1115 "unloaded_modules": self.unloaded_modules.iter().map(|module| json!({
1116 "base_addr": json_hex(module.raw.base_of_image),
1117 "code_id": module.code_identifier().unwrap_or_default().as_str(),
1118 "end_addr": json_hex(module.raw.base_of_image + module.raw.size_of_image as u64),
1119 "filename": module.name,
1120 "cert_subject": self.cert_info.get(&module.name),
1121 })).collect::<Vec<_>>(),
1122 "handles": self.handles.as_ref().map(|handles| handles.iter().map(|handle| json!({
1123 "handle": handle.raw.handle(),
1124 "type_name": handle.type_name,
1125 "object_name": handle.object_name
1126 })).collect::<Vec<_>>()),
1127 });
1128
1129 if let Some(requesting_thread) = self.requesting_thread {
1130 if let Some(f) = self.threads[requesting_thread].frames.first() {
1140 let registers = json_registers(&f.context);
1141
1142 let mut thread = output.get_mut("threads").unwrap().as_array().unwrap()
1144 [requesting_thread]
1145 .clone();
1146 let thread_obj = thread.as_object_mut().unwrap();
1147 let frames = thread_obj
1148 .get_mut("frames")
1149 .unwrap()
1150 .as_array_mut()
1151 .unwrap();
1152 let frame = frames[0].as_object_mut().unwrap();
1153
1154 frame.insert(String::from("registers"), registers);
1155 thread_obj.insert(String::from("threads_index"), json!(requesting_thread));
1156
1157 output
1158 .as_object_mut()
1159 .unwrap()
1160 .insert(String::from("crashing_thread"), thread);
1161 }
1162 }
1163
1164 if pretty {
1165 serde_json::to_writer_pretty(f, &output)
1166 } else {
1167 serde_json::to_writer(f, &output)
1168 }
1169 }
1170
1171 fn set_print_context(&self) {
1172 SERIALIZATION_CONTEXT.with(|ctx| {
1173 ctx.borrow_mut().pointer_width = Some(self.system_info.cpu.pointer_width());
1174 });
1175 }
1176}