807 lines
30 KiB
Rust
807 lines
30 KiB
Rust
#![cfg(any(target_os = "linux", target_os = "android"))]
|
|
#![allow(unused_imports, unused_variables)]
|
|
|
|
use {
|
|
common::*,
|
|
minidump::*,
|
|
minidump_common::format::{GUID, MINIDUMP_STREAM_TYPE::*},
|
|
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,
|
|
},
|
|
nix::{errno::Errno, sys::signal::Signal},
|
|
procfs_core::process::MMPermissions,
|
|
serde_json::json,
|
|
std::{
|
|
collections::HashSet,
|
|
io::{BufRead, BufReader},
|
|
os::unix::process::ExitStatusExt,
|
|
process::{Command, Stdio},
|
|
},
|
|
};
|
|
|
|
mod 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 expected_errors = vec![
|
|
json!("PrincipalMappingNotReferenced"),
|
|
];
|
|
|
|
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);
|
|
|
|
// Ensure the MozSoftErrors stream contains the expected errors
|
|
let dump = Minidump::read_path(tmpfile.path()).expect("failed to read minidump");
|
|
assert_soft_errors_in_minidump(&dump, &expected_errors);
|
|
}
|
|
}
|
|
|
|
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");
|
|
child.wait().expect("Failed to wait on killed 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);
|
|
}
|