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 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 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 memory_op,
779 ));
780 }
781 }
782 }
783 }
784 }
785
786 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(®ion) {
812 return true;
813 }
814 if range.end + 1 == other_range.start {
815 return is_accessible(®ion);
818 }
819 }
820 false
821 };
822
823 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 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 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 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 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 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 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 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 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 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 let unknown_streams = dump.unknown_streams().collect();
1088 let unimplemented_streams = dump.unimplemented_streams().collect();
1089
1090 let symbol_stats = symbol_provider.stats();
1092
1093 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 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 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 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 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
1267fn 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 const NON_CANONICAL_RANGE: RangeInclusive<u64> = 0x0000_8000_0000_0000..=0xffff_7fff_ffff_ffff;
1293
1294 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 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 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
1326fn 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 const SI_KERNEL_U32: u32 = minidump_errors::ExceptionCodeLinuxSicode::SI_KERNEL as u32;
1346
1347 match (os, reason, address) {
1348 (
1350 Os::Windows,
1351 CrashReason::WindowsAccessViolation(
1352 minidump_errors::ExceptionCodeWindowsAccessType::READ,
1353 ),
1354 u64::MAX,
1355 ) => true,
1356 (Os::Windows, _, _) => false,
1357 (
1359 Os::MacOs,
1360 CrashReason::MacBadAccessX86(
1361 minidump_errors::ExceptionCodeMacBadAccessX86Type::EXC_I386_GPFLT,
1362 ),
1363 0,
1364 ) => true,
1365 (Os::MacOs, _, _) => false,
1366 (
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
1385fn 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 #[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 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 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
1463mod bitflip {
1465 use super::*;
1466 use crate::memory_operation::MemoryOperation;
1467 use crate::PossibleBitFlip;
1468
1469 #[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 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 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 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}