Source code

Revision control

Copy as Markdown

Other Tools

#![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<Target = [u8]> + '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)
);
}