Skip to main content

minidump_processor/
process_state.rs

1// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3
4//! The state of a process.
5
6use 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) // skip header
170            .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/// Info about an exception that may have occurred
199///
200/// May not be available if the minidump wasn't triggered by an exception, or if required
201/// info about the exception is missing
202#[derive(Debug, Clone)]
203pub struct ExceptionInfo {
204    /// a `CrashReason` describing the crash reason.
205    pub reason: CrashReason,
206    /// The memory address implicated in the crash.
207    ///
208    /// If the crash reason implicates memory, this is the memory address that
209    /// caused the crash. For data access errors this will be the data address
210    /// that caused the fault. For code errors, this will be the address of the
211    /// instruction that caused the fault.
212    pub address: Address,
213    /// In certain circumstances, the previous `address` member may report a sub-optimal value
214    /// for debugging purposes. If instruction analysis is able to successfully determine a
215    /// more helpful value, it will be reported here.
216    pub adjusted_address: Option<AdjustedAddress>,
217    /// A string representing the crashing instruction (if available)
218    pub instruction_str: Option<String>,
219    /// A list of booleans representing properties of crashing instruction (if availaable)
220    pub instruction_properties: Option<InstructionProperties>,
221    /// A list of memory accesses performed by crashing instruction (if available)
222    pub memory_access_list: Option<MemoryAccessList>,
223    /// Whether the instruction pointer is updated by crashing instruction (if available)
224    pub instruction_pointer_update: Option<InstructionPointerUpdate>,
225    /// Possible valid addresses which are one flipped bit away from the crashing address or adjusted address.
226    ///
227    /// The original address was possibly the result of faulty hardware, alpha particles, etc.
228    pub possible_bit_flips: Vec<PossibleBitFlip>,
229    /// Whether the crash reason/address is inconsistent with crashing instruction and memory info
230    pub inconsistencies: Vec<CrashInconsistency>,
231}
232
233/// Info about a memory address that was adjusted from its reported value
234///
235/// There will be situations where the memory address reported by the OS is sub-optimal for
236/// debugging purposes, such as when an array is accidently indexed into with a null pointer base,
237/// at which point the address might read something like `0x00001000` when the more-useful address
238/// would just be zero.
239///
240/// If such a correction was made, this will be included in `ExceptionInfo`.
241#[derive(Debug, Clone, PartialEq, Eq)]
242pub enum AdjustedAddress {
243    /// The original access was an Amd64 "non-canonical" address; actual address is provided here.
244    NonCanonical(Address),
245    /// The base pointer was null; offset from base is provided here.
246    NullPointerWithOffset(AddressOffset),
247}
248
249#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize)]
250pub struct BitFlipDetails {
251    /// The bit flip caused a non-canonical address access.
252    pub was_non_canonical: bool,
253    /// The corrected address is null.
254    pub is_null: bool,
255    /// The original address was fairly low.
256    ///
257    /// This is only populated if `is_null` is true, and may indicate that a bit flip didn't occur
258    /// (and the original value was merely a small value which is more likely to be produced by
259    /// booleans, iteration, etc).
260    pub was_low: bool,
261    /// The number of registers near the corrected address.
262    ///
263    /// This will only be populated for sufficiently high addresses (to avoid high false positive
264    /// rates).
265    pub nearby_registers: u32,
266    /// There are poison patterns in one or more registers.
267    ///
268    /// This may indicate that a bit flip _didn't_ occur, and instead there was a UAF.
269    pub poison_registers: bool,
270}
271
272mod confidence {
273    /* The hat from which these numbers are drawn.
274           .~~~~`\~~\
275          ;       ~~ \
276          |           ;
277      ,--------,______|---.
278     /          \-----`    \
279     `.__________`-_______-'
280    */
281
282    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    // TODO: do we want this at all, vs Option<f32> for confidence?
291    // The only problem is there may not be a good way to display this (i.e. omitting a confidence
292    // would potentially make those seem _stronger_).
293    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    // Detractors
300    pub const POISON: f32 = MEDIUM;
301    pub const ORIGINAL_LOW: f32 = MEDIUM;
302}
303
304impl BitFlipDetails {
305    /// Calculate a confidence level between 0 and 1 pertaining to the bit flip likelihood.
306    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    /// The un-bit-flipped (potentially correct) address.
340    pub address: Address,
341    /// The register which held the bit-flipped address, if from a register at all.
342    pub source_register: Option<&'static str>,
343    /// Heuristics related to the determination of the bit flip.
344    pub details: BitFlipDetails,
345    /// A confidence level for the bit flip, derived from the details.
346    pub confidence: Option<f32>,
347}
348
349/// The maximum distance between addresses to consider them "nearby" when calculating bit flip
350/// heuristics with regard to register contents.
351const NEARBY_REGISTER_DISTANCE: u64 = 1 << 12;
352
353/// The cutoff for addresses considered "low".
354const 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            // Don't calculate nearby registers for low addresses, there will be a high false
392            // positive rate.
393            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                    // Poison patterns from
404                    // https://searchfox.org/mozilla-central/rev/3002762e41363de8ee9ca80196d55e79651bcb6b/js/src/util/Poison.h#52
405                    //
406                    // 0xa5 from xmalloc/jemalloc
407                    // 0xe5 from mozilla jemalloc
408                    // (https://searchfox.org/mozilla-central/source/memory/build/mozjemalloc.cpp#1412)
409                    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/// The state of a process as recorded by a `Minidump`.
457#[derive(Debug, Clone)]
458pub struct ProcessState {
459    /// The PID of the process.
460    pub process_id: Option<u32>,
461    /// When the minidump was written.
462    pub time: SystemTime,
463    /// When the process started, if available
464    pub process_create_time: Option<SystemTime>,
465    /// Known code signing certificates (module name => cert name)
466    pub cert_info: HashMap<String, String>,
467    /// Info about the exception that triggered the dump (if one did)
468    pub exception_info: Option<ExceptionInfo>,
469    /// A string describing an assertion that was hit, if present.
470    pub assertion: Option<String>,
471    /// The index of the thread that requested a dump be written.
472    /// If a dump was produced as a result of a crash, this
473    /// will point to the thread that crashed.  If the dump was produced as
474    /// by user code without crashing, and the dump contains extended Breakpad
475    /// information, this will point to the thread that requested the dump.
476    /// If the dump was not produced as a result of an exception and no
477    /// extended Breakpad information is present, this field will be
478    /// `None`.
479    pub requesting_thread: Option<usize>,
480    /// Stacks for each thread (except possibly the exception handler
481    /// thread) at the time of the crash.
482    pub threads: Vec<CallStack>,
483    // TODO:
484    // thread_memory_regions
485    /// Information about the system on which the minidump was written.
486    pub system_info: SystemInfo,
487    /// Linux Standard Base Info
488    pub linux_standard_base: Option<LinuxStandardBase>,
489    /// Linux Proc Limits
490    pub linux_proc_limits: Option<LinuxProcLimits>,
491    pub mac_crash_info: Option<Vec<RawMacCrashInfo>>,
492    pub mac_boot_args: Option<MinidumpMacBootargs>,
493    /// The modules that were loaded into the process represented by the
494    /// `ProcessState`.
495    pub modules: MinidumpModuleList,
496    pub unloaded_modules: MinidumpUnloadedModuleList,
497    pub handles: Option<MinidumpHandleDataStream>,
498    // modules_without_symbols
499    // modules_with_corrupt_symbols
500    // exploitability
501    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 &reg 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    /// `true` if the minidump was written in response to a process crash.
537    pub fn crashed(&self) -> bool {
538        self.exception_info.is_some()
539    }
540    /// Write a human-readable description of the process state to `f`.
541    ///
542    /// This is very verbose, it implements the output format used by
543    /// minidump_stackwalk.
544    pub fn print<T: Write>(&self, f: &mut T) -> io::Result<()> {
545        self.print_internal(f, false)
546    }
547
548    /// Write a brief human-readable description of the process state to `f`.
549    ///
550    /// Only includes the summary at the top and a backtrace of the crashing thread.
551    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                // Sort by confidence (descending), then address (ascending).
655                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        // We're done if this is a brief report!
758        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                // Don't print the requesting thread again,
765                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            // TODO: missing symbols, corrupt symbols
788            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    /// Outputs json in a schema compatible with mozilla's Socorro crash reporting servers.
874    ///
875    /// See the top level documentation of this library for the stable JSON schema.
876    pub fn print_json<T: Write>(&self, f: &mut T, pretty: bool) -> Result<(), serde_json::Error> {
877        // See ../json-schema.md for details on this format.
878
879        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            // Currently unused, we either produce no output or successful output.
889            // OK | ERROR_* | SYMBOL_SUPPLIER_INTERRUPTED
890            "status": "OK",
891            "system_info": {
892                // Linux | Windows NT | Mac OS X
893                "os": sys.os.long_name(),
894                "os_ver": sys.format_os_version(),
895                // x86 | amd64 | arm | ppc | sparc
896                "cpu_arch": sys.cpu.to_string(),
897                "cpu_info": sys.cpu_info,
898                "cpu_count": sys.cpu_count,
899                // optional, print as hex string
900                "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                            // Only add the `is_likely_guard_page` field when it is affirmative.
926                            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                // thread index | null
961                "crashing_thread": self.requesting_thread,
962                "assertion": self.assertion,
963            },
964            // optional
965            "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            // optional
972            "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            // optional
982            "mac_crash_info": self.mac_crash_info.as_ref().map(|info| json!({
983                "num_records": info.len(),
984                // All of these fields are optional
985                "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            // optional
998            "mac_boot_args": self.mac_boot_args.as_ref().map(|info| info.bootargs.as_ref()),
999
1000            // optional
1001            "linux_memory_map_count": self.linux_memory_map_count,
1002
1003            // the first module is always the main one
1004            "main_module": 0,
1005            // [UNSTABLE:evil_json]
1006            "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                // Gather statistics on the module's symbols
1012                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                // Resolve debug file and debug id from extra debug info if present
1017                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                // Only consider the symbols "missing" if the symbolizer
1028                // actually has statistics on them (implying it *tried* to
1029                // get the symbols but failed.)
1030                let missing_symbols = had_stats && !stats.loaded_symbols;
1031                json!({
1032                    "base_addr": json_hex(module.raw.base_of_image),
1033                    // filename | empty string
1034                    "debug_file": basename(debug_file),
1035                    // [[:xdigit:]]{33} | empty string
1036                    "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                    // [UNSTABLE:evil_json]
1042                    "cert_subject": self.cert_info.get(name),
1043
1044                    // These are all just metrics for debugging minidump-processor's execution
1045
1046                    // optional, if mdsw looked for the file and it doesn't exist
1047                    "missing_symbols": missing_symbols,
1048                    // optional, if mdsw looked for the file and it does exist
1049                    "loaded_symbols": stats.loaded_symbols,
1050                    // optional, if mdsw found a file that has parse errors
1051                    "corrupt_symbols": stats.corrupt_symbols,
1052                    // optional, url of symbol file
1053                    "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                // optional
1064                "last_error_value": thread.last_error_value.map(|error| error.to_string()),
1065                // optional
1066                "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                    // optional
1071                    "module": frame.module.as_ref().map(|module| basename(&module.name)),
1072                    // optional
1073                    "function": frame.function_name,
1074                    // optional
1075                    "file": frame.source_file_name,
1076                    // optional
1077                    "line": frame.source_line,
1078                    "offset": json_hex(frame.instruction),
1079                    // optional
1080                    "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                    // optional
1092                    "module_offset": frame
1093                        .module
1094                        .as_ref()
1095                        .map(|module| frame.instruction - module.raw.base_of_image)
1096                        .map(json_hex),
1097                    // optional
1098                    "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                    // optional
1107                    "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                    // none | scan | cfi_scan | frame_pointer | cfi | context | prewalked
1113                    "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            // Copy the crashing thread into a top-level "crashing_thread" field and:
1133            // * Add a "threads_index" field to indicate which thread it was
1134            // * Add a "registers" field to its first frame
1135            //
1136            // Note that we currently make crashing_thread a strict superset
1137            // of a normal "threads" entry, while the original schema strips
1138            // many of the fields here. We don't to keep things more uniform.
1139
1140            // We can't do any of this work if we don't have at least one frame.
1141            if let Some(f) = self.threads[requesting_thread].frames.first() {
1142                let registers = json_registers(&f.context);
1143
1144                // Yuck, spidering through json...
1145                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}