From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../minidump-writer/tests/linux_minidump_writer.rs | 775 +++++++++++++++++++++ 1 file changed, 775 insertions(+) create mode 100644 third_party/rust/minidump-writer/tests/linux_minidump_writer.rs (limited to 'third_party/rust/minidump-writer/tests/linux_minidump_writer.rs') diff --git a/third_party/rust/minidump-writer/tests/linux_minidump_writer.rs b/third_party/rust/minidump-writer/tests/linux_minidump_writer.rs new file mode 100644 index 0000000000..cd2ca7375d --- /dev/null +++ b/third_party/rust/minidump-writer/tests/linux_minidump_writer.rs @@ -0,0 +1,775 @@ +#![cfg(any(target_os = "linux", target_os = "android"))] +#![allow(unused_imports, unused_variables)] + +use minidump::*; +use minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*}; +use minidump_writer::{ + app_memory::AppMemory, + crash_context::CrashContext, + errors::*, + maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo}, + minidump_writer::MinidumpWriter, + ptrace_dumper::PtraceDumper, + thread_info::Pid, +}; +use nix::{errno::Errno, sys::signal::Signal}; +use std::collections::HashSet; + +use std::{ + io::{BufRead, BufReader}, + os::unix::process::ExitStatusExt, + process::{Command, Stdio}, +}; + +mod common; +use common::*; + +#[derive(Debug, PartialEq)] +enum Context { + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + With, + Without, +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +fn get_ucontext() -> Result { + let mut context = std::mem::MaybeUninit::uninit(); + unsafe { + let res = crash_context::crash_context_getcontext(context.as_mut_ptr()); + Errno::result(res)?; + + Ok(context.assume_init()) + } +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +fn get_crash_context(tid: Pid) -> CrashContext { + let siginfo: libc::signalfd_siginfo = unsafe { std::mem::zeroed() }; + let context = get_ucontext().expect("Failed to get ucontext"); + let float_state = unsafe { std::mem::zeroed() }; + CrashContext { + inner: crash_context::CrashContext { + siginfo, + pid: std::process::id() as _, + tid, + context, + float_state, + }, + } +} + +fn test_write_dump_helper(context: Context) { + let num_of_threads = 3; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + let in_memory_buffer = tmp.dump(&mut tmpfile).expect("Could not write minidump"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + let mem_slice = std::fs::read(tmpfile.path()).expect("Failed to minidump"); + assert_eq!(mem_slice.len(), in_memory_buffer.len()); + assert_eq!(mem_slice, in_memory_buffer); +} + +#[test] +fn test_write_dump() { + test_write_dump_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_write_dump_with_context() { + test_write_dump_helper(Context::With) +} + +fn test_write_and_read_dump_from_parent_helper(context: Context) { + let mut child = start_child_and_return(&["spawn_mmap_wait"]); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_and_read_dump") + .tempfile() + .unwrap(); + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + let mmap_addr = output + .next() + .unwrap() + .parse() + .expect("unable to parse mmap_addr"); + let memory_size = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); + // Add information about the mapped memory. + let mapping = MappingInfo { + start_address: mmap_addr, + size: memory_size, + offset: 0, + executable: false, + name: Some("a fake mapping".to_string()), + system_mapping_info: SystemMappingInfo { + start_address: mmap_addr, + end_address: mmap_addr + memory_size, + }, + }; + + let identifier = vec![ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, + 0xFF, + ]; + let entry = MappingEntry { + mapping, + identifier, + }; + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + tmp.set_user_mapping_list(vec![entry]) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let module_list: MinidumpModuleList = dump + .get_stream() + .expect("Couldn't find stream MinidumpModuleList"); + let module = module_list + .module_at_address(mmap_addr as u64) + .expect("Couldn't find user mapping module"); + assert_eq!(module.base_address(), mmap_addr as u64); + assert_eq!(module.size(), memory_size as u64); + assert_eq!(module.code_file(), "a fake mapping"); + assert_eq!( + module.debug_identifier(), + Some("33221100554477668899AABBCCDDEEFF0".parse().unwrap()) + ); + + let _: MinidumpException = dump.get_stream().expect("Couldn't find MinidumpException"); + let _: MinidumpThreadList = dump.get_stream().expect("Couldn't find MinidumpThreadList"); + let _: MinidumpMemoryList = dump.get_stream().expect("Couldn't find MinidumpMemoryList"); + let _: MinidumpSystemInfo = dump.get_stream().expect("Couldn't find MinidumpSystemInfo"); + let _ = dump + .get_raw_stream(LinuxCpuInfo as u32) + .expect("Couldn't find LinuxCpuInfo"); + let _ = dump + .get_raw_stream(LinuxProcStatus as u32) + .expect("Couldn't find LinuxProcStatus"); + let _ = dump + .get_raw_stream(LinuxCmdLine as u32) + .expect("Couldn't find LinuxCmdLine"); + let _ = dump + .get_raw_stream(LinuxEnviron as u32) + .expect("Couldn't find LinuxEnviron"); + let _ = dump + .get_raw_stream(LinuxAuxv as u32) + .expect("Couldn't find LinuxAuxv"); + let _ = dump + .get_raw_stream(LinuxMaps as u32) + .expect("Couldn't find LinuxMaps"); + let _ = dump + .get_raw_stream(LinuxDsoDebug as u32) + .expect("Couldn't find LinuxDsoDebug"); +} + +#[test] +fn test_write_and_read_dump_from_parent() { + test_write_and_read_dump_from_parent_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_write_and_read_dump_from_parent_with_context() { + test_write_and_read_dump_from_parent_helper(Context::With) +} + +fn test_write_with_additional_memory_helper(context: Context) { + let mut child = start_child_and_return(&["spawn_alloc_wait"]); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("additional_memory") + .tempfile() + .unwrap(); + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + let memory_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) + .expect("unable to parse mmap_addr"); + let memory_size = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); + + let app_memory = AppMemory { + ptr: memory_addr, + length: memory_size, + }; + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + tmp.set_app_memory(vec![app_memory]) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + + let section: MinidumpMemoryList = dump.get_stream().expect("Couldn't find MinidumpMemoryList"); + let region = section + .memory_at_address(memory_addr as u64) + .expect("Couldn't find memory region"); + + assert_eq!(region.base_address, memory_addr as u64); + assert_eq!(region.size, memory_size as u64); + + let mut values = Vec::::with_capacity(memory_size); + for idx in 0..memory_size { + values.push((idx % 255) as u8); + } + + // Verify memory contents. + assert_eq!(region.bytes, values); +} + +#[test] +fn test_write_with_additional_memory() { + test_write_with_additional_memory_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_write_with_additional_memory_with_context() { + test_write_with_additional_memory_helper(Context::With) +} + +#[test] +fn test_minidump_size_limit() { + let num_of_threads = 40; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut total_normal_stack_size = 0; + let normal_file_size; + // First, write a minidump with no size limit. + { + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_unlimited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + normal_file_size = meta.len(); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + for thread in thread_list.threads { + assert!(thread.raw.thread_id > 0); + assert!(thread.raw.stack.memory.data_size > 0); + total_normal_stack_size += thread.raw.stack.memory.data_size; + } + } + + // Second, write a minidump with a size limit big enough to not trigger + // anything. + { + // Set size limit arbitrarily 2MiB larger than the normal file size -- such + // that the limiting code will not kick in. + let minidump_size_limit = normal_file_size + 2 * 1024 * 1024; + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_pseudolimited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .set_minidump_size_limit(minidump_size_limit) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + + // Make sure limiting wasn't actually triggered. NOTE: If you fail this, + // first make sure that "minidump_size_limit" above is indeed set to a + // large enough value -- the limit-checking code in minidump_writer.rs + // does just a rough estimate. + // TODO: Fix this properly + // There are occasionally CI failures where the sizes are off by 1 due + // some minor difference in (probably) a string somewhere in the dump + // since the state capture is not going to be 100% the same + //assert_eq!(meta.len(), normal_file_size); + let min = std::cmp::min(meta.len(), normal_file_size); + let max = std::cmp::max(meta.len(), normal_file_size); + + assert!(max - min < 10); + } + + // Third, write a minidump with a size limit small enough to be triggered. + { + // Set size limit to some arbitrary amount, such that the limiting code + // will kick in. The equation used to set this value was determined by + // simply reversing the size-limit logic a little bit in order to pick a + // size we know will trigger it. + + // Copyied from sections/thread_list_stream.rs + const LIMIT_AVERAGE_THREAD_STACK_LENGTH: u64 = 8 * 1024; + let mut minidump_size_limit = LIMIT_AVERAGE_THREAD_STACK_LENGTH * 40; + + // If, in reality, each of the threads' stack is *smaller* than + // kLimitAverageThreadStackLength, the normal file size could very well be + // smaller than the arbitrary limit that was just set. In that case, + // either of these numbers should trigger the size-limiting code, but we + // might as well pick the smallest. + if normal_file_size < minidump_size_limit { + minidump_size_limit = normal_file_size; + } + + let mut tmpfile = tempfile::Builder::new() + .prefix("write_dump_limited") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .set_minidump_size_limit(minidump_size_limit) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + // Make sure the file size is at least smaller than the original. If this + // fails because it's the same size, then the size-limit logic didn't kick + // in like it was supposed to. + assert!(meta.len() < normal_file_size); + + let mut total_limit_stack_size = 0; + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + for thread in thread_list.threads { + assert!(thread.raw.thread_id > 0); + assert!(thread.raw.stack.memory.data_size > 0); + total_limit_stack_size += thread.raw.stack.memory.data_size; + } + + // Make sure stack size shrunk by at least 1KB per extra thread. + // Note: The 1KB is arbitrary, and assumes that the thread stacks are big + // enough to shrink by that much. For example, if each thread stack was + // originally only 2KB, the current size-limit logic wouldn't actually + // shrink them because that's the size to which it tries to shrink. If + // you fail this part of the test due to something like that, the test + // logic should probably be improved to account for your situation. + + // Copyied from sections/thread_list_stream.rs + const LIMIT_BASE_THREAD_COUNT: usize = 20; + const MIN_PER_EXTRA_THREAD_STACK_REDUCTION: usize = 1024; + let min_expected_reduction = + (40 - LIMIT_BASE_THREAD_COUNT) * MIN_PER_EXTRA_THREAD_STACK_REDUCTION; + assert!(total_limit_stack_size < total_normal_stack_size - min_expected_reduction as u32); + } + + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); +} + +#[test] +fn test_with_deleted_binary() { + let num_of_threads = 1; + let binary_copy_dir = tempfile::Builder::new() + .prefix("deleted_binary") + .tempdir() + .unwrap(); + let binary_copy = binary_copy_dir.as_ref().join("binary_copy"); + + let path: &'static str = std::env!("CARGO_BIN_EXE_test"); + + std::fs::copy(path, &binary_copy).expect("Failed to copy binary"); + let mem_slice = std::fs::read(&binary_copy).expect("Failed to read binary"); + + let mut child = Command::new(&binary_copy) + .env("RUST_BACKTRACE", "1") + .arg("spawn_and_wait") + .arg(format!("{}", num_of_threads)) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to execute child"); + wait_for_threads(&mut child, num_of_threads); + + let pid = child.id() as i32; + + let build_id = PtraceDumper::elf_file_identifier_from_mapped_file(&mem_slice) + .expect("Failed to get build_id"); + + let guid = GUID { + data1: u32::from_ne_bytes(build_id[0..4].try_into().unwrap()), + data2: u16::from_ne_bytes(build_id[4..6].try_into().unwrap()), + data3: u16::from_ne_bytes(build_id[6..8].try_into().unwrap()), + data4: build_id[8..16].try_into().unwrap(), + }; + + // guid_to_string() is not public in minidump, so copied it here + // And append a zero, because module IDs include an "age" field + // which is always zero on Linux. + let filtered = format!( + "{:08X}{:04X}{:04X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}0", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7], + ); + // Strip out dashes + //let mut filtered: String = identifier.chars().filter(|x| *x != '-').collect(); + + std::fs::remove_file(&binary_copy).expect("Failed to remove binary"); + + let mut tmpfile = tempfile::Builder::new() + .prefix("deleted_binary") + .tempfile() + .unwrap(); + + MinidumpWriter::new(pid, pid) + .dump(&mut tmpfile) + .expect("Could not write minidump"); + + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Begin checks on dump + let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile"); + assert!(meta.len() > 0); + + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let module_list: MinidumpModuleList = dump + .get_stream() + .expect("Couldn't find stream MinidumpModuleList"); + let main_module = module_list + .main_module() + .expect("Could not get main module"); + assert_eq!(main_module.code_file(), binary_copy.to_string_lossy()); + assert_eq!(main_module.debug_identifier(), filtered.parse().ok()); +} + +fn test_skip_if_requested_helper(context: Context) { + let num_of_threads = 1; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("skip_if_requested") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + let pr_mapping_addr; + #[cfg(target_pointer_width = "64")] + { + pr_mapping_addr = 0x0102030405060708; + } + #[cfg(target_pointer_width = "32")] + { + pr_mapping_addr = 0x010203040; + }; + let res = tmp + .skip_stacks_if_mapping_unreferenced() + .set_principal_mapping_address(pr_mapping_addr) + .dump(&mut tmpfile); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + assert!(res.is_err()); +} + +#[test] +fn test_skip_if_requested() { + test_skip_if_requested_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_skip_if_requested_with_context() { + test_skip_if_requested_helper(Context::With) +} + +fn test_sanitized_stacks_helper(context: Context) { + let num_of_threads = 1; + let mut child = start_child_and_wait_for_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("skip_if_requested") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + tmp.sanitize_stack() + .dump(&mut tmpfile) + .expect("Faild to dump minidump"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + let dump_array = std::fs::read(tmpfile.path()).expect("Failed to read minidump as vec"); + let thread_list: MinidumpThreadList = + dump.get_stream().expect("Couldn't find MinidumpThreadList"); + + let defaced; + #[cfg(target_pointer_width = "64")] + { + defaced = 0x0defaced0defacedusize.to_ne_bytes(); + } + #[cfg(target_pointer_width = "32")] + { + defaced = 0x0defacedusize.to_ne_bytes() + }; + + for thread in thread_list.threads { + let mem = thread.raw.stack.memory; + let start = mem.rva as usize; + let end = (mem.rva + mem.data_size) as usize; + let slice = &dump_array.as_slice()[start..end]; + assert!(slice.windows(defaced.len()).any(|window| window == defaced)); + } +} + +#[test] +fn test_sanitized_stacks() { + test_sanitized_stacks_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_sanitized_stacks_with_context() { + test_sanitized_stacks_helper(Context::Without) +} + +fn test_write_early_abort_helper(context: Context) { + let mut child = start_child_and_return(&["spawn_alloc_wait"]); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("additional_memory") + .tempfile() + .unwrap(); + + let mut f = BufReader::new(child.stdout.as_mut().expect("Can't open stdout")); + let mut buf = String::new(); + let _ = f + .read_line(&mut buf) + .expect("Couldn't read address provided by child"); + let mut output = buf.split_whitespace(); + // We do not read the actual memory_address, but use NULL, which + // should create an error during dumping and lead to a truncated minidump. + let _ = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16) + .expect("unable to parse mmap_addr"); + let memory_addr = 0; + let memory_size = output + .next() + .unwrap() + .parse() + .expect("unable to parse memory_size"); + + let app_memory = AppMemory { + ptr: memory_addr, + length: memory_size, + }; + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + + // This should fail, because during the dump an error is detected (try_from fails) + match tmp.set_app_memory(vec![app_memory]).dump(&mut tmpfile) { + Err(WriterError::SectionAppMemoryError(_)) => (), + _ => panic!("Wrong kind of error returned"), + } + + child.kill().expect("Failed to kill process"); + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents. There should be a truncated minidump available + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + // Should be there + let _: MinidumpThreadList = dump.get_stream().expect("Couldn't find MinidumpThreadList"); + let _: MinidumpModuleList = dump.get_stream().expect("Couldn't find MinidumpThreadList"); + + // Should be missing: + assert!(dump.get_stream::().is_err()); +} + +#[test] +fn test_write_early_abort() { + test_write_early_abort_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_write_early_abort_with_context() { + test_write_early_abort_helper(Context::With) +} + +fn test_named_threads_helper(context: Context) { + let num_of_threads = 5; + let mut child = start_child_and_wait_for_named_threads(num_of_threads); + let pid = child.id() as i32; + + let mut tmpfile = tempfile::Builder::new() + .prefix("named_threads") + .tempfile() + .unwrap(); + + let mut tmp = MinidumpWriter::new(pid, pid); + #[cfg(not(any(target_arch = "mips", target_arch = "arm")))] + if context == Context::With { + let crash_context = get_crash_context(pid); + tmp.set_crash_context(crash_context); + } + let _ = tmp.dump(&mut tmpfile).expect("Could not write minidump"); + child.kill().expect("Failed to kill process"); + + // Reap child + let waitres = child.wait().expect("Failed to wait for child"); + let status = waitres.signal().expect("Child did not die due to signal"); + assert_eq!(waitres.code(), None); + assert_eq!(status, Signal::SIGKILL as i32); + + // Read dump file and check its contents. There should be a truncated minidump available + let dump = Minidump::read_path(tmpfile.path()).expect("Failed to read minidump"); + + let threads: MinidumpThreadList = dump.get_stream().expect("Couldn't find MinidumpThreadList"); + + let thread_names: MinidumpThreadNames = dump + .get_stream() + .expect("Couldn't find MinidumpThreadNames"); + + let thread_ids: Vec<_> = threads.threads.iter().map(|t| t.raw.thread_id).collect(); + let names: HashSet<_> = thread_ids + .iter() + .map(|id| thread_names.get_name(*id).unwrap_or_default()) + .map(|cow| cow.into_owned()) + .collect(); + let mut expected = HashSet::new(); + expected.insert("test".to_string()); + for id in 1..num_of_threads { + expected.insert(format!("thread_{}", id)); + } + assert_eq!(expected, names); +} + +#[test] +fn test_named_threads() { + test_named_threads_helper(Context::Without) +} + +#[cfg(not(any(target_arch = "mips", target_arch = "arm")))] +#[test] +fn test_named_threads_with_context() { + test_named_threads_helper(Context::With) +} -- cgit v1.2.3