summaryrefslogtreecommitdiffstats
path: root/third_party/rust/minidump-writer/tests/mac_minidump_writer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/minidump-writer/tests/mac_minidump_writer.rs')
-rw-r--r--third_party/rust/minidump-writer/tests/mac_minidump_writer.rs227
1 files changed, 227 insertions, 0 deletions
diff --git a/third_party/rust/minidump-writer/tests/mac_minidump_writer.rs b/third_party/rust/minidump-writer/tests/mac_minidump_writer.rs
new file mode 100644
index 0000000000..4dafb1a77a
--- /dev/null
+++ b/third_party/rust/minidump-writer/tests/mac_minidump_writer.rs
@@ -0,0 +1,227 @@
+#![cfg(target_os = "macos")]
+
+mod common;
+use common::start_child_and_return;
+
+use minidump::{
+ CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpMiscInfo,
+ MinidumpModuleList, MinidumpSystemInfo, MinidumpThreadList,
+};
+use minidump_writer::minidump_writer::MinidumpWriter;
+
+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::MacOs,
+ if cfg!(target_arch = "x86_64") {
+ minidump::system_info::Cpu::X86_64
+ } else if cfg!(target_arch = "aarch64") {
+ minidump::system_info::Cpu::Arm64
+ } else {
+ unimplemented!()
+ },
+ )
+}
+
+struct Captured<'md> {
+ #[allow(dead_code)]
+ task: u32,
+ thread: u32,
+ minidump: Minidump<'md, memmap2::Mmap>,
+}
+
+fn capture_minidump(name: &str, exception_kind: u32) -> Captured<'_> {
+ // Create a mach port server to retrieve the crash details from the child
+ let mut server = crash_context::ipc::Server::create(&std::ffi::CString::new(name).unwrap())
+ .expect("failed to create mach port service");
+
+ let mut child = start_child_and_return(&[name, &exception_kind.to_string()]);
+
+ // Wait for the child to spinup and report a crash context to us
+ let mut rcc = server
+ .try_recv_crash_context(Some(std::time::Duration::from_secs(5)))
+ .expect("failed to receive context")
+ .expect("receive timed out");
+
+ let mut tmpfile = tempfile::Builder::new().prefix(name).tempfile().unwrap();
+
+ let task = rcc.crash_context.task;
+ let thread = rcc.crash_context.thread;
+
+ let mut dumper = MinidumpWriter::with_crash_context(rcc.crash_context);
+
+ dumper
+ .dump(tmpfile.as_file_mut())
+ .expect("failed to write minidump");
+
+ // Signal the child that we've received and processed the crash context
+ rcc.acker
+ .send_ack(1, Some(std::time::Duration::from_secs(2)))
+ .expect("failed to send ack");
+
+ child.kill().expect("failed to kill child");
+
+ let minidump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
+
+ Captured {
+ task,
+ thread,
+ minidump,
+ }
+}
+
+#[test]
+fn dump_external_process() {
+ if std::env::var("CI").is_ok() {
+ println!("test disabled, consistently times out because of potato runners");
+ return;
+ }
+
+ let approximate_proc_start_time = std::time::SystemTime::now()
+ .duration_since(std::time::SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs();
+
+ let md = capture_minidump(
+ "dump_external_process",
+ mach2::exception_types::EXC_BREAKPOINT,
+ )
+ .minidump;
+
+ let crash_reason = get_crash_reason(&md);
+
+ assert!(matches!(
+ crash_reason,
+ CrashReason::MacGeneral(minidump_common::errors::ExceptionCodeMac::EXC_BREAKPOINT, _)
+ ));
+
+ let _: MinidumpModuleList = md.get_stream().expect("Couldn't find MinidumpModuleList");
+ 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 _: MinidumpBreakpadInfo = md.get_stream().expect("Couldn't find MinidumpBreakpadInfo");
+
+ let misc_info: MinidumpMiscInfo = md.get_stream().expect("Couldn't find MinidumpMiscInfo");
+
+ if let minidump::RawMiscInfo::MiscInfo2(mi) = &misc_info.raw {
+ // Unfortunately the minidump format only has 32-bit precision for the
+ // process start time
+ let process_create_time = mi.process_create_time as u64;
+
+ assert!(
+ process_create_time >= approximate_proc_start_time
+ && process_create_time <= approximate_proc_start_time + 2
+ );
+
+ // I've tried busy looping to spend CPU time to get this up, but
+ // MACH_TASK_BASIC_INFO which should give terminated thread times only ever
+ // reports 0, and TASK_THREAD_TIMES_INFO which should show active thread
+ // times I've only been able to get upt to a few thousand microseconds
+ // even when busy looping for well over a second, and those get truncated
+ // to whole seconds. And it seems that crashpad doesn't have tests around
+ // this, though that's hard to say given how tedious it is finding stuff
+ // in that bloated codebase
+ // assert!(mi.process_user_time > 0);
+ // assert!(mi.process_kernel_time > 0);
+
+ // These aren't currently available on aarch64, or if they are, they
+ // are not via the same sysctlbyname mechanism. Would be nice if Apple
+ // documented...anything
+ if cfg!(target_arch = "x86_64") {
+ assert!(mi.processor_max_mhz > 0);
+ assert!(mi.processor_current_mhz > 0);
+ }
+ } else {
+ panic!("unexpected misc info type {:?}", misc_info);
+ }
+}
+
+/// Validates we can actually walk the stack for each thread in the minidump,
+/// this is using minidump-processor, which (currently) depends on breakpad
+/// symbols, however https://github.com/mozilla/dump_syms is not available as
+/// a library https://github.com/mozilla/dump_syms/issues/253, so we just require
+/// that it already be installed, hence the ignore
+#[test]
+fn stackwalks() {
+ if std::env::var("CI").is_ok() {
+ println!("test disabled, consistently times out because of potato runners");
+ return;
+ }
+
+ println!("generating minidump...");
+ let md = capture_minidump("stackwalks", mach2::exception_types::EXC_BREAKPOINT);
+
+ // Generate the breakpad symbols
+ println!("generating symbols...");
+ dump_syms::dumper::single_file(
+ &dump_syms::dumper::Config {
+ output: dump_syms::dumper::Output::Store(".test-symbols".into()),
+ symbol_server: None,
+ debug_id: None,
+ code_id: None,
+ arch: if cfg!(target_arch = "aarch64") {
+ "arm64"
+ } else if cfg!(target_arch = "x86_64") {
+ "x86_64"
+ } else {
+ panic!("invalid MacOS target architecture")
+ },
+ num_jobs: 2, // default this
+ check_cfi: false,
+ emit_inlines: false,
+ mapping_var: None,
+ mapping_src: None,
+ mapping_dest: None,
+ mapping_file: None,
+ },
+ "target/debug/test",
+ )
+ .expect("failed to dump symbols");
+
+ let provider =
+ minidump_processor::Symbolizer::new(minidump_processor::simple_symbol_supplier(vec![
+ ".test-symbols".into(),
+ ]));
+
+ let state = futures::executor::block_on(async {
+ minidump_processor::process_minidump(&md.minidump, &provider).await
+ })
+ .unwrap();
+
+ //state.print(&mut std::io::stdout()).map_err(|_| ()).unwrap();
+
+ // We expect at least 2 threads, one of which is the fake crashing thread
+ let fake_crash_thread = state
+ .threads
+ .iter()
+ .find(|cs| cs.thread_id == md.thread)
+ .expect("failed to find crash thread");
+
+ assert_eq!(
+ fake_crash_thread.thread_name.as_deref(),
+ Some("test-thread")
+ );
+
+ assert!(
+ fake_crash_thread.frames.iter().any(|sf| {
+ sf.function_name
+ .as_ref()
+ .map_or(false, |fname| fname.ends_with("wait_until_killed"))
+ }),
+ "unable to locate expected function"
+ );
+
+ let mod_list: MinidumpModuleList = md
+ .minidump
+ .get_stream()
+ .expect("Couldn't find MinidumpModuleList");
+
+ // Ensure we found dyld
+ assert!(mod_list
+ .iter()
+ .any(|module| &module.name == "/usr/lib/dyld"));
+}