#![cfg(all(target_os = "windows", target_arch = "x86_64"))] use minidump::{ CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpSystemInfo, MinidumpThreadList, }; use minidump_writer::minidump_writer::MinidumpWriter; mod common; use common::start_child_and_return; const EXCEPTION_ILLEGAL_INSTRUCTION: i32 = -1073741795; const STATUS_INVALID_PARAMETER: i32 = -1073741811; #[link(name = "kernel32")] extern "system" { fn GetCurrentThreadId() -> u32; } fn get_crash_reason<'a, T: std::ops::Deref + 'a>( md: &Minidump<'a, T>, ) -> CrashReason { let exc: minidump::MinidumpException<'_> = md.get_stream().expect("unable to find exception stream"); exc.get_crash_reason( minidump::system_info::Os::Windows, minidump::system_info::Cpu::X86_64, ) } /// Ensures that we can write minidumps for the current process, even if this is /// not necessarily the primary intended use case of out-of-process dumping #[test] fn dump_current_process() { let mut tmpfile = tempfile::Builder::new() .prefix("windows_current_process") .tempfile() .unwrap(); MinidumpWriter::dump_local_context( Some(STATUS_INVALID_PARAMETER), None, None, tmpfile.as_file_mut(), ) .expect("failed to write minidump"); let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump"); let _: MinidumpThreadList = md.get_stream().expect("Couldn't find MinidumpThreadList"); let _: MinidumpMemoryList = md.get_stream().expect("Couldn't find MinidumpMemoryList"); let _: MinidumpSystemInfo = md.get_stream().expect("Couldn't find MinidumpSystemInfo"); let crash_reason = get_crash_reason(&md); assert_eq!( crash_reason, CrashReason::from_windows_error(STATUS_INVALID_PARAMETER as u32) ); // SAFETY: syscall let thread_id = unsafe { GetCurrentThreadId() }; let bp_info: MinidumpBreakpadInfo = md.get_stream().expect("Couldn't find MinidumpBreakpadInfo"); assert_eq!(bp_info.dump_thread_id.unwrap(), thread_id); assert_eq!(bp_info.requesting_thread_id.unwrap(), thread_id); } #[test] fn dump_specific_thread() { let mut tmpfile = tempfile::Builder::new() .prefix("windows_current_process") .tempfile() .unwrap(); let (tx, rx) = std::sync::mpsc::channel(); let jh = std::thread::spawn(move || { // SAFETY: syscall let thread_id = unsafe { GetCurrentThreadId() }; while tx.send(thread_id).is_ok() { std::thread::sleep(std::time::Duration::from_millis(10)); } }); let crashing_thread_id = rx.recv().unwrap(); MinidumpWriter::dump_local_context( Some(STATUS_INVALID_PARAMETER), Some(crashing_thread_id), None, tmpfile.as_file_mut(), ) .expect("failed to write minidump"); drop(rx); jh.join().unwrap(); let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump"); let _: MinidumpThreadList = md.get_stream().expect("Couldn't find MinidumpThreadList"); let _: MinidumpMemoryList = md.get_stream().expect("Couldn't find MinidumpMemoryList"); let _: MinidumpSystemInfo = md.get_stream().expect("Couldn't find MinidumpSystemInfo"); let crash_reason = get_crash_reason(&md); assert_eq!( crash_reason, CrashReason::from_windows_error(STATUS_INVALID_PARAMETER as u32) ); // SAFETY: syscall let requesting_thread_id = unsafe { GetCurrentThreadId() }; let bp_info: MinidumpBreakpadInfo = md.get_stream().expect("Couldn't find MinidumpBreakpadInfo"); assert_eq!(bp_info.dump_thread_id.unwrap(), crashing_thread_id); assert_eq!(bp_info.requesting_thread_id.unwrap(), requesting_thread_id); } /// Ensures that we can write minidumps for an external process. Unfortunately /// this requires us to know the actual pointer in the client process for the /// exception, as the `MiniDumpWriteDump` syscall directly reads points from /// the process memory, so we communicate that back from the client process /// via stdout #[test] fn dump_external_process() { use std::io::BufRead; let mut child = start_child_and_return(&[&format!("{:x}", EXCEPTION_ILLEGAL_INSTRUCTION)]); let (process_id, exception_pointers, thread_id, exception_code) = { let mut f = std::io::BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); let mut buf = String::new(); f.read_line(&mut buf).expect("failed to read stdout"); assert!(!buf.is_empty()); let mut biter = buf.trim().split(' '); let process_id: u32 = biter.next().unwrap().parse().unwrap(); let exception_pointers: usize = biter.next().unwrap().parse().unwrap(); let thread_id: u32 = biter.next().unwrap().parse().unwrap(); let exception_code = u32::from_str_radix(biter.next().unwrap(), 16).unwrap(); (process_id, exception_pointers, thread_id, exception_code) }; let exception_code = exception_code as i32; assert_eq!(exception_code, EXCEPTION_ILLEGAL_INSTRUCTION); let crash_context = crash_context::CrashContext { exception_pointers: exception_pointers as _, process_id, thread_id, exception_code, }; let mut tmpfile = tempfile::Builder::new() .prefix("windows_external_process") .tempfile() .unwrap(); // SAFETY: We keep the process we are dumping alive until the minidump is written // and the test process keep the pointers it sent us alive until it is killed MinidumpWriter::dump_crash_context(crash_context, None, tmpfile.as_file_mut()) .expect("failed to write minidump"); child.kill().expect("failed to kill child"); let md = Minidump::read_path(tmpfile.path()).expect("failed to read minidump"); let _: MinidumpThreadList = md.get_stream().expect("Couldn't find MinidumpThreadList"); let _: MinidumpMemoryList = md.get_stream().expect("Couldn't find MinidumpMemoryList"); let _: MinidumpSystemInfo = md.get_stream().expect("Couldn't find MinidumpSystemInfo"); let crash_reason = get_crash_reason(&md); assert_eq!( crash_reason, CrashReason::from_windows_code(EXCEPTION_ILLEGAL_INSTRUCTION as u32) ); }