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