1use 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#[derive(Debug, Clone)]
57#[non_exhaustive]
58pub struct ProcessorOptions<'a> {
59 pub evil_json: Option<&'a Path>,
63
64 pub recover_function_args: bool,
69
70 pub stat_reporter: Option<&'a PendingProcessorStats>,
74}
75
76#[derive(Debug)]
86pub struct PendingProcessorStats {
87 subscriptions: PendingProcessorStatSubscriptions,
89 stats: Arc<Mutex<PendingProcessorStatsInner>>,
91}
92
93#[derive(Default, Debug, Clone)]
96struct PendingProcessorStatsInner {
97 num_threads_processed: u64,
99 total_threads: u64,
101 num_frames_processed: u64,
103 new_walked_frames: Vec<WalkedFrame>,
105 unwalked_result: Option<ProcessState>,
107}
108
109#[derive(Debug, Clone, Default)]
110#[non_exhaustive]
111pub struct PendingProcessorStatSubscriptions {
115 pub thread_count: bool,
121 pub frame_count: bool,
127 pub unwalked_result: bool,
134 pub live_frames: bool,
144}
145
146#[derive(Debug, Clone)]
151pub struct WalkedFrame {
152 pub thread_idx: usize,
154 pub frame_idx: usize,
156 pub frame: StackFrame,
160}
161
162impl PendingProcessorStats {
163 pub fn new(subscriptions: PendingProcessorStatSubscriptions) -> Self {
167 Self {
168 subscriptions,
169 stats: Default::default(),
170 }
171 }
172
173 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 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 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 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 pub(crate) fn set_total_threads(&self, total_threads: u64) {
233 if self.subscriptions.thread_count {
235 let mut stats = self.stats.lock().unwrap();
236 stats.total_threads = total_threads;
237 }
238 }
239
240 pub(crate) fn inc_processed_threads(&self) {
242 if self.subscriptions.thread_count {
244 let mut stats = self.stats.lock().unwrap();
245 stats.num_threads_processed += 1;
246 }
247 }
248
249 pub(crate) fn add_walked_frame(&self, thread_idx: usize, frame_idx: usize, frame: &StackFrame) {
251 if self.subscriptions.live_frames || self.subscriptions.frame_count {
253 let mut stats = self.stats.lock().unwrap();
254 stats.num_frames_processed += 1;
256 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 pub(crate) fn add_unwalked_result(&self, state: &ProcessState) {
269 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 pub fn stable_basic() -> Self {
292 ProcessorOptions {
293 evil_json: None,
294 recover_function_args: false,
295 stat_reporter: None,
296 }
297 }
298
299 pub fn stable_all() -> Self {
311 ProcessorOptions {
312 evil_json: None,
313 recover_function_args: false,
314 stat_reporter: None,
315 }
316 }
317
318 pub fn unstable_all() -> Self {
327 ProcessorOptions {
328 evil_json: None,
329 recover_function_args: true,
330 stat_reporter: None,
331 }
332 }
333
334 fn check_deprecated_and_disabled(&self) {
337 }
351}
352
353impl Default for ProcessorOptions<'_> {
354 fn default() -> Self {
355 Self::stable_basic()
356 }
357}
358
359#[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 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
385pub 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 process_minidump_with_options(dump, symbol_provider, ProcessorOptions::default()).await
420}
421
422fn 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
438pub 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 linux_memory_map_count: Option<usize>,
487 memory_info: UnifiedMemoryInfoList<'a>,
488 handle_data_stream: Option<MinidumpHandleDataStream>,
489 exception: Option<MinidumpException<'a>>,
490 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 let evil = options
503 .evil_json
504 .and_then(evil::handle_evil)
505 .unwrap_or_default();
506
507 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 let thread_names = dump
519 .get_stream::<MinidumpThreadNames>()
520 .unwrap_or_else(|_| MinidumpThreadNames::default());
521
522 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 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 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 let modules = match dump.get_stream::<MinidumpModuleList>() {
583 Ok(module_list) => module_list,
584 Err(_) => MinidumpModuleList::new(),
586 };
587 let unloaded_modules = match dump.get_stream::<MinidumpUnloadedModuleList>() {
588 Ok(module_list) => module_list,
589 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 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 memory_info,
626 linux_memory_map_count,
627 handle_data_stream,
628 exception,
629 soft_errors,
631 })
632 }
633
634 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 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 pub fn check_for_bitflips(&self, exception_details: &mut ExceptionDetails<'a>) {
719 if self.system_info.cpu.pointer_width() != PointerWidth::Bits64 {
722 return;
723 }
724
725 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 Some(AdjustedAddress::NonCanonical(v)) => Some((v.0, BitRange::Amd64NonCanonical)),
740 Some(AdjustedAddress::NullPointerWithOffset(_)) => None,
742 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 let access = info.memory_access_list.as_ref().and_then(|list| {
755 list.iter().find(|access| {
756 let size = access.size.unwrap_or_default();
757 let base = access.address_info.address;
758 (base..base.saturating_add(size as u64)).contains(&info.address.0)
759 })
760 });
761
762 let access_address = access.map(|a| a.address_info.address).unwrap_or(address);
766 let memory_access_size = access.and_then(|a| a.size);
769
770 info.possible_bit_flips = bitflip::try_bit_flips(
771 access_address,
772 None,
773 bit_range,
774 exception_details.context.as_deref(),
775 &self.memory_info,
776 memory_op,
777 memory_access_size,
778 );
779
780 if let Some(context) = exception_details.context.as_deref() {
783 for reg in &exception_details.instruction_registers {
784 if let Some(address) = context.get_register(reg) {
785 info.possible_bit_flips.extend(bitflip::try_bit_flips(
786 address,
787 Some(reg),
788 bit_range,
789 Some(context),
790 &self.memory_info,
791 memory_op,
796 memory_access_size,
797 ));
798 }
799 }
800 }
801 }
802 }
803
804 pub fn check_for_guard_pages(&self, exception_details: &mut ExceptionDetails<'a>) {
806 const GUARD_MEMORY_MAX_SIZE: u64 = 2 << 14;
807
808 if let Some(access_list) = &mut exception_details.info.memory_access_list {
809 for access in &mut access_list.accesses {
810 let Some(info) = self
811 .memory_info
812 .memory_info_at_address(access.address_info.address)
813 else {
814 continue;
815 };
816 let Some(range) = info.memory_range() else {
817 continue;
818 };
819
820 fn is_accessible(range: &UnifiedMemoryInfo) -> bool {
821 range.is_readable() || range.is_writable() || range.is_executable()
822 }
823
824 let is_adjacent_to_accessible_memory = || {
825 for region in self.memory_info.by_addr() {
826 let Some(other_range) = region.memory_range() else {
827 continue;
828 };
829 if other_range.end + 1 == range.start && is_accessible(®ion) {
830 return true;
831 }
832 if range.end + 1 == other_range.start {
833 return is_accessible(®ion);
836 }
837 }
838 false
839 };
840
841 if !is_accessible(&info)
846 && range.end - range.start < GUARD_MEMORY_MAX_SIZE
847 && is_adjacent_to_accessible_memory()
848 {
849 access.address_info.is_likely_guard_page = true;
850 }
851 }
852 }
853 }
854
855 pub fn check_for_crash_inconsistencies(&self, exception_details: &mut ExceptionDetails) {
857 use minidump_common::errors::{
858 ExceptionCodeLinuxSigfpeKind as LinuxSigfpe,
859 ExceptionCodeMacArithmeticPpcType as MacArithPpc,
860 ExceptionCodeMacArithmeticX86Type as MacArithX86, ExceptionCodeWindows,
861 ExceptionCodeWindowsAccessType as WinAccess, NtStatusWindows,
862 };
863 use CrashInconsistency as Inconsistency;
864
865 let mut inconsistencies = Vec::new();
866 match exception_details.info.reason {
867 CrashReason::MacArithmeticPpc(MacArithPpc::EXC_PPC_ZERO_DIVIDE)
868 | CrashReason::MacArithmeticX86(MacArithX86::EXC_I386_DIV)
869 | CrashReason::LinuxSigfpe(LinuxSigfpe::FPE_INTDIV)
870 | CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_INT_DIVIDE_BY_ZERO)
871 if exception_details
872 .info
873 .instruction_properties
874 .as_ref()
875 .is_some_and(|p| !p.is_division) =>
876 {
877 inconsistencies.push(Inconsistency::IntDivByZeroNotPossible);
878 }
879
880 CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_PRIV_INSTRUCTION)
881 | CrashReason::WindowsNtStatus(NtStatusWindows::STATUS_PRIVILEGED_INSTRUCTION)
882 if exception_details
883 .info
884 .instruction_properties
885 .as_ref()
886 .is_some_and(|p| !p.is_privileged) =>
887 {
888 inconsistencies.push(Inconsistency::PrivInstructionCrashWithoutPrivInstruction);
889 }
890
891 CrashReason::WindowsAccessViolation(WinAccess::READ) => {
892 let is_gpf = represents_general_protection_fault(
895 self.system_info.os,
896 exception_details.info.reason,
897 exception_details.info.address.0,
898 ) && self.crashing_access_is_not_among_accesses(exception_details);
899 let gpf_implies_non_canonical = exception_details
900 .info
901 .instruction_properties
902 .as_ref()
903 .is_some_and(|p| p.is_only_gpf_when_non_canonical);
904
905 if is_gpf {
906 if gpf_implies_non_canonical
907 && self.non_canonical_address_is_not_among_accesses(exception_details)
908 {
909 inconsistencies.push(Inconsistency::NonCanonicalAddressFalselyReported);
910 }
911 } else {
912 if self.crashing_access_is_not_among_accesses(exception_details) {
913 inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
914 }
915 if self.access_is_not_violation(exception_details) {
916 inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
917 }
918 }
919 }
920 CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW)
923 | CrashReason::WindowsAccessViolation(WinAccess::WRITE)
924 | CrashReason::WindowsAccessViolation(WinAccess::EXEC) => {
925 if self.crashing_access_is_not_among_accesses(exception_details) {
926 inconsistencies.push(Inconsistency::CrashingAccessNotFoundInMemoryAccesses);
927 }
928 if self.access_is_not_violation(exception_details) {
929 inconsistencies.push(Inconsistency::AccessViolationWhenAccessAllowed);
930 }
931 }
932
933 _ => (),
934 }
935 exception_details.info.inconsistencies = inconsistencies;
936 }
937
938 fn non_canonical_address_is_not_among_accesses(
941 &self,
942 exception_details: &ExceptionDetails,
943 ) -> bool {
944 use crate::op_analysis::InstructionPointerUpdate;
945 const NON_CANONICAL_RANGE: RangeInclusive<u64> =
946 0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
947 let info = &exception_details.info;
948 info.instruction_properties
951 .as_ref()
952 .is_some_and(|properties| properties.is_access_derivable)
953 && info.memory_access_list.as_ref().is_some_and(|access_list| {
954 !access_list
955 .iter()
956 .any(|access| NON_CANONICAL_RANGE.contains(&access.address_info.address))
957 })
958 && info
959 .instruction_pointer_update
960 .as_ref()
961 .is_some_and(|update| match update {
962 InstructionPointerUpdate::Update { address_info } => {
963 !NON_CANONICAL_RANGE.contains(&address_info.address)
964 }
965 InstructionPointerUpdate::NoUpdate => true,
966 })
967 }
968
969 fn crashing_access_is_not_among_accesses(&self, exception_details: &ExceptionDetails) -> bool {
972 use crate::op_analysis::MemoryAccessType;
973 use minidump_common::errors::{
974 ExceptionCodeWindows, ExceptionCodeWindowsAccessType as WinAccess,
975 };
976
977 let info = &exception_details.info;
978 let crash_address = info.address.0;
979 let has_access_derivable_instruction = info
980 .instruction_properties
981 .as_ref()
982 .is_some_and(|p| p.is_access_derivable);
983
984 if let Some(access_list) = &info.memory_access_list {
985 match info.reason {
986 CrashReason::WindowsAccessViolation(WinAccess::READ) => {
987 has_access_derivable_instruction
988 && !access_list.contains_access(crash_address, MemoryAccessType::Read)
989 && !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
990 }
991 CrashReason::WindowsAccessViolation(WinAccess::WRITE) => {
992 has_access_derivable_instruction
993 && !access_list.contains_access(crash_address, MemoryAccessType::Write)
994 && !access_list.contains_access(crash_address, MemoryAccessType::ReadWrite)
995 }
996 CrashReason::WindowsAccessViolation(WinAccess::EXEC) => exception_details
997 .context
998 .as_ref()
999 .is_some_and(|context| context.get_instruction_pointer() != crash_address),
1000 CrashReason::WindowsGeneral(ExceptionCodeWindows::EXCEPTION_STACK_OVERFLOW) => {
1001 has_access_derivable_instruction && access_list.is_empty()
1002 }
1003 _ => false,
1004 }
1005 } else {
1006 false
1007 }
1008 }
1009
1010 fn access_is_not_violation(&self, exception_details: &ExceptionDetails) -> bool {
1013 {
1014 use memory_operation::MemoryOperation;
1015 use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
1016
1017 let info = &exception_details.info;
1018 let crash_address = info.address.0;
1019
1020 if let Some(mi) = self.memory_info.memory_info_at_address(crash_address) {
1021 matches!(
1022 info.reason,
1023 CrashReason::WindowsAccessViolation(WinAccess::READ)
1024 | CrashReason::WindowsAccessViolation(WinAccess::WRITE)
1025 | CrashReason::WindowsAccessViolation(WinAccess::EXEC)
1026 ) && MemoryOperation::from_crash_reason(&info.reason).is_allowed_for(&mi)
1027 } else {
1028 false
1029 }
1030 }
1031 }
1032
1033 pub async fn into_process_state<P, T>(
1034 self,
1035 dump: &Minidump<'a, T>,
1036 symbol_provider: &P,
1037 exception_details: Option<ExceptionDetails<'a>>,
1038 ) -> Result<ProcessState, ProcessError>
1039 where
1040 T: Deref<Target = [u8]> + 'a,
1041 P: SymbolProvider + Sync,
1042 {
1043 let crashing_thread_id = self.exception.as_ref().map(|e| e.get_crashing_thread_id());
1044
1045 let (exception_info, exception_context) = match exception_details {
1046 Some(details) => (Some(details.info), details.context),
1047 None => (None, None),
1048 };
1049
1050 let mut requesting_thread = None;
1051
1052 let threads = self
1053 .thread_list
1054 .threads
1055 .iter()
1056 .enumerate()
1057 .map(|(i, thread)| {
1058 let id = thread.raw.thread_id;
1059
1060 if self.dump_thread_id == Some(id) {
1062 return CallStack::with_info(id, CallStackInfo::DumpThreadSkipped);
1063 }
1064
1065 let thread_context =
1066 thread.context(&self.dump_system_info, self.misc_info.as_ref());
1067 let context = if crashing_thread_id.or(self.requesting_thread_id) == Some(id) {
1071 requesting_thread = Some(i);
1072 exception_context.as_deref().or(thread_context.as_deref())
1073 } else {
1074 thread_context.as_deref()
1075 };
1076
1077 let name = self
1078 .thread_names
1079 .get_name(thread.raw.thread_id)
1080 .map(|cow| cow.into_owned());
1081
1082 let (info, frames) = if let Some(context) = context {
1083 let ctx = context.clone();
1084 (
1085 CallStackInfo::Ok,
1086 vec![StackFrame::from_context(ctx, FrameTrust::Context)],
1087 )
1088 } else {
1089 (CallStackInfo::MissingContext, vec![])
1090 };
1091
1092 CallStack {
1093 frames,
1094 info,
1095 thread_id: id,
1096 thread_name: name,
1097 last_error_value: thread.last_error(self.system_info.cpu, &self.memory_list),
1098 }
1099 })
1100 .collect();
1101
1102 let unknown_streams = dump.unknown_streams().collect();
1104 let unimplemented_streams = dump.unimplemented_streams().collect();
1105
1106 let symbol_stats = symbol_provider.stats();
1108
1109 let process_id = if let Some(misc_info) = self.misc_info.as_ref() {
1111 misc_info.raw.process_id().cloned()
1112 } else {
1113 self.linux_proc_status
1114 .map(|linux_proc_status| linux_proc_status.pid)
1115 };
1116
1117 let process_create_time = if let Some(misc_info) = self.misc_info.as_ref() {
1118 misc_info.process_create_time()
1119 } else {
1120 None
1121 };
1122
1123 let mut state = ProcessState {
1124 process_id,
1125 time: SystemTime::UNIX_EPOCH + Duration::from_secs(dump.header.time_date_stamp as u64),
1126 process_create_time,
1127 cert_info: self.evil.certs,
1128 exception_info,
1129 assertion: None,
1130 requesting_thread,
1131 system_info: self.system_info,
1132 linux_standard_base: self.linux_standard_base,
1133 linux_proc_limits: self.linux_proc_limits,
1134 mac_crash_info: self.mac_crash_info,
1135 mac_boot_args: self.mac_boot_args,
1136 threads,
1137 modules: self.modules,
1138 unloaded_modules: self.unloaded_modules,
1139 handles: self.handle_data_stream,
1140 unknown_streams,
1141 unimplemented_streams,
1142 symbol_stats,
1143 linux_memory_map_count: self.linux_memory_map_count,
1144 soft_errors: self.soft_errors,
1145 };
1146
1147 if let Some(reporter) = self.options.stat_reporter {
1149 reporter.add_unwalked_result(&state);
1150 }
1151
1152 {
1153 let memory_list = &self.memory_list;
1154 let modules = &state.modules;
1155 let system_info = &state.system_info;
1156 let unloaded_modules = &state.unloaded_modules;
1157 let options = &self.options;
1158
1159 futures_util::future::join_all(
1160 state
1161 .threads
1162 .iter_mut()
1163 .zip(self.thread_list.threads.iter())
1164 .enumerate()
1165 .map(|(i, (stack, thread))| async move {
1166 let mut stack_memory = thread.stack_memory(memory_list);
1167 let stack_ptr = stack
1171 .frames
1172 .first()
1173 .map(|ctx_frame| ctx_frame.context.get_stack_pointer());
1174 if let Some(stack_ptr) = stack_ptr {
1175 let contains_stack_ptr = stack_memory
1176 .as_ref()
1177 .and_then(|memory| memory.get_memory_at_address::<u64>(stack_ptr))
1178 .is_some();
1179 if !contains_stack_ptr {
1180 stack_memory =
1181 memory_list.memory_at_address(stack_ptr).or(stack_memory);
1182 }
1183 }
1184
1185 walk_stack(
1186 i,
1187 |frame_idx: usize, frame: &StackFrame| {
1188 if let Some(reporter) = options.stat_reporter {
1189 reporter.add_walked_frame(i, frame_idx, frame);
1190 }
1191 },
1192 stack,
1193 stack_memory,
1194 modules,
1195 system_info,
1196 symbol_provider,
1197 )
1198 .await;
1199
1200 for frame in &mut stack.frames {
1201 if frame.module.is_none() {
1205 let mut offsets = BTreeMap::new();
1206 for unloaded in
1207 unloaded_modules.modules_at_address(frame.instruction)
1208 {
1209 let offset = frame.instruction - unloaded.raw.base_of_image;
1210 offsets
1211 .entry(unloaded.name.clone())
1212 .or_insert_with(BTreeSet::new)
1213 .insert(offset);
1214 }
1215
1216 frame.unloaded_modules = offsets;
1217 }
1218 }
1219
1220 if options.recover_function_args {
1221 arg_recovery::fill_arguments(stack, stack_memory);
1222 }
1223
1224 if let Some(reporter) = options.stat_reporter {
1226 reporter.inc_processed_threads();
1227 }
1228
1229 stack
1230 }),
1231 )
1232 .await
1233 };
1234
1235 let symbol_stats = symbol_provider.stats();
1236 state.symbol_stats = symbol_stats;
1237
1238 Ok(state)
1239 }
1240}
1241
1242impl crate::ExceptionInfo {
1243 fn new(reason: CrashReason, address: crate::Address) -> Self {
1244 Self {
1245 reason,
1246 address,
1247 adjusted_address: None,
1248 instruction_str: None,
1249 instruction_properties: None,
1250 memory_access_list: None,
1251 instruction_pointer_update: None,
1252 possible_bit_flips: Default::default(),
1253 inconsistencies: Default::default(),
1254 }
1255 }
1256
1257 fn with_op_analysis(
1258 reason: CrashReason,
1259 address: crate::Address,
1260 adjusted_address: Option<AdjustedAddress>,
1261 op_analysis: crate::op_analysis::OpAnalysis,
1262 ) -> Self {
1263 Self {
1264 reason,
1265 address,
1266 adjusted_address,
1267 instruction_str: Some(op_analysis.instruction_str),
1268 instruction_properties: Some(op_analysis.instruction_properties),
1269 memory_access_list: op_analysis.memory_access_list,
1270 instruction_pointer_update: op_analysis.instruction_pointer_update,
1271 possible_bit_flips: Default::default(),
1272 inconsistencies: Default::default(),
1273 }
1274 }
1275}
1276
1277struct ExceptionDetails<'a> {
1278 info: crate::ExceptionInfo,
1279 context: Option<std::borrow::Cow<'a, MinidumpContext>>,
1280 instruction_registers: BTreeSet<&'static str>,
1281}
1282
1283fn try_get_non_canonical_crash_address(
1299 system_info: &SystemInfo,
1300 memory_addresses: Option<&[MemoryAddressInfo]>,
1301 reason: CrashReason,
1302 address: u64,
1303) -> Option<u64> {
1304 use system_info::Cpu;
1305
1306 const NON_CANONICAL_RANGE: RangeInclusive<u64> = 0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
1309
1310 if system_info.cpu != Cpu::X86_64 {
1312 return None;
1313 }
1314
1315 if !represents_general_protection_fault(system_info.os, reason, address) {
1316 return None;
1317 }
1318
1319 if memory_addresses.is_none() {
1321 tracing::warn!(
1322 "lack of instruction analysis prevented determination of non-canonical address"
1323 );
1324 return None;
1325 }
1326
1327 for access in memory_addresses.unwrap().iter() {
1329 if NON_CANONICAL_RANGE.contains(&access.address) {
1330 return Some(access.address);
1331 }
1332 }
1333
1334 tracing::warn!(
1335 r#"somehow got a general protection fault in an instruction
1336 that doesn't appear to access a non-canonical address"#
1337 );
1338
1339 None
1340}
1341
1342fn represents_general_protection_fault(
1353 os: system_info::Os,
1354 reason: CrashReason,
1355 address: u64,
1356) -> bool {
1357 use minidump_common::errors as minidump_errors;
1358 use system_info::Os;
1359
1360 const SI_KERNEL_U32: u32 = minidump_errors::ExceptionCodeLinuxSicode::SI_KERNEL as u32;
1362
1363 match (os, reason, address) {
1364 (
1366 Os::Windows,
1367 CrashReason::WindowsAccessViolation(
1368 minidump_errors::ExceptionCodeWindowsAccessType::READ,
1369 ),
1370 u64::MAX,
1371 ) => true,
1372 (Os::Windows, _, _) => false,
1373 (
1375 Os::MacOs,
1376 CrashReason::MacBadAccessX86(
1377 minidump_errors::ExceptionCodeMacBadAccessX86Type::EXC_I386_GPFLT,
1378 ),
1379 0,
1380 ) => true,
1381 (Os::MacOs, _, _) => false,
1382 (
1384 Os::Linux,
1385 CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGSEGV, SI_KERNEL_U32),
1386 0,
1387 ) => true,
1388 (
1389 Os::Linux,
1390 CrashReason::LinuxGeneral(minidump_errors::ExceptionCodeLinux::SIGBUS, SI_KERNEL_U32),
1391 0,
1392 ) => true,
1393 (Os::Linux, _, _) => false,
1394 (_, _, _) => {
1395 tracing::warn!("we don't currently support non-canonical analysis for your OS");
1396 false
1397 }
1398 }
1399}
1400
1401fn try_detect_null_pointer_in_disguise(
1407 memory_addresses: Option<&[MemoryAddressInfo]>,
1408) -> Option<u64> {
1409 if let Some(memory_addresses) = memory_addresses {
1410 for access in memory_addresses.iter() {
1411 if access.is_likely_null_pointer_dereference {
1412 return Some(access.address);
1413 }
1414 }
1415 }
1416 None
1417}
1418
1419pub mod memory_operation {
1420 use super::*;
1421
1422 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1424 pub enum MemoryOperation {
1425 #[default]
1426 Undetermined,
1427 Read,
1428 Write,
1429 Execute,
1430 }
1431
1432 impl std::fmt::Display for MemoryOperation {
1433 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1434 f.write_str(match self {
1435 Self::Undetermined => "Undetermined",
1436 Self::Read => "Read",
1437 Self::Write => "Write",
1438 Self::Execute => "Execute",
1439 })
1440 }
1441 }
1442
1443 impl MemoryOperation {
1444 pub fn from_crash_reason(reason: &CrashReason) -> Self {
1445 use minidump_common::errors::ExceptionCodeWindowsAccessType as WinAccess;
1446
1447 match reason {
1448 CrashReason::WindowsAccessViolation(WinAccess::READ) => Self::Read,
1449 CrashReason::WindowsAccessViolation(WinAccess::WRITE) => Self::Write,
1450 CrashReason::WindowsAccessViolation(WinAccess::EXEC) => Self::Execute,
1451 _ => Self::default(),
1452 }
1453 }
1454
1455 pub fn is_possibly_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
1458 match self {
1459 Self::Undetermined => true,
1460 Self::Read => memory_info.is_readable(),
1461 Self::Write => memory_info.is_writable(),
1462 Self::Execute => memory_info.is_executable(),
1463 }
1464 }
1465
1466 pub fn is_allowed_for(&self, memory_info: &UnifiedMemoryInfo) -> bool {
1469 match self {
1470 Self::Undetermined => false,
1471 Self::Read => memory_info.is_readable(),
1472 Self::Write => memory_info.is_writable(),
1473 Self::Execute => memory_info.is_executable(),
1474 }
1475 }
1476 }
1477}
1478
1479mod bitflip {
1481 use super::*;
1482 use crate::memory_operation::MemoryOperation;
1483 use crate::PossibleBitFlip;
1484
1485 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1487 pub enum BitRange {
1488 Amd64Canononical,
1489 Amd64NonCanonical,
1490 All,
1491 }
1492
1493 impl BitRange {
1494 pub fn range(&self) -> std::ops::Range<u32> {
1495 match self {
1496 Self::All => 0..u64::BITS,
1497 Self::Amd64Canononical => 0..48,
1498 Self::Amd64NonCanonical => 48..u64::BITS,
1499 }
1500 }
1501 }
1502
1503 pub fn try_bit_flips(
1509 address: u64,
1510 source_register: Option<&'static str>,
1511 bit_range: BitRange,
1512 exception_context: Option<&MinidumpContext>,
1513 memory_info: &UnifiedMemoryInfoList,
1514 memory_operation: MemoryOperation,
1515 memory_access_size: Option<u8>,
1516 ) -> Vec<PossibleBitFlip> {
1517 let mut addresses = Vec::new();
1518 if let Some(mi) = memory_info.memory_info_at_address(address) {
1520 if memory_operation.is_possibly_allowed_for(&mi) {
1521 return addresses;
1522 }
1523 }
1524
1525 let distance_to_closest_mapping =
1529 distance_to_closest_mapping(address, memory_operation, memory_info);
1530
1531 match (distance_to_closest_mapping, memory_access_size) {
1533 (Some(distance), Some(access)) if distance < access as u64 => return addresses,
1534 _ => (),
1535 }
1536
1537 let create_possible_address = |new_address: u64| {
1538 let mut ret = PossibleBitFlip::new(new_address, source_register);
1539 ret.calculate_heuristics(
1540 address,
1541 bit_range == BitRange::Amd64NonCanonical,
1542 exception_context,
1543 );
1544 ret
1545 };
1546
1547 for i in bit_range.range() {
1548 let possible_address = address ^ (1 << i);
1549 if possible_address == 0 {
1552 addresses.push(create_possible_address(possible_address));
1553 }
1554 if let Some(mi) = memory_info.memory_info_at_address(possible_address) {
1555 if memory_operation.is_possibly_allowed_for(&mi) {
1556 addresses.push(create_possible_address(possible_address))
1557 }
1558 }
1559 }
1560
1561 addresses
1562 }
1563
1564 fn distance_to_closest_mapping(
1567 address: u64,
1568 operation: MemoryOperation,
1569 memory_info: &UnifiedMemoryInfoList,
1570 ) -> Option<u64> {
1571 memory_info
1572 .by_addr()
1573 .filter(|r| operation.is_possibly_allowed_for(r))
1574 .filter_map(|region| {
1575 let range = region.memory_range()?;
1576 Some(if address < range.start {
1578 range.start - address
1579 } else {
1580 address.saturating_sub(range.end)
1581 })
1582 })
1583 .min()
1584 }
1585}