diff options
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.rs | 227 |
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")); +} |