minidump_processor/
processor.rs

1// Copyright 2015 Ted Mielczarek. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::ops::{Deref, RangeInclusive};
6use std::path::Path;
7use std::sync::{Arc, Mutex};
8use std::time::{Duration, SystemTime};
9
10use minidump::system_info::PointerWidth;
11use minidump::*;
12use minidump_unwind::{
13    walk_stack, CallStack, CallStackInfo, FrameTrust, StackFrame, SymbolProvider, SystemInfo,
14};
15
16use crate::op_analysis::MemoryAddressInfo;
17use crate::process_state::{LinuxStandardBase, ProcessState};
18use crate::{
19    arg_recovery, evil, AdjustedAddress, CrashInconsistency, LinuxProcLimits, LinuxProcStatus,
20};
21
22/// Configuration of the processor's exact behaviour.
23///
24/// This can be used to either:
25///
26/// * enable extra features that are disabled by default
27/// * lock in the features you want enabled to minimize future changes
28///
29/// All fields are `pub`, but the type is `non_exhaustive`.
30/// Recommended usage is to call one of the constructors to get a baseline
31/// set of features, and then manually set any values you particularly care about.
32///
33/// If we decide an unstable feature exposed by these flags is a bad idea,
34/// we may remove its functionality and turn it into a noop, but the flag
35/// will remain to avoid breaking code. Similarly, if a feature seems to be
36/// too bloated, its implementation may be hidden behind a cargo feature
37/// flag, producing a similar result if that feature is statically disabled.
38///
39/// In either of these cases, a `warn` diagnostic will be emitted if you
40/// try to use request a feature whose implementation does not exist.
41///
42/// [`process_minidump`][] uses [`ProcessorOptions::stable_basic`][], which
43/// is also exposed as [`Default::default`].
44///
45/// ## Example:
46///
47/// ```
48/// use minidump_processor::ProcessorOptions;
49///
50/// // Happy with the default-enabled features
51/// let mut options = ProcessorOptions::stable_basic();
52/// // But specifically want this cool unstable feature
53/// options.recover_function_args = true;
54/// ```
55///
56#[derive(Debug, Clone)]
57#[non_exhaustive]
58pub struct ProcessorOptions<'a> {
59    /// **\[UNSTABLE\]** The evil "raw json" mozilla's legacy infrastructure relies on.
60    ///
61    /// Please don't use this. If you have to use this, you know who you are.
62    pub evil_json: Option<&'a Path>,
63
64    /// **\[UNSTABLE\]** Whether to try to heuristically recover function arguments in backtraces.
65    ///
66    /// Currently this only work for x86, and assumes everything is either cdecl or thiscall
67    /// (inferred from whether the symbol name looks like a static function or a method).
68    pub recover_function_args: bool,
69
70    /// Set this value to subscribe to live statistics during the processing.
71    ///
72    /// See [`PendingProcessorStats`] and [`PendingProcessorStatSubscriptions`].
73    pub stat_reporter: Option<&'a PendingProcessorStats>,
74}
75
76/// A subscription to various live updates during minidump processing.
77///
78/// Construct it with [`PendingProcessorStats::new`] and pass it into
79/// [`ProcessorOptions::stat_reporter`]. The type internally handles
80/// concurrency and can be safely sent or shared between threads.
81///
82/// The type can't be cloned just because we don't want to guarantee
83/// how the atomics are implemented. Wrap it in an Arc if you want
84/// shared access for yourself.
85#[derive(Debug)]
86pub struct PendingProcessorStats {
87    /// The stats we will track
88    subscriptions: PendingProcessorStatSubscriptions,
89    /// The actual computed stats
90    stats: Arc<Mutex<PendingProcessorStatsInner>>,
91}
92
93/// An implementation detail of PendingProcessorStats, where all the
94/// actual stats are recorded. Can be changed without anything caring.
95#[derive(Default, Debug, Clone)]
96struct PendingProcessorStatsInner {
97    /// How many threads have been processed
98    num_threads_processed: u64,
99    /// How many threads there are in total (redundant, but convenient)
100    total_threads: u64,
101    /// The number of frames that have been walked
102    num_frames_processed: u64,
103    /// Frames that have been walked since you last queried this stat
104    new_walked_frames: Vec<WalkedFrame>,
105    /// The partial ProcessState, before stackwalking
106    unwalked_result: Option<ProcessState>,
107}
108
109#[derive(Debug, Clone, Default)]
110#[non_exhaustive]
111/// Live updates you want to subscribe to during the processing.
112///
113/// Pass this into [`PendingProcessorStats::new`] to configure it.
114pub struct PendingProcessorStatSubscriptions {
115    /// Subscribe to stats on how many threads have been processed.
116    ///
117    /// This can be used to give a progress estimate.
118    ///
119    /// The values can be read with [`PendingProcessorStats::get_thread_count`].
120    pub thread_count: bool,
121    /// Subscribe to stats on how many frames have been processed.
122    ///
123    /// This can be used to give a progress estimate.
124    ///
125    /// The value can be read with [`PendingProcessorStats::get_frame_count`].
126    pub frame_count: bool,
127    /// Subscribe to a copy of the ProcessState before stackwalking (or symbolication).
128    ///
129    /// This can be used to provide the quick and easy results while the expensive
130    /// stackwalker has to go off and start doing file or network i/o for symbols.
131    ///
132    /// The values can be read with [`PendingProcessorStats::take_unwalked_result`].
133    pub unwalked_result: bool,
134    /// Subscribe to live StackFrame results.
135    ///
136    /// This can be used to update [`PendingProcessorStatSubscriptions::unwalked_result`]
137    /// as the stackwalker makes progress. How useful/smooth this is depends on the input.
138    /// If the biggest symbol file is the first frame of the stack, the walker may hang at 0%
139    /// progress for a long time and then suddenly jump to 100% instantly, as the
140    /// first dependency gets resolved last.
141    ///
142    /// The values can be read with [`PendingProcessorStats::drain_new_frames`].
143    pub live_frames: bool,
144}
145
146/// A StackFrame that has been walked, with metadata on which thread it's part of,
147/// and which frame of that thread it is.
148///
149/// This is the payload for [`PendingProcessorStatSubscriptions::live_frames`].
150#[derive(Debug, Clone)]
151pub struct WalkedFrame {
152    /// The thread that this was, the index corresponds to [`ProcessState::threads`].
153    pub thread_idx: usize,
154    /// The frame that this was, the index corresponds to [`CallStack::frames`].
155    pub frame_idx: usize,
156    /// The actual walked and symbolicated StackFrame. Some post-processing analysis
157    /// may be missing, so these results should be discarded once you have the
158    /// final [`ProcessState`].
159    pub frame: StackFrame,
160}
161
162impl PendingProcessorStats {
163    /// Subscribe to the given stats.
164    ///
165    /// Pass this into [`ProcessorOptions::stat_reporter`] to use it.
166    pub fn new(subscriptions: PendingProcessorStatSubscriptions) -> Self {
167        Self {
168            subscriptions,
169            stats: Default::default(),
170        }
171    }
172
173    /// Gets (processed_thread_count, total_thread_count).
174    ///
175    /// This will panic if you didn't subscribe to
176    /// [`PendingProcessorStatSubscriptions::thread_count`].
177    pub fn get_thread_count(&self) -> (u64, u64) {
178        assert!(
179            self.subscriptions.thread_count,
180            "tried to get thread count stats, but wasn't subscribed!"
181        );
182        let stats = self.stats.lock().unwrap();
183        (stats.num_threads_processed, stats.total_threads)
184    }
185
186    /// Get count of walked frames.
187    ///
188    /// This will panic if you didn't subscribe to
189    /// [`PendingProcessorStatSubscriptions::frame_count`].
190    pub fn get_frame_count(&self) -> u64 {
191        assert!(
192            self.subscriptions.frame_count,
193            "tried to get frame count stats, but wasn't subscribed!"
194        );
195        let stats = self.stats.lock().unwrap();
196        stats.num_frames_processed
197    }
198
199    /// Get all the new walked frames since this method was last called.
200    ///
201    /// This operates via callback to allow implementation flexibility.
202    ///
203    /// This will panic if you didn't subscribe to
204    /// [`PendingProcessorStatSubscriptions::live_frames`].
205    pub fn drain_new_frames(&self, mut callback: impl FnMut(WalkedFrame)) {
206        assert!(
207            self.subscriptions.live_frames,
208            "tried to get new frames, but wasn't subscribed!"
209        );
210        let mut stats = self.stats.lock().unwrap();
211        for frame in stats.new_walked_frames.drain(..) {
212            callback(frame);
213        }
214    }
215
216    /// Get the unwalked [`ProcessState`], if it has been computed.
217    ///
218    /// This will yield `Some` exactly once.
219    ///
220    /// This will panic if you didn't subscribe to
221    /// [`PendingProcessorStatSubscriptions::unwalked_result`].
222    pub fn take_unwalked_result(&self) -> Option<ProcessState> {
223        assert!(
224            self.subscriptions.unwalked_result,
225            "tried to get unwalked result, but wasn't subscribed!"
226        );
227        let mut stats = self.stats.lock().unwrap();
228        stats.unwalked_result.take()
229    }
230
231    /// Record how many threads there are in total.
232    pub(crate) fn set_total_threads(&self, total_threads: u64) {
233        // Only bother doing this if the user cares
234        if self.subscriptions.thread_count {
235            let mut stats = self.stats.lock().unwrap();
236            stats.total_threads = total_threads;
237        }
238    }
239
240    /// Record that a thread has been processed.
241    pub(crate) fn inc_processed_threads(&self) {
242        // Only bother doing this if the user cares
243        if self.subscriptions.thread_count {
244            let mut stats = self.stats.lock().unwrap();
245            stats.num_threads_processed += 1;
246        }
247    }
248
249    /// Record that this frame has been walked.
250    pub(crate) fn add_walked_frame(&self, thread_idx: usize, frame_idx: usize, frame: &StackFrame) {
251        // Only bother doing this if the user cares
252        if self.subscriptions.live_frames || self.subscriptions.frame_count {
253            let mut stats = self.stats.lock().unwrap();
254            // Once we're in here it's easier to update this then check if they care
255            stats.num_frames_processed += 1;
256            // But this one is worth rechecking
257            if self.subscriptions.live_frames {
258                stats.new_walked_frames.push(WalkedFrame {
259                    thread_idx,
260                    frame_idx,
261                    frame: frame.clone(),
262                });
263            }
264        }
265    }
266
267    /// Record this unwalked [`ProcessState`].
268    pub(crate) fn add_unwalked_result(&self, state: &ProcessState) {
269        // Only bother doing this if the user cares
270        if self.subscriptions.unwalked_result {
271            let mut stats = self.stats.lock().unwrap();
272            stats.unwalked_result = Some(state.clone());
273        }
274    }
275}
276
277impl ProcessorOptions<'_> {
278    /// "Do the normal stuff everyone should want"
279    ///
280    /// * `evil_json: None`
281    /// * `recover_function_args: false`
282    ///
283    /// Unlike stable_all, you shouldn't expect this to change its results much.
284    ///
285    /// It will specifically always try to:
286    ///
287    /// * Perform full backtraces and symbolication of every thread.
288    /// * Produce detailed system info (OS, Cpu, Versions...)
289    /// * Produce detailed crash info (Crashing thread, crash address, formatted error...)
290    /// * List loaded and unloaded modules
291    pub fn stable_basic() -> Self {
292        ProcessorOptions {
293            evil_json: None,
294            recover_function_args: false,
295            stat_reporter: None,
296        }
297    }
298
299    /// "Turn all the stable features on"
300    ///
301    /// * `evil_json: None`
302    /// * `recover_function_args: false`
303    ///
304    /// (At this precise moment this is identical to stable_basic, but may diverge
305    /// as we introduce more features.)
306    ///
307    /// Everything included by stable_basic, but willing to enable more interesting
308    /// features and spend extra time trying to find extra insights. This is the default
309    /// place that unstable features will "graduate" to when they're deemed good enough.
310    pub fn stable_all() -> Self {
311        ProcessorOptions {
312            evil_json: None,
313            recover_function_args: false,
314            stat_reporter: None,
315        }
316    }
317
318    /// "Turn EVERYTHING on, even the experimental stuff!"
319    ///
320    /// * `evil_json: None`
321    /// * `recover_function_args: true`
322    ///
323    /// (evil_json is still "disabled" because you need to give it needs a path.)
324    ///
325    /// Some of this stuff can be really jank, use at your own risk!
326    pub fn unstable_all() -> Self {
327        ProcessorOptions {
328            evil_json: None,
329            recover_function_args: true,
330            stat_reporter: None,
331        }
332    }
333
334    /// Check if any of the enabled features are deprecated or disabled
335    /// and emit warnings if they are.
336    fn check_deprecated_and_disabled(&self) {
337        // Currently nothing is deprecated / disableable, but here's the template.
338
339        /*
340        use log::warn;
341
342        if self.my_bad_feature {
343            warn!("Deprecated ProcessorOption my_bad_feature has been removed and does nothing.")
344        }
345
346        if !cfg!(feature = "my-optional-feature") && self.my_optional_feature {
347            warn!("Disabled ProcessorOption my_optional_feature must be enabled via cargo.")
348        }
349        */
350    }
351}
352
353impl Default for ProcessorOptions<'_> {
354    fn default() -> Self {
355        Self::stable_basic()
356    }
357}
358
359/// An error encountered during minidump processing.
360#[derive(Clone, Debug, thiserror::Error)]
361pub enum ProcessError {
362    #[error("Failed to read minidump")]
363    MinidumpReadError(#[from] minidump::Error),
364    #[error("An unknown error occurred")]
365    UnknownError,
366    #[error("The system information stream was not found")]
367    MissingSystemInfo,
368    #[error("The thread list stream was not found")]
369    MissingThreadList,
370}
371
372impl ProcessError {
373    /// Returns just the name of the error, as a more human-friendly version of
374    /// an error-code for error logging.
375    pub fn name(&self) -> &'static str {
376        match self {
377            ProcessError::MinidumpReadError(_) => "MinidumpReadError",
378            ProcessError::UnknownError => "UnknownError",
379            ProcessError::MissingSystemInfo => "MissingSystemInfo",
380            ProcessError::MissingThreadList => "MissingThreadList",
381        }
382    }
383}
384
385/// Unwind all threads in `dump` and return a report as a `ProcessState`.
386///
387/// This is equivalent to [`process_minidump_with_options`] with
388/// [`ProcessorOptions::stable_basic`][].
389///
390/// # Examples
391///
392/// ```
393/// use minidump::Minidump;
394/// use std::path::PathBuf;
395/// use breakpad_symbols::{Symbolizer, SimpleSymbolSupplier};
396/// use minidump_processor::ProcessError;
397///
398/// #[tokio::main]
399/// async fn main() -> Result<(), ProcessError> {
400///     # std::env::set_current_dir(env!("CARGO_MANIFEST_DIR"));
401///     let mut dump = Minidump::read_path("../testdata/test.dmp")?;
402///     let supplier = SimpleSymbolSupplier::new(vec!(PathBuf::from("../testdata/symbols")));
403///     let symbolizer = Symbolizer::new(supplier);
404///     let state = minidump_processor::process_minidump(&mut dump, &symbolizer).await?;
405///     assert_eq!(state.threads.len(), 2);
406///     println!("Processed {} threads", state.threads.len());
407///     Ok(())
408/// }
409/// ```
410pub async fn process_minidump<'a, T, P>(
411    dump: &Minidump<'a, T>,
412    symbol_provider: &P,
413) -> Result<ProcessState, ProcessError>
414where
415    T: Deref<Target = [u8]> + 'a,
416    P: SymbolProvider + Sync,
417{
418    // No Evil JSON Here!
419    process_minidump_with_options(dump, symbol_provider, ProcessorOptions::default()).await
420}
421
422/// Get the microcode version from linux cpu info and evil options.
423fn get_microcode_version(linux_cpu_info: &MinidumpLinuxCpuInfo, evil: &evil::Evil) -> Option<u64> {
424    linux_cpu_info
425        .iter()
426        .find_map(|(key, val)| {
427            if key.as_bytes() == b"microcode" {
428                val.to_str().ok()
429            } else {
430                None
431            }
432        })
433        .or(evil.cpu_microcode_version.as_deref())
434        .and_then(|val| val.strip_prefix("0x"))
435        .and_then(|val| u64::from_str_radix(val, 16).ok())
436}
437
438/// Process `dump` with the given options and return a report as a `ProcessState`.
439///
440/// See [`ProcessorOptions`][] for details on the specific features that can be
441/// enabled and how to choose them.
442pub async fn process_minidump_with_options<'a, T, P>(
443    dump: &Minidump<'a, T>,
444    symbol_provider: &P,
445    options: ProcessorOptions<'_>,
446) -> Result<ProcessState, ProcessError>
447where
448    T: Deref<Target = [u8]> + 'a,
449    P: SymbolProvider + Sync,
450{
451    let info = MinidumpInfo::new(dump, options)?;
452
453    let mut exception_details = info.get_exception_details();
454
455    if let Some(details) = &mut exception_details {
456        info.check_for_bitflips(details);
457        info.check_for_guard_pages(details);
458        info.check_for_crash_inconsistencies(details);
459    }
460    info.into_process_state(dump, symbol_provider, exception_details)
461        .await
462}
463
464struct MinidumpInfo<'a> {
465    options: ProcessorOptions<'a>,
466    evil: crate::evil::Evil,
467    thread_list: MinidumpThreadList<'a>,
468    thread_names: MinidumpThreadNames,
469    dump_system_info: MinidumpSystemInfo,
470    linux_standard_base: Option<LinuxStandardBase>,
471    linux_proc_status: Option<LinuxProcStatus>,
472    linux_proc_limits: Option<LinuxProcLimits>,
473    system_info: SystemInfo,
474    mac_crash_info: Option<Vec<RawMacCrashInfo>>,
475    mac_boot_args: Option<MinidumpMacBootargs>,
476    misc_info: Option<MinidumpMiscInfo>,
477    dump_thread_id: Option<u32>,
478    requesting_thread_id: Option<u32>,
479    modules: MinidumpModuleList,
480    unloaded_modules: MinidumpUnloadedModuleList,
481    memory_list: UnifiedMemoryList<'a>,
482    /*
483    memory_info_list: Option<MinidumpMemoryInfoList<'a>>,
484    linux_maps: Option<MinidumpLinuxMaps<'a>>,
485    */
486    linux_memory_map_count: Option<usize>,
487    memory_info: UnifiedMemoryInfoList<'a>,
488    handle_data_stream: Option<MinidumpHandleDataStream>,
489    exception: Option<MinidumpException<'a>>,
490    //exception_details: Option<ExceptionDetails<'a>>,
491    soft_errors: Option<serde_json::Value>,
492}
493
494impl<'a> MinidumpInfo<'a> {
495    pub fn new<T: Deref<Target = [u8]> + 'a>(
496        dump: &'a Minidump<'a, T>,
497        options: ProcessorOptions<'a>,
498    ) -> Result<Self, ProcessError> {
499        options.check_deprecated_and_disabled();
500
501        // Get the evil JSON file (thread names, module certificates, etc)
502        let evil = options
503            .evil_json
504            .and_then(evil::handle_evil)
505            .unwrap_or_default();
506
507        // Thread list is required for processing.
508        let thread_list = dump
509            .get_stream::<MinidumpThreadList>()
510            .or(Err(ProcessError::MissingThreadList))?;
511
512        let num_threads = thread_list.threads.len() as u64;
513        if let Some(reporter) = options.stat_reporter {
514            reporter.set_total_threads(num_threads);
515        }
516
517        // Try to get thread names, but it's only a nice-to-have.
518        let thread_names = dump
519            .get_stream::<MinidumpThreadNames>()
520            .unwrap_or_else(|_| MinidumpThreadNames::default());
521
522        // System info is required for processing.
523        let dump_system_info = dump
524            .get_stream::<MinidumpSystemInfo>()
525            .or(Err(ProcessError::MissingSystemInfo))?;
526
527        let (os_version, os_build) = dump_system_info.os_parts();
528
529        let linux_standard_base = dump.get_stream::<MinidumpLinuxLsbRelease>().ok();
530        let linux_cpu_info = dump
531            .get_stream::<MinidumpLinuxCpuInfo>()
532            .unwrap_or_default();
533        let _linux_environ = dump.get_stream::<MinidumpLinuxEnviron>().ok();
534        let linux_proc_status = dump.get_stream::<MinidumpLinuxProcStatus>().ok();
535        let linux_proc_limits = dump.get_stream::<MinidumpLinuxProcLimits>().ok();
536        let soft_errors = dump.get_stream::<MinidumpSoftErrors>().ok();
537
538        // Extract everything we care about from linux streams here.
539        // We don't eagerly process them in the minidump crate because there's just
540        // tons of random information in there and it's not obvious what anyone
541        // would care about. So just providing an iterator and letting minidump-processor
542        // pull out the things it cares about is simple and effective.
543
544        let cpu_microcode_version = get_microcode_version(&linux_cpu_info, &evil);
545
546        let linux_standard_base = linux_standard_base.map(LinuxStandardBase::from);
547        let linux_proc_status = linux_proc_status.map(LinuxProcStatus::from);
548        let linux_proc_limits = linux_proc_limits.map(LinuxProcLimits::from);
549        let soft_errors =
550            soft_errors.and_then(|se| serde_json::from_str::<serde_json::Value>(se.as_ref()).ok());
551
552        let cpu_info = dump_system_info
553            .cpu_info()
554            .map(|string| string.into_owned());
555
556        let system_info = SystemInfo {
557            os: dump_system_info.os,
558            os_version: Some(os_version),
559            os_build,
560            cpu: dump_system_info.cpu,
561            cpu_info,
562            cpu_microcode_version,
563            cpu_count: dump_system_info.raw.number_of_processors as usize,
564        };
565
566        let mac_crash_info = dump
567            .get_stream::<MinidumpMacCrashInfo>()
568            .ok()
569            .map(|info| info.raw);
570
571        let mac_boot_args = dump.get_stream::<MinidumpMacBootargs>().ok();
572
573        let misc_info = dump.get_stream::<MinidumpMiscInfo>().ok();
574        // If Breakpad info exists in dump, get dump and requesting thread ids.
575        let breakpad_info = dump.get_stream::<MinidumpBreakpadInfo>();
576        let (dump_thread_id, requesting_thread_id) = if let Ok(info) = breakpad_info {
577            (info.dump_thread_id, info.requesting_thread_id)
578        } else {
579            (None, None)
580        };
581        // Get assertion
582        let modules = match dump.get_stream::<MinidumpModuleList>() {
583            Ok(module_list) => module_list,
584            // Just give an empty list, simplifies things.
585            Err(_) => MinidumpModuleList::new(),
586        };
587        let unloaded_modules = match dump.get_stream::<MinidumpUnloadedModuleList>() {
588            Ok(module_list) => module_list,
589            // Just give an empty list, simplifies things.
590            Err(_) => MinidumpUnloadedModuleList::new(),
591        };
592        let memory_list = dump.get_memory().unwrap_or_default();
593        let memory_info_list = dump.get_stream::<MinidumpMemoryInfoList>().ok();
594        let linux_maps = dump.get_stream::<MinidumpLinuxMaps>().ok();
595        let linux_memory_map_count = linux_maps.clone().map(|maps| maps.memory_map_count());
596        let memory_info =
597            UnifiedMemoryInfoList::new(memory_info_list, linux_maps).unwrap_or_default();
598        let handle_data_stream = dump.get_stream::<MinidumpHandleDataStream>().ok();
599
600        // Get exception info if it exists.
601        let exception = dump.get_stream::<MinidumpException>().ok();
602
603        Ok(MinidumpInfo {
604            options,
605            evil,
606            thread_list,
607            thread_names,
608            dump_system_info,
609            linux_standard_base,
610            linux_proc_status,
611            linux_proc_limits,
612            system_info,
613            mac_crash_info,
614            mac_boot_args,
615            misc_info,
616            dump_thread_id,
617            requesting_thread_id,
618            modules,
619            unloaded_modules,
620            memory_list,
621            /*
622            memory_info_list: Option<MinidumpMemoryInfoList<'a>>,
623            linux_maps: Option<MinidumpLinuxMaps<'a>>,
624            */
625            memory_info,
626            linux_memory_map_count,
627            handle_data_stream,
628            exception,
629            //exception_details: None,
630            soft_errors,
631        })
632    }
633
634    /// Get details about the minidump exception, if available.
635    pub fn get_exception_details(&self) -> Option<ExceptionDetails<'a>> {
636        use crate::op_analysis::InstructionPointerUpdate;
637
638        let exception = self.exception.as_ref()?;
639
640        let reason = exception.get_crash_reason(self.system_info.os, self.system_info.cpu);
641        let address = exception.get_crash_address(self.system_info.os, self.system_info.cpu);
642
643        let stack_memory_ref = self
644            .thread_list
645            .get_thread(exception.get_crashing_thread_id())
646            .and_then(|thread| thread.stack_memory(&self.memory_list));
647
648        let context = exception.context(&self.dump_system_info, self.misc_info.as_ref());
649
650        let mut exception_info: Option<crate::ExceptionInfo> = None;
651        let mut instruction_registers: BTreeSet<&'static str> = Default::default();
652
653        // If we have a context, we can attempt to analyze the crashing thread's instructions
654        if let Some(context) = context.as_ref() {
655            match crate::op_analysis::analyze_thread_context(
656                context,
657                &self.memory_list,
658                stack_memory_ref,
659            ) {
660                Ok(op_analysis) => {
661                    let access_addresses =
662                        op_analysis.memory_access_list.as_ref().map(|access_list| {
663                            access_list
664                                .iter()
665                                .map(|access| access.address_info)
666                                .collect::<Vec<MemoryAddressInfo>>()
667                        });
668                    let addresses = access_addresses.map(|mut accesses| {
669                        match op_analysis.instruction_pointer_update {
670                            Some(InstructionPointerUpdate::Update { address_info }) => {
671                                accesses.push(address_info);
672                                accesses
673                            }
674                            _ => accesses,
675                        }
676                    });
677                    let addresses = addresses.as_deref();
678
679                    let adjusted_address = try_detect_null_pointer_in_disguise(addresses)
680                        .map(|offset| AdjustedAddress::NullPointerWithOffset(offset.into()))
681                        .or_else(|| {
682                            try_get_non_canonical_crash_address(
683                                &self.system_info,
684                                addresses,
685                                reason,
686                                address,
687                            )
688                            .map(|addr| AdjustedAddress::NonCanonical(addr.into()))
689                        });
690
691                    instruction_registers.clone_from(&op_analysis.registers);
692                    exception_info = Some(crate::ExceptionInfo::with_op_analysis(
693                        reason,
694                        address.into(),
695                        adjusted_address,
696                        op_analysis,
697                    ));
698                }
699                Err(e) => {
700                    tracing::warn!("failed to analyze the thread context: {e}");
701                }
702            }
703        }
704
705        let info =
706            exception_info.unwrap_or_else(|| crate::ExceptionInfo::new(reason, address.into()));
707
708        Some(ExceptionDetails {
709            info,
710            context,
711            instruction_registers,
712        })
713    }
714
715    /// Check for bit-flips of the exception address/instruction.
716    ///
717    /// Additional bit flip information will be added to `exception_details`.
718    pub fn check_for_bitflips(&self, exception_details: &mut ExceptionDetails<'a>) {
719        // Only check for bit-flips on 64-bit systems, as the large memory space makes
720        // false-positives less likely.
721        if self.system_info.cpu.pointer_width() != PointerWidth::Bits64 {
722            return;
723        }
724
725        // Do not check for bit-flips on 64-bit ARM systems as the lack of disassembly
726        // support can lead to false positives when dealing with near-NULL crashes
727        // caused by register + offset addressing with power-of-2 offsets. Remove this
728        // once issue #863 is fixed.
729        if self.system_info.cpu == system_info::Cpu::Arm64 {
730            return;
731        }
732
733        let info = &mut exception_details.info;
734
735        use bitflip::BitRange;
736        use memory_operation::MemoryOperation;
737        let bit_flip_address = match &info.adjusted_address {
738            // Use the non canonical address if present.
739            Some(AdjustedAddress::NonCanonical(v)) => Some((v.0, BitRange::Amd64NonCanonical)),
740            // If we think the address is a null pointer with an offset, don't try bit flips.
741            Some(AdjustedAddress::NullPointerWithOffset(_)) => None,
742            // Try the crashing address if no adjustments have been made.
743            None => Some((
744                info.address.0,
745                if self.system_info.cpu != system_info::Cpu::X86_64 {
746                    BitRange::All
747                } else {
748                    BitRange::Amd64Canononical
749                },
750            )),
751        };
752        if let Some((address, bit_range)) = bit_flip_address {
753            let memory_op = MemoryOperation::from_crash_reason(&info.reason);
754            info.possible_bit_flips = bitflip::try_bit_flips(
755                address,
756                None,
757                bit_range,
758                exception_details.context.as_deref(),
759                &self.memory_info,
760                memory_op,
761            );
762
763            // If we have an exception context, we can check the registers involved in the
764            // crashing instruction.
765            if let Some(context) = exception_details.context.as_deref() {
766                for reg in &exception_details.instruction_registers {
767                    if let Some(address) = context.get_register(reg) {
768                        info.possible_bit_flips.extend(bitflip::try_bit_flips(
769                            address,
770                            Some(reg),
771                            bit_range,
772                            Some(context),
773                            &self.memory_info,
774                            // We assume that a register that is causing a crash due to a flipped
775                            // bit has the same memory operation as the crash (i.e. we assume that
776                            // the base address, possibly combined with some offset, is still in
777                            // the same memory region).
778                            memory_op,
779                        ));
780                    }
781                }
782            }
783        }
784    }
785
786    /// Check whether memory accesses are accessing likely guard pages.
787    pub fn check_for_guard_pages(&self, exception_details: &mut ExceptionDetails<'a>) {
788        const GUARD_MEMORY_MAX_SIZE: u64 = 2 << 14;
789
790        if let Some(access_list) = &mut exception_details.info.memory_access_list {
791            for access in &mut access_list.accesses {
792                let Some(info) = self
793                    .memory_info
794                    .memory_info_at_address(access.address_info.address)
795                else {
796                    continue;
797                };
798                let Some(range) = info.memory_range() else {
799                    continue;
800                };
801
802                fn is_accessible(range: &UnifiedMemoryInfo) -> bool {
803                    range.is_readable() || range.is_writable() || range.is_executable()
804                }
805
806                let is_adjacent_to_accessible_memory = || {
807                    for region in self.memory_info.by_addr() {
808                        let Some(other_range) = region.memory_range() else {
809                            continue;
810                        };
811                        if other_range.end + 1 == range.start && is_accessible(&region) {
812                            return true;
813                        }
814                        if range.end + 1 == other_range.start {
815                            // At this point we won't encounter any other relevant regions as we're
816                            // iterating by address, so return.
817                            return is_accessible(&region);
818                        }
819                    }
820                    false
821                };
822
823                // As a heuristic, we consider any mapped memory to be a guard page if it:
824                // * has no permissions,
825                // * is less than `GUARD_MEMORY_MAX_SIZE`, and
826                // * is adjacent to a region with permissions.
827                if !is_accessible(&info)
828                    && range.end - range.start < GUARD_MEMORY_MAX_SIZE
829                    && is_adjacent_to_accessible_memory()
830                {
831                    access.address_info.is_likely_guard_page = true;
832                }
833            }
834        }
835    }
836
837    /// Check for inconsistencies between crash reason and crashing instruction
838    pub fn check_for_crash_inconsistencies(&self, exception_details: &mut ExceptionDetails) {
839        use minidump_common::errors::{
840            ExceptionCodeLinuxSigfpeKind as LinuxSigfpe,
841            ExceptionCodeMacArithmeticPpcType as MacArithPpc,
842            ExceptionCodeMacArithmeticX86Type as MacArithX86, ExceptionCodeWindows,
843            ExceptionCodeWindowsAccessType as WinAccess, NtStatusWindows,
844        };
845        use CrashInconsistency as Inconsistency;
846
847        let mut inconsistencies = Vec::new();
848        match exception_details.info.reason {
849            CrashReason::MacArithmeticPpc(MacArithPpc::EXC_PPC_ZERO_DIVIDE)
850            | CrashReason::MacArithmeticX86(MacArithX86::EXC_I386_DIV)
851            | CrashReason::LinuxSigfpe(LinuxSigfpe::FPE_INTDIV)
852            | CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_INT_DIVIDE_BY_ZERO) => {
853                if exception_details
854                    .info
855                    .instruction_properties
856                    .as_ref()
857                    .is_some_and(|p| !p.is_division)
858                {
859                    inconsistencies.push(Inconsistency::IntDivByZeroNotPossible);
860                }
861            }
862
863            CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_PRIV_INSTRUCTION)
864            | CrashReason::WindowsNtStatus(NtStatusWindows::STATUS_PRIVILEGED_INSTRUCTION) => {
865                if exception_details
866                    .info
867                    .instruction_properties
868                    .as_ref()
869                    .is_some_and(|p| !p.is_privileged)
870                {
871                    inconsistencies.push(Inconsistency::PrivInstructionCrashWithoutPrivInstruction);
872                }
873            }
874
875            CrashReason::WindowsAccessViolation(WinAccess::READ) => {
876                // To conclude with certainty that it is a non-canonical access exception,
877                // need to check if crashing access (READ 0xffffffffffffffff) is actually not among accesses
878                let is_gpf = represents_general_protection_fault(
879                    self.system_info.os,
880                    exception_details.info.reason,
881                    exception_details.info.address.0,
882                ) && self.crashing_access_is_not_among_accesses(exception_details);
883                let gpf_implies_non_canonical = exception_details
884                    .info
885                    .instruction_properties
886                    .as_ref()
887                    .is_some_and(|p| p.is_only_gpf_when_non_canonical);
888
889                if is_gpf {
890                    if gpf_implies_non_canonical
891                        && self.non_canonical_address_is_not_among_accesses(exception_details)
892                    {
893                        inconsistencies.push(Inconsistency::NonCanonicalAddressFalselyReported);
894                    }
895                } else {
896                    if self.crashing_access_is_not_among_accesses(exception_details) {
897                        inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
898                    }
899                    if self.access_is_not_violation(exception_details) {
900                        inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
901                    }
902                }
903            }
904            // We treat stack overflow like an access violation with unknown operation
905            // i.e. It is considered inconsistent if the instruction has no memory access
906            CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW)
907            | CrashReason::WindowsAccessViolation(WinAccess::WRITE)
908            | CrashReason::WindowsAccessViolation(WinAccess::EXEC) => {
909                if self.crashing_access_is_not_among_accesses(exception_details) {
910                    inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
911                }
912                if self.access_is_not_violation(exception_details) {
913                    inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
914                }
915            }
916
917            _ => (),
918        }
919        exception_details.info.inconsistencies = inconsistencies;
920    }
921
922    /// Returns whether non-canonical address is not used in accesses of crashing instruction
923    /// Returns false if there is insufficient information to determine
924    fn non_canonical_address_is_not_among_accesses(
925        &self,
926        exception_details: &ExceptionDetails,
927    ) -> bool {
928        use crate::op_analysis::InstructionPointerUpdate;
929        const NON_CANONICAL_RANGE: RangeInclusive<u64> =
930            0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
931        let info = &exception_details.info;
932        // `adjusted_address` might not show that there is non-canonical address if there is
933        // also a disguised null pointer, so we check `memory_access_list` directly
934        info.instruction_properties
935            .as_ref()
936            .is_some_and(|properties| properties.is_access_derivable)
937            && info.memory_access_list.as_ref().is_some_and(|access_list| {
938                !access_list
939                    .iter()
940                    .any(|access| NON_CANONICAL_RANGE.contains(&access.address_info.address))
941            })
942            && info
943                .instruction_pointer_update
944                .as_ref()
945                .is_some_and(|update| match update {
946                    InstructionPointerUpdate::Update { address_info } => {
947                        !NON_CANONICAL_RANGE.contains(&address_info.address)
948                    }
949                    InstructionPointerUpdate::NoUpdate => true,
950                })
951    }
952
953    /// Returns whether crashing access is not found in accesses of crashing instruction
954    /// Returns false if there is insufficient information to determine
955    fn crashing_access_is_not_among_accesses(&self, exception_details: &ExceptionDetails) -> bool {
956        use crate::op_analysis::MemoryAccessType;
957        use minidump_common::errors::{
958            ExceptionCodeWindows, ExceptionCodeWindowsAccessType as WinAccess,
959        };
960
961        let info = &exception_details.info;
962        let crash_address = info.address.0;
963        let has_access_derivable_instruction = info
964            .instruction_properties
965            .as_ref()
966            .is_some_and(|p| p.is_access_derivable);
967
968        if let Some(access_list) = &info.memory_access_list {
969            match info.reason {
970                CrashReason::WindowsAccessViolation(WinAccess::READ) => {
971                    has_access_derivable_instruction
972                        && !access_list.contains_access(crash_address, MemoryAccessType::Read)
973                        && !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
974                }
975                CrashReason::WindowsAccessViolation(WinAccess::WRITE) => {
976                    has_access_derivable_instruction
977                        && !access_list.contains_access(crash_address, MemoryAccessType::Write)
978                        && !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
979                }
980                CrashReason::WindowsAccessViolation(WinAccess::EXEC) => exception_details
981                    .context
982                    .as_ref()
983                    .is_some_and(|context| context.get_instruction_pointer() != crash_address),
984                CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW) => {
985                    has_access_derivable_instruction && access_list.is_empty()
986                }
987                _ => false,
988            }
989        } else {
990            false
991        }
992    }
993
994    /// Returns whether crashing access is allowed by the memory info
995    /// Returns false if there is insufficient information to determine
996    fn access_is_not_violation(&self, exception_details: &ExceptionDetails) -> bool {
997        {
998            use memory_operation::MemoryOperation;
999            use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
1000
1001            let info = &exception_details.info;
1002            let crash_address = info.address.0;
1003
1004            if let Some(mi) = self.memory_info.memory_info_at_address(crash_address) {
1005                matches!(
1006                    info.reason,
1007                    CrashReason::WindowsAccessViolation(WinAccess::READ)
1008                        | CrashReason::WindowsAccessViolation(WinAccess::WRITE)
1009                        | CrashReason::WindowsAccessViolation(WinAccess::EXEC)
1010                ) && MemoryOperation::from_crash_reason(&info.reason).is_allowed_for(&mi)
1011            } else {
1012                false
1013            }
1014        }
1015    }
1016
1017    pub async fn into_process_state<P, T>(
1018        self,
1019        dump: &Minidump<'a, T>,
1020        symbol_provider: &P,
1021        exception_details: Option<ExceptionDetails<'a>>,
1022    ) -> Result<ProcessState, ProcessError>
1023    where
1024        T: Deref<Target = [u8]> + 'a,
1025        P: SymbolProvider + Sync,
1026    {
1027        let crashing_thread_id = self.exception.as_ref().map(|e| e.get_crashing_thread_id());
1028
1029        let (exception_info, exception_context) = match exception_details {
1030            Some(details) => (Some(details.info), details.context),
1031            None => (None, None),
1032        };
1033
1034        let mut requesting_thread = None;
1035
1036        let threads = self
1037            .thread_list
1038            .threads
1039            .iter()
1040            .enumerate()
1041            .map(|(i, thread)| {
1042                let id = thread.raw.thread_id;
1043
1044                // If this is the thread that wrote the dump, skip processing it.
1045                if self.dump_thread_id == Some(id) {
1046                    return CallStack::with_info(id, CallStackInfo::DumpThreadSkipped);
1047                }
1048
1049                let thread_context =
1050                    thread.context(&self.dump_system_info, self.misc_info.as_ref());
1051                // If this thread requested the dump then try to use the exception
1052                // context if it exists. (prefer the exception stream's thread id over
1053                // the breakpad info stream's thread id.)
1054                let context = if crashing_thread_id.or(self.requesting_thread_id) == Some(id) {
1055                    requesting_thread = Some(i);
1056                    exception_context.as_deref().or(thread_context.as_deref())
1057                } else {
1058                    thread_context.as_deref()
1059                };
1060
1061                let name = self
1062                    .thread_names
1063                    .get_name(thread.raw.thread_id)
1064                    .map(|cow| cow.into_owned());
1065
1066                let (info, frames) = if let Some(context) = context {
1067                    let ctx = context.clone();
1068                    (
1069                        CallStackInfo::Ok,
1070                        vec![StackFrame::from_context(ctx, FrameTrust::Context)],
1071                    )
1072                } else {
1073                    (CallStackInfo::MissingContext, vec![])
1074                };
1075
1076                CallStack {
1077                    frames,
1078                    info,
1079                    thread_id: id,
1080                    thread_name: name,
1081                    last_error_value: thread.last_error(self.system_info.cpu, &self.memory_list),
1082                }
1083            })
1084            .collect();
1085
1086        // Collect up info on unimplemented/unknown modules
1087        let unknown_streams = dump.unknown_streams().collect();
1088        let unimplemented_streams = dump.unimplemented_streams().collect();
1089
1090        // Get symbol stats from the symbolizer
1091        let symbol_stats = symbol_provider.stats();
1092
1093        // Process ID & create time is optional.
1094        let process_id = if let Some(misc_info) = self.misc_info.as_ref() {
1095            misc_info.raw.process_id().cloned()
1096        } else {
1097            self.linux_proc_status
1098                .map(|linux_proc_status| linux_proc_status.pid)
1099        };
1100
1101        let process_create_time = if let Some(misc_info) = self.misc_info.as_ref() {
1102            misc_info.process_create_time()
1103        } else {
1104            None
1105        };
1106
1107        let mut state = ProcessState {
1108            process_id,
1109            time: SystemTime::UNIX_EPOCH + Duration::from_secs(dump.header.time_date_stamp as u64),
1110            process_create_time,
1111            cert_info: self.evil.certs,
1112            exception_info,
1113            assertion: None,
1114            requesting_thread,
1115            system_info: self.system_info,
1116            linux_standard_base: self.linux_standard_base,
1117            linux_proc_limits: self.linux_proc_limits,
1118            mac_crash_info: self.mac_crash_info,
1119            mac_boot_args: self.mac_boot_args,
1120            threads,
1121            modules: self.modules,
1122            unloaded_modules: self.unloaded_modules,
1123            handles: self.handle_data_stream,
1124            unknown_streams,
1125            unimplemented_streams,
1126            symbol_stats,
1127            linux_memory_map_count: self.linux_memory_map_count,
1128            soft_errors: self.soft_errors,
1129        };
1130
1131        // Report the unwalked result
1132        if let Some(reporter) = self.options.stat_reporter {
1133            reporter.add_unwalked_result(&state);
1134        }
1135
1136        {
1137            let memory_list = &self.memory_list;
1138            let modules = &state.modules;
1139            let system_info = &state.system_info;
1140            let unloaded_modules = &state.unloaded_modules;
1141            let options = &self.options;
1142
1143            futures_util::future::join_all(
1144                state
1145                    .threads
1146                    .iter_mut()
1147                    .zip(self.thread_list.threads.iter())
1148                    .enumerate()
1149                    .map(|(i, (stack, thread))| async move {
1150                        let mut stack_memory = thread.stack_memory(memory_list);
1151                        // Always choose the memory region that is referenced by the context,
1152                        // as the `exception_context` may refer to a different memory region than
1153                        // the `thread_context`, which in turn would fail to stack walk.
1154                        let stack_ptr = stack
1155                            .frames
1156                            .first()
1157                            .map(|ctx_frame| ctx_frame.context.get_stack_pointer());
1158                        if let Some(stack_ptr) = stack_ptr {
1159                            let contains_stack_ptr = stack_memory
1160                                .as_ref()
1161                                .and_then(|memory| memory.get_memory_at_address::<u64>(stack_ptr))
1162                                .is_some();
1163                            if !contains_stack_ptr {
1164                                stack_memory =
1165                                    memory_list.memory_at_address(stack_ptr).or(stack_memory);
1166                            }
1167                        }
1168
1169                        walk_stack(
1170                            i,
1171                            |frame_idx: usize, frame: &StackFrame| {
1172                                if let Some(reporter) = options.stat_reporter {
1173                                    reporter.add_walked_frame(i, frame_idx, frame);
1174                                }
1175                            },
1176                            stack,
1177                            stack_memory,
1178                            modules,
1179                            system_info,
1180                            symbol_provider,
1181                        )
1182                        .await;
1183
1184                        for frame in &mut stack.frames {
1185                            // If the frame doesn't have a loaded module, try to find an unloaded module
1186                            // that overlaps with its address range. The may be multiple, so record all
1187                            // of them and the offsets this frame has in them.
1188                            if frame.module.is_none() {
1189                                let mut offsets = BTreeMap::new();
1190                                for unloaded in
1191                                    unloaded_modules.modules_at_address(frame.instruction)
1192                                {
1193                                    let offset = frame.instruction - unloaded.raw.base_of_image;
1194                                    offsets
1195                                        .entry(unloaded.name.clone())
1196                                        .or_insert_with(BTreeSet::new)
1197                                        .insert(offset);
1198                                }
1199
1200                                frame.unloaded_modules = offsets;
1201                            }
1202                        }
1203
1204                        if options.recover_function_args {
1205                            arg_recovery::fill_arguments(stack, stack_memory);
1206                        }
1207
1208                        // Report the unwalked result
1209                        if let Some(reporter) = options.stat_reporter {
1210                            reporter.inc_processed_threads();
1211                        }
1212
1213                        stack
1214                    }),
1215            )
1216            .await
1217        };
1218
1219        let symbol_stats = symbol_provider.stats();
1220        state.symbol_stats = symbol_stats;
1221
1222        Ok(state)
1223    }
1224}
1225
1226impl crate::ExceptionInfo {
1227    fn new(reason: CrashReason, address: crate::Address) -> Self {
1228        Self {
1229            reason,
1230            address,
1231            adjusted_address: None,
1232            instruction_str: None,
1233            instruction_properties: None,
1234            memory_access_list: None,
1235            instruction_pointer_update: None,
1236            possible_bit_flips: Default::default(),
1237            inconsistencies: Default::default(),
1238        }
1239    }
1240
1241    fn with_op_analysis(
1242        reason: CrashReason,
1243        address: crate::Address,
1244        adjusted_address: Option<AdjustedAddress>,
1245        op_analysis: crate::op_analysis::OpAnalysis,
1246    ) -> Self {
1247        Self {
1248            reason,
1249            address,
1250            adjusted_address,
1251            instruction_str: Some(op_analysis.instruction_str),
1252            instruction_properties: Some(op_analysis.instruction_properties),
1253            memory_access_list: op_analysis.memory_access_list,
1254            instruction_pointer_update: op_analysis.instruction_pointer_update,
1255            possible_bit_flips: Default::default(),
1256            inconsistencies: Default::default(),
1257        }
1258    }
1259}
1260
1261struct ExceptionDetails<'a> {
1262    info: crate::ExceptionInfo,
1263    context: Option<std::borrow::Cow<'a, MinidumpContext>>,
1264    instruction_registers: BTreeSet<&'static str>,
1265}
1266
1267/// If a non-canonical access caused a crash, return the real address
1268///
1269/// Amd64 has the concept of a "canonical addressing", which requires that the upper 16 bits of
1270/// an address contain the same binary digit as bit 47. A violation of this rule triggers a
1271/// General Protection Fault instead of the usual Page Fault, which unfortunately means that the
1272/// OS has no idea what memory address actually caused the issue
1273///
1274/// If `exception_info` contains the markers of a non-canonical exception, and it also contains
1275/// memory access info from analyzing the CPU instruction with `op_analysis`, this module will
1276/// attempt to determine what address the CPU was instructed to access when the GPF occurred
1277///
1278/// # Return
1279///
1280/// `Some(address)` if the crash was caused by a non-canonical access and the real address was
1281/// determined, `None` otherwise
1282fn try_get_non_canonical_crash_address(
1283    system_info: &SystemInfo,
1284    memory_addresses: Option<&[MemoryAddressInfo]>,
1285    reason: CrashReason,
1286    address: u64,
1287) -> Option<u64> {
1288    use system_info::Cpu;
1289
1290    // The range of non-canonical addresses in the current 48-bit implementation
1291    // See: https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details
1292    const NON_CANONICAL_RANGE: RangeInclusive<u64> = 0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
1293
1294    // Only Amd64 has non-canonical addresses
1295    if system_info.cpu != Cpu::X86_64 {
1296        return None;
1297    }
1298
1299    if !represents_general_protection_fault(system_info.os, reason, address) {
1300        return None;
1301    }
1302
1303    // If we weren't able to determine the memory accessed by the instruction, we can't do this analysis
1304    if memory_addresses.is_none() {
1305        tracing::warn!(
1306            "lack of instruction analysis prevented determination of non-canonical address"
1307        );
1308        return None;
1309    }
1310
1311    // If any of the instructions operands were within the non-canonical range, we have our culprit
1312    for access in memory_addresses.unwrap().iter() {
1313        if NON_CANONICAL_RANGE.contains(&access.address) {
1314            return Some(access.address);
1315        }
1316    }
1317
1318    tracing::warn!(
1319        r#"somehow got a general protection fault in an instruction 
1320        that doesn't appear to access a non-canonical address"#
1321    );
1322
1323    None
1324}
1325
1326/// Report whether the given exception represents a general protection fault (GPF) on the given OS
1327///
1328/// Different operating systems have different ways of reporting GPF, this function will return
1329/// whether the given `exception_info` object represents that on the given OS
1330///
1331/// Note it is possible that the crashing instruction is performing an access to 0xffffffffffffffff
1332/// or 0x00000000000000000, and the crash is actually not caused by GPF
1333///
1334/// The most likely cause of GPF is the use of non-canonical address but there are other possibilities,
1335/// such as misaligned accesses
1336fn represents_general_protection_fault(
1337    os: system_info::Os,
1338    reason: CrashReason,
1339    address: u64,
1340) -> bool {
1341    use minidump_common::errors as minidump_errors;
1342    use system_info::Os;
1343
1344    // This is needed because match arms don't allow casting
1345    const SI_KERNEL_U32: u32 = minidump_errors::ExceptionCodeLinuxSicode::SI_KERNEL as u32;
1346
1347    match (os, reason, address) {
1348        // Windows reports it as EXCEPTION_ACCESS_VIOLATION_READ, address 0xffffffffffffffff
1349        (
1350            Os::Windows,
1351            CrashReason::WindowsAccessViolation(
1352                minidump_errors::ExceptionCodeWindowsAccessType::READ,
1353            ),
1354            u64::MAX,
1355        ) => true,
1356        (Os::Windows, _, _) => false,
1357        // macOS reports it as EXC_BAD_ACCESS / EXC_I386_GPFLT, address 0x0000000000000000
1358        (
1359            Os::MacOs,
1360            CrashReason::MacBadAccessX86(
1361                minidump_errors::ExceptionCodeMacBadAccessX86Type::EXC_I386_GPFLT,
1362            ),
1363            0,
1364        ) => true,
1365        (Os::MacOs, _, _) => false,
1366        // Linux reports it as either "SIGBUS / SI_KERNEL" or "SIGSEGV / SI_KERNEL", address 0x0000000000000000
1367        (
1368            Os::Linux,
1369            CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGSEGV, SI_KERNEL_U32),
1370            0,
1371        ) => true,
1372        (
1373            Os::Linux,
1374            CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGBUS, SI_KERNEL_U32),
1375            0,
1376        ) => true,
1377        (Os::Linux, _, _) => false,
1378        (_, _, _) => {
1379            tracing::warn!("we don't currently support non-canonical analysis for your OS");
1380            false
1381        }
1382    }
1383}
1384
1385/// Try to detect a "null pointer in disguise"
1386///
1387/// This function will search though all the memory accessses for the instruction and see if any
1388/// of them were flagged by `op_analysis` as being a disguised nullptr. If so, we return that
1389/// address as an "offset" from the null pointer value
1390fn try_detect_null_pointer_in_disguise(
1391    memory_addresses: Option<&[MemoryAddressInfo]>,
1392) -> Option<u64> {
1393    if let Some(memory_addresses) = memory_addresses {
1394        for access in memory_addresses.iter() {
1395            if access.is_likely_null_pointer_dereference {
1396                return Some(access.address);
1397            }
1398        }
1399    }
1400    None
1401}
1402
1403pub mod memory_operation {
1404    use super::*;
1405
1406    /// The memory operation occurring when a crash occurred.
1407    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1408    pub enum MemoryOperation {
1409        #[default]
1410        Undetermined,
1411        Read,
1412        Write,
1413        Execute,
1414    }
1415
1416    impl std::fmt::Display for MemoryOperation {
1417        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1418            f.write_str(match self {
1419                Self::Undetermined => "Undetermined",
1420                Self::Read => "Read",
1421                Self::Write => "Write",
1422                Self::Execute => "Execute",
1423            })
1424        }
1425    }
1426
1427    impl MemoryOperation {
1428        pub fn from_crash_reason(reason: &CrashReason) -> Self {
1429            use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
1430
1431            match reason {
1432                CrashReason::WindowsAccessViolation(WinAccess::READ) => Self::Read,
1433                CrashReason::WindowsAccessViolation(WinAccess::WRITE) => Self::Write,
1434                CrashReason::WindowsAccessViolation(WinAccess::EXEC) => Self::Execute,
1435                _ => Self::default(),
1436            }
1437        }
1438
1439        /// Return whether this memory operation is possibily allowed in the given memory region.
1440        /// If operation is `Undetermined`, this method returns true.
1441        pub fn is_possibly_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
1442            match self {
1443                Self::Undetermined => true,
1444                Self::Read => memory_info.is_readable(),
1445                Self::Write => memory_info.is_writable(),
1446                Self::Execute => memory_info.is_executable(),
1447            }
1448        }
1449
1450        /// Return whether this memory operation is definitely allowed in the given memory region.
1451        /// If operation is `Undetermined`, this method returns false.
1452        pub fn is_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
1453            match self {
1454                Self::Undetermined => false,
1455                Self::Read => memory_info.is_readable(),
1456                Self::Write => memory_info.is_writable(),
1457                Self::Execute => memory_info.is_executable(),
1458            }
1459        }
1460    }
1461}
1462
1463/// Bit-flip detection.
1464mod bitflip {
1465    use super::*;
1466    use crate::memory_operation::MemoryOperation;
1467    use crate::PossibleBitFlip;
1468
1469    /// The bit range over which to check bit flips.
1470    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1471    pub enum BitRange {
1472        Amd64Canononical,
1473        Amd64NonCanonical,
1474        All,
1475    }
1476
1477    impl BitRange {
1478        pub fn range(&self) -> std::ops::Range<u32> {
1479            match self {
1480                Self::All => 0..u64::BITS,
1481                Self::Amd64Canononical => 0..48,
1482                Self::Amd64NonCanonical => 48..u64::BITS,
1483            }
1484        }
1485    }
1486
1487    /// Try to determine whether an address was the result of a flipped bit.
1488    ///
1489    /// `memory_operation` represents the memory operation that was occurring at the crashing address
1490    /// (read/write/exec). If left as `Undetermined`, all memory operations are considered allowed.
1491    /// Otherwise, specify one of the operations that was occurring.
1492    pub fn try_bit_flips(
1493        address: u64,
1494        source_register: Option<&'static str>,
1495        bit_range: BitRange,
1496        exception_context: Option<&MinidumpContext>,
1497        memory_info: &UnifiedMemoryInfoList,
1498        memory_operation: MemoryOperation,
1499    ) -> Vec<PossibleBitFlip> {
1500        let mut addresses = Vec::new();
1501        // If the address maps to valid memory, don't do anything else.
1502        if let Some(mi) = memory_info.memory_info_at_address(address) {
1503            if memory_operation.is_possibly_allowed_for(&mi) {
1504                return addresses;
1505            }
1506        }
1507
1508        let create_possible_address = |new_address: u64| {
1509            let mut ret = PossibleBitFlip::new(new_address, source_register);
1510            ret.calculate_heuristics(
1511                address,
1512                bit_range == BitRange::Amd64NonCanonical,
1513                exception_context,
1514            );
1515            ret
1516        };
1517
1518        for i in bit_range.range() {
1519            let possible_address = address ^ (1 << i);
1520            // If the possible address is NULL, we assume that this was the originally intended address
1521            // and some logic error has occurred (e.g. a NULL check went the wrong way).
1522            if possible_address == 0 {
1523                addresses.push(create_possible_address(possible_address));
1524            }
1525            if let Some(mi) = memory_info.memory_info_at_address(possible_address) {
1526                if memory_operation.is_possibly_allowed_for(&mi) {
1527                    addresses.push(create_possible_address(possible_address))
1528                }
1529            }
1530        }
1531
1532        addresses
1533    }
1534}