Revision control

Copy as Markdown

Other Tools

#![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,
module_reader::{BuildId, ReadFromModule},
ptrace_dumper::PtraceDumper,
Pid,
};
use nix::{errno::Errno, sys::signal::Signal};
use procfs_core::process::MMPermissions;
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 {
With,
Without,
}
impl Context {
pub fn minidump_writer(&self, pid: Pid) -> MinidumpWriter {
let mut mw = MinidumpWriter::new(pid, pid);
#[cfg(not(target_arch = "mips"))]
if self == &Context::With {
let crash_context = get_crash_context(pid);
mw.set_crash_context(crash_context);
}
mw
}
}
#[cfg(not(target_arch = "mips"))]
fn get_ucontext() -> Result<crash_context::ucontext_t> {
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(target_arch = "mips"))]
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");
#[cfg(not(target_arch = "arm"))]
let float_state = unsafe { std::mem::zeroed() };
CrashContext {
inner: crash_context::CrashContext {
siginfo,
pid: std::process::id() as _,
tid,
context,
#[cfg(not(target_arch = "arm"))]
float_state,
},
}
}
macro_rules! contextual_test {
( $(#[$attr:meta])? fn $name:ident ($ctx:ident : Context) $body:block ) => {
mod $name {
use super::*;
fn test($ctx: Context) $body
#[test]
$(#[$attr])?
fn without_context() {
test(Context::Without)
}
#[cfg(not(target_arch = "mips"))]
#[test]
$(#[$attr])?
fn with_context() {
test(Context::With)
}
}
}
}
contextual_test! {
fn write_dump(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 = context.minidump_writer(pid);
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);
}
}
contextual_test! {
#[ignore]
fn write_and_read_dump_from_parent(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,
permissions: MMPermissions::READ | MMPermissions::WRITE,
name: Some("a fake mapping".into()),
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 = context.minidump_writer(pid);
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");
let _ = dump
.get_raw_stream(MozLinuxLimits as u32)
.expect("Couldn't find MozLinuxLimits");
}
}
contextual_test! {
fn write_with_additional_memory(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 = context.minidump_writer(pid);
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::<u8>::with_capacity(memory_size);
for idx in 0..memory_size {
values.push((idx % 255) as u8);
}
// Verify memory contents.
assert_eq!(region.bytes, values);
}
}
contextual_test! {
fn skip_if_requested(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 = context.minidump_writer(pid);
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());
}
}
contextual_test! {
fn sanitized_stacks(context: Context) {
if context == Context::With {
// FIXME the context's stack pointer very often doesn't lie in mapped memory, resulting
// in the stack memory having 0 size (so no slice will match `defaced` in the
// assertion).
return;
}
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("sanitized_stacks")
.tempfile()
.unwrap();
let mut tmp = context.minidump_writer(pid);
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));
}
}
}
contextual_test! {
fn write_early_abort(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 = context.minidump_writer(pid);
// 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::<MinidumpMemoryList>().is_err());
}
}
contextual_test! {
fn named_threads(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 = context.minidump_writer(pid);
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);
}
}
contextual_test! {
fn file_descriptors(context: Context) {
let num_of_files = 5;
let mut child = start_child_and_wait_for_create_files(num_of_files);
let pid = child.id() as i32;
let mut tmpfile = tempfile::Builder::new()
.prefix("testfiles")
.tempfile()
.unwrap();
let mut tmp = context.minidump_writer(pid);
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 fds: MinidumpHandleDataStream = dump.get_stream().expect("Couldn't find MinidumpHandleDataStream");
// We check that we create num_of_files plus stdin, stdout and stderr
for i in 0..3 {
let descriptor = fds.handles.get(i).expect("Descriptor should be present");
let fd = *descriptor.raw.handle().expect("Handle should be populated");
assert_eq!(fd, i as u64);
}
let non_std_files = &fds.handles[3..];
// We need to handle the android case where additional pipes might be opened and
// interspersed with the test_files (emulator? adb?) so that CI doesn't sporadically fail
for i in 0..num_of_files {
if !non_std_files.iter().any(|descriptor| {
let Some(name) = &descriptor.object_name else { return false; };
let Some(file_name) = name.rsplit_once('/').map(|(_, fname)| fname) else { return false; };
if !file_name.starts_with("test_file") {
return false;
}
file_name.ends_with(&i.to_string())
}) {
panic!("unable to locate expected file `test_file{i}` in file handle stream");
}
}
}
}
#[test]
fn 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
//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);
// Setting a stack limit limits the size of non-main stacks even before
// the limit is reached. This will cause slight variations in size
// between a limited and an unlimited minidump.
assert!(max - min < 1024, "max = {max:} min = {min:}");
}
// 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 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: String = if let Ok(p) = std::env::var("TEST_HELPER") {
p
} else {
std::env!("CARGO_BIN_EXE_test").into()
};
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(num_of_threads.to_string())
.stdout(Stdio::piped())
.spawn()
.expect("failed to execute child");
wait_for_threads(&mut child, num_of_threads);
let pid = child.id() as i32;
let BuildId(mut build_id) =
BuildId::read_from_module(mem_slice.as_slice().into()).expect("Failed to get build_id");
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());
let did = main_module
.debug_identifier()
.expect("expected value debug id");
{
let uuid = did.uuid();
let uuid = uuid.as_bytes();
// Swap bytes in the original to match the expected uuid
if cfg!(target_endian = "little") {
build_id[..4].reverse();
build_id[4..6].reverse();
build_id[6..8].reverse();
}
// The build_id from the binary can be as little as 8 bytes, eg LLD uses
// xxhash to calculate the build_id by default from 10+
build_id.resize(16, 0);
assert_eq!(uuid.as_slice(), &build_id);
}
// The 'age'/appendix, always 0 on non-windows targets
assert_eq!(did.appendix(), 0);
}
#[test]
fn memory_info_list_stream() {
let mut child = start_child_and_wait_for_threads(1);
let pid = child.id() as i32;
let mut tmpfile = tempfile::Builder::new()
.prefix("memory_info_list_stream")
.tempfile()
.unwrap();
// Write a minidump
MinidumpWriter::new(pid, pid)
.dump(&mut tmpfile)
.expect("cound not write minidump");
child.kill().expect("Failed to kill process");
// Ensure the minidump has a MemoryInfoListStream present and has at least one entry.
let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
let list: MinidumpMemoryInfoList = dump.get_stream().expect("no memory info list");
assert!(list.iter().count() > 1);
}