diff --git a/examples/synthetic.rs b/examples/synthetic.rs
index 58ddc7d2..63681d99 100644
--- a/examples/synthetic.rs
+++ b/examples/synthetic.rs
@@ -1,11 +1,12 @@
//! Emits default minidump with no streams to specified path
-use std::fs::File;
-
-use minidump_writer::{
- dir_section::DirSection,
- mem_writer::{Buffer, MemoryWriter},
- minidump_format::{MDRawHeader, MD_HEADER_SIGNATURE, MD_HEADER_VERSION},
+use {
+ minidump_writer::{
+ dir_section::DirSection,
+ mem_writer::{Buffer, MemoryWriter},
+ minidump_format::{MDRawHeader, MD_HEADER_SIGNATURE, MD_HEADER_VERSION},
+ },
+ std::fs::File,
};
// usage: `cargo run --example synthetic /tmp/micro-minidump.dmp`
diff --git a/src/bin/test.rs b/src/bin/test.rs
index 9ab42f13..07fa81df 100644
--- a/src/bin/test.rs
+++ b/src/bin/test.rs
@@ -10,8 +10,8 @@ mod linux {
super::*,
error_graph::ErrorList,
minidump_writer::{
- minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper,
- LINUX_GATE_LIBRARY_NAME,
+ minidump_writer::{MinidumpWriter, MinidumpWriterConfig},
+ module_reader, LINUX_GATE_LIBRARY_NAME,
},
nix::{
sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
@@ -40,12 +40,8 @@ mod linux {
let ppid = getppid();
fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid.as_raw(),
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )?
+ MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw())
+ .build_for_testing(&mut soft_errors)?
);
Ok(())
}
@@ -54,12 +50,8 @@ mod linux {
let ppid = getppid();
let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid.as_raw(),
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )?
+ MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw())
+ .build_for_testing(&mut soft_errors)?
);
test!(!dumper.threads.is_empty(), "No threads");
test!(
@@ -87,18 +79,11 @@ mod linux {
use minidump_writer::mem_reader::MemReader;
let ppid = getppid().as_raw();
- let mut dumper = fail_on_soft_error!(
+ let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid,
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors
- )?
+ MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)?
);
- fail_on_soft_error!(soft_errors, dumper.suspend_threads(&mut soft_errors));
-
// We support 3 different methods of reading memory from another
// process, ensure they all function and give the same results
@@ -141,30 +126,27 @@ mod linux {
}
let stack_res =
- PtraceDumper::copy_from_process(ppid, stack_var, std::mem::size_of::())?;
+ MinidumpWriter::copy_from_process(ppid, stack_var, std::mem::size_of::())?;
test!(stack_res == expected_stack, "stack var not correct");
let heap_res =
- PtraceDumper::copy_from_process(ppid, heap_var, std::mem::size_of::())?;
+ MinidumpWriter::copy_from_process(ppid, heap_var, std::mem::size_of::())?;
test!(heap_res == expected_heap, "heap var not correct");
- fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));
+ drop(dumper);
Ok(())
}
fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
let ppid = getppid();
+
let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid.as_raw(),
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )?
+ MinidumpWriterConfig::new(ppid.as_raw(), ppid.as_raw())
+ .build_for_testing(&mut soft_errors)?
);
dumper
.find_mapping(addr1)
@@ -185,16 +167,9 @@ mod linux {
let mut dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid,
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors
- )?
+ MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)?
);
- fail_on_soft_error!(soft_errors, dumper.suspend_threads(&mut soft_errors));
-
let mut found_exe = None;
for (idx, mapping) in dumper.mappings.iter().enumerate() {
if mapping.name.as_ref().map(|x| x.into()).as_ref() == Some(&exe_name) {
@@ -205,7 +180,7 @@ mod linux {
let idx = found_exe.unwrap();
let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?;
- fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));
+ drop(dumper);
assert!(!id.is_empty());
assert!(id.iter().any(|&x| x > 0));
@@ -216,12 +191,8 @@ mod linux {
// Now check that PtraceDumper interpreted the mappings properly.
let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- getppid().as_raw(),
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )?
+ MinidumpWriterConfig::new(getppid().as_raw(), getppid().as_raw())
+ .build_for_testing(&mut soft_errors)?
);
let mut mapping_count = 0;
for map in &dumper.mappings {
@@ -244,29 +215,20 @@ mod linux {
fn test_linux_gate_mapping_id() -> Result<()> {
let ppid = getppid().as_raw();
- let mut dumper = fail_on_soft_error!(
+ let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid,
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors
- )?
+ MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)?
);
let mut found_linux_gate = false;
for mapping in dumper.mappings.clone() {
if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
found_linux_gate = true;
- fail_on_soft_error!(soft_errors, dumper.suspend_threads(&mut soft_errors));
-
let module_reader::BuildId(id) =
- PtraceDumper::from_process_memory_for_mapping(&mapping, ppid)?;
+ MinidumpWriter::from_process_memory_for_mapping(&mapping, ppid)?;
test!(!id.is_empty(), "id-vec is empty");
test!(id.iter().any(|&x| x > 0), "all id elements are 0");
-
- fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));
-
+ drop(dumper);
break;
}
}
@@ -278,12 +240,7 @@ mod linux {
let ppid = getppid().as_raw();
let dumper = fail_on_soft_error!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- ppid,
- STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors
- )?
+ MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)?
);
let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap();
test!(linux_gate_loc != 0, "linux_gate_loc == 0");
@@ -332,7 +289,7 @@ mod linux {
// One less than the requested amount, as the main thread counts as well
for id in 1..num {
std::thread::Builder::new()
- .name(format!("thread_{}", id))
+ .name(format!("thread_{id}"))
.spawn(|| {
println!("1");
loop {
diff --git a/src/linux/android.rs b/src/linux/android.rs
index 18d35443..7eee7cb5 100644
--- a/src/linux/android.rs
+++ b/src/linux/android.rs
@@ -1,8 +1,10 @@
-use crate::errors::AndroidError;
-use crate::maps_reader::MappingInfo;
-use crate::ptrace_dumper::PtraceDumper;
-use crate::Pid;
-use goblin::elf;
+use {
+ super::{
+ maps_reader::MappingInfo, mem_reader::CopyFromProcessError,
+ minidump_writer::MinidumpWriter, Pid,
+ },
+ goblin::elf,
+};
cfg_if::cfg_if! {
if #[cfg(target_pointer_width = "32")] {
@@ -26,6 +28,20 @@ cfg_if::cfg_if! {
type Result = std::result::Result;
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum AndroidError {
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] CopyFromProcessError),
+ #[error("Failed slice conversion")]
+ TryFromSliceError(
+ #[from]
+ #[serde(skip)]
+ std::array::TryFromSliceError,
+ ),
+ #[error("No Android rel found")]
+ NoRelFound,
+}
+
struct DynVaddresses {
min_vaddr: usize,
dyn_vaddr: usize,
@@ -36,7 +52,7 @@ fn has_android_packed_relocations(pid: Pid, load_bias: usize, vaddrs: DynVaddres
let dyn_addr = load_bias + vaddrs.dyn_vaddr;
for idx in 0..vaddrs.dyn_count {
let addr = dyn_addr + SIZEOF_DYN * idx;
- let dyn_data = PtraceDumper::copy_from_process(pid, addr, SIZEOF_DYN)?;
+ let dyn_data = MinidumpWriter::copy_from_process(pid, addr, SIZEOF_DYN)?;
// TODO: Couldn't find a nice way to use goblin for that, to avoid the unsafe-block
let dyn_obj: Dyn;
unsafe {
@@ -76,7 +92,7 @@ fn parse_loaded_elf_program_headers(
let mut dyn_vaddr = 0;
let mut dyn_count = 0;
- let phdr_opt = PtraceDumper::copy_from_process(
+ let phdr_opt = MinidumpWriter::copy_from_process(
pid,
phdr_addr,
elf_header::SIZEOF_EHDR * ehdr.e_phnum as usize,
@@ -114,7 +130,7 @@ pub fn late_process_mappings(pid: Pid, mappings: &mut [MappingInfo]) -> Result<(
.filter(|m| m.is_executable() && m.name_is_path())
{
let ehdr_opt =
- PtraceDumper::copy_from_process(pid, map.start_address, elf_header::SIZEOF_EHDR)
+ MinidumpWriter::copy_from_process(pid, map.start_address, elf_header::SIZEOF_EHDR)
.ok()
.and_then(|x| elf_header::Header::parse(&x).ok());
diff --git a/src/linux/auxv/mod.rs b/src/linux/auxv/mod.rs
index 527fc45b..01654798 100644
--- a/src/linux/auxv/mod.rs
+++ b/src/linux/auxv/mod.rs
@@ -1,6 +1,7 @@
use {
self::reader::ProcfsAuxvIter,
- crate::{serializers::*, Pid},
+ super::Pid,
+ crate::serializers::*,
error_graph::WriteErrorList,
failspot::failspot,
std::{fs::File, io::BufReader},
diff --git a/src/linux/auxv/reader.rs b/src/linux/auxv/reader.rs
index dadb9979..e36ba93a 100644
--- a/src/linux/auxv/reader.rs
+++ b/src/linux/auxv/reader.rs
@@ -99,6 +99,6 @@ fn read_long(reader: &mut dyn Read) -> std::io::Result {
match std::mem::size_of::() {
4 => reader.read_u32::().map(|u| u as AuxvType),
8 => reader.read_u64::().map(|u| u as AuxvType),
- x => panic!("Unexpected type width: {}", x),
+ x => panic!("Unexpected type width: {x}"),
}
}
diff --git a/src/linux/crash_context/aarch64.rs b/src/linux/crash_context/aarch64.rs
index c53c3772..f254ca47 100644
--- a/src/linux/crash_context/aarch64.rs
+++ b/src/linux/crash_context/aarch64.rs
@@ -1,7 +1,9 @@
-use super::CrashContext;
-use crate::{
- minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
- minidump_format::format,
+use {
+ super::CrashContext,
+ crate::{
+ minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
+ minidump_format::format,
+ },
};
impl CrashContext {
diff --git a/src/linux/crash_context/arm.rs b/src/linux/crash_context/arm.rs
index e4e40216..7cf830a7 100644
--- a/src/linux/crash_context/arm.rs
+++ b/src/linux/crash_context/arm.rs
@@ -1,5 +1,4 @@
-use super::CrashContext;
-use crate::minidump_cpu::RawContextCPU;
+use {super::CrashContext, crate::minidump_cpu::RawContextCPU};
impl CrashContext {
pub fn get_instruction_pointer(&self) -> usize {
diff --git a/src/linux/crash_context.rs b/src/linux/crash_context/mod.rs
similarity index 63%
rename from src/linux/crash_context.rs
rename to src/linux/crash_context/mod.rs
index f7a554d1..185ad769 100644
--- a/src/linux/crash_context.rs
+++ b/src/linux/crash_context/mod.rs
@@ -6,6 +6,16 @@ pub struct CrashContext {
pub inner: crash_context::CrashContext,
}
+impl std::fmt::Debug for CrashContext {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("CrashContext")
+ .field("siginfo", &self.inner.siginfo)
+ .field("pid", &self.inner.pid)
+ .field("tid", &self.inner.tid)
+ .finish_non_exhaustive()
+ }
+}
+
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
mod x86_64;
diff --git a/src/linux/crash_context/x86.rs b/src/linux/crash_context/x86.rs
index 5a2e43ee..d8fb3322 100644
--- a/src/linux/crash_context/x86.rs
+++ b/src/linux/crash_context/x86.rs
@@ -1,8 +1,10 @@
-use super::CrashContext;
-use crate::{minidump_cpu::RawContextCPU, minidump_format::format::ContextFlagsX86};
-use libc::{
- REG_CS, REG_DS, REG_EAX, REG_EBP, REG_EBX, REG_ECX, REG_EDI, REG_EDX, REG_EFL, REG_EIP, REG_ES,
- REG_ESI, REG_ESP, REG_FS, REG_GS, REG_SS, REG_UESP,
+use {
+ super::CrashContext,
+ crate::{minidump_cpu::RawContextCPU, minidump_format::format::ContextFlagsX86},
+ libc::{
+ REG_CS, REG_DS, REG_EAX, REG_EBP, REG_EBX, REG_ECX, REG_EDI, REG_EDX, REG_EFL, REG_EIP,
+ REG_ES, REG_ESI, REG_ESP, REG_FS, REG_GS, REG_SS, REG_UESP,
+ },
};
impl CrashContext {
pub fn get_instruction_pointer(&self) -> usize {
diff --git a/src/linux/crash_context/x86_64.rs b/src/linux/crash_context/x86_64.rs
index e5596926..2d5f29c2 100644
--- a/src/linux/crash_context/x86_64.rs
+++ b/src/linux/crash_context/x86_64.rs
@@ -1,12 +1,12 @@
-use super::CrashContext;
-use crate::{
- minidump_cpu::RawContextCPU, minidump_format::format, thread_info::copy_u32_registers,
+use {
+ super::{super::thread_info::copy_u32_registers, CrashContext},
+ crate::{minidump_cpu::RawContextCPU, minidump_format::format},
+ libc::{
+ REG_CSGSFS, REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9,
+ REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP,
+ },
+ scroll::Pwrite,
};
-use libc::{
- REG_CSGSFS, REG_EFL, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9,
- REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP,
-};
-use scroll::Pwrite;
impl CrashContext {
pub fn get_instruction_pointer(&self) -> usize {
diff --git a/src/linux/dso_debug.rs b/src/linux/dso_debug.rs
index ef27dd5a..67d6e6f8 100644
--- a/src/linux/dso_debug.rs
+++ b/src/linux/dso_debug.rs
@@ -1,7 +1,14 @@
-use crate::{
- linux::{auxv::AuxvDumpInfo, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper},
- mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter},
- minidump_format::*,
+use {
+ super::{
+ auxv::AuxvDumpInfo, mem_reader::CopyFromProcessError, minidump_writer::MinidumpWriter,
+ serializers::*,
+ },
+ crate::{
+ mem_writer::{
+ write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError,
+ },
+ minidump_format::*,
+ },
};
type Result = std::result::Result;
@@ -26,6 +33,22 @@ cfg_if::cfg_if! {
}
}
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum SectionDsoDebugError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Could not find: {0}")]
+ CouldNotFind(&'static str),
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] CopyFromProcessError),
+ #[error("Failed to copy memory from process")]
+ FromUTF8Error(
+ #[from]
+ #[serde(serialize_with = "serialize_from_utf8_error")]
+ std::string::FromUtf8Error,
+ ),
+}
+
// COPY from
#[derive(Debug, Clone, Default)]
#[repr(C)]
@@ -85,7 +108,7 @@ pub fn write_dso_debug_stream(
.get_program_header_address()
.ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize;
- let ph = PtraceDumper::copy_from_process(blamed_thread, phdr, SIZEOF_PHDR * phnum_max)?;
+ let ph = MinidumpWriter::copy_from_process(blamed_thread, phdr, SIZEOF_PHDR * phnum_max)?;
let program_headers;
#[cfg(target_pointer_width = "64")]
{
@@ -131,7 +154,7 @@ pub fn write_dso_debug_stream(
// DSOs loaded into the program. If this information is indeed available,
// dump it to a MD_LINUX_DSO_DEBUG stream.
loop {
- let dyn_data = PtraceDumper::copy_from_process(
+ let dyn_data = MinidumpWriter::copy_from_process(
blamed_thread,
dyn_addr as usize + dynamic_length,
dyn_size,
@@ -160,7 +183,7 @@ pub fn write_dso_debug_stream(
// loader communicates with debuggers.
let debug_entry_data =
- PtraceDumper::copy_from_process(blamed_thread, r_debug, std::mem::size_of::())?;
+ MinidumpWriter::copy_from_process(blamed_thread, r_debug, std::mem::size_of::())?;
// goblin::elf::Dyn doesn't have padding bytes
let (head, body, _tail) = unsafe { debug_entry_data.align_to::() };
@@ -171,7 +194,7 @@ pub fn write_dso_debug_stream(
let mut dso_vec = Vec::new();
let mut curr_map = debug_entry.r_map;
while curr_map != 0 {
- let link_map_data = PtraceDumper::copy_from_process(
+ let link_map_data = MinidumpWriter::copy_from_process(
blamed_thread,
curr_map,
std::mem::size_of::(),
@@ -198,7 +221,7 @@ pub fn write_dso_debug_stream(
let mut filename = String::new();
if map.l_name > 0 {
let filename_data =
- PtraceDumper::copy_from_process(blamed_thread, map.l_name, 256)?;
+ MinidumpWriter::copy_from_process(blamed_thread, map.l_name, 256)?;
// C - string is NULL-terminated
if let Some(name) = filename_data.splitn(2, |x| *x == b'\0').next() {
@@ -234,7 +257,7 @@ pub fn write_dso_debug_stream(
dirent.location.data_size += dynamic_length as u32;
let dso_debug_data =
- PtraceDumper::copy_from_process(blamed_thread, dyn_addr as usize, dynamic_length)?;
+ MinidumpWriter::copy_from_process(blamed_thread, dyn_addr as usize, dynamic_length)?;
MemoryArrayWriter::write_bytes(buffer, &dso_debug_data);
Ok(dirent)
diff --git a/src/linux/dumper_cpu_info/arm.rs b/src/linux/dumper_cpu_info/arm.rs
index 886920f5..712c43c6 100644
--- a/src/linux/dumper_cpu_info/arm.rs
+++ b/src/linux/dumper_cpu_info/arm.rs
@@ -1,10 +1,13 @@
-use crate::{errors::CpuInfoError, minidump_format::*};
-use scroll::Pwrite;
-use std::{
- collections::HashSet,
- fs::File,
- io::{BufRead, BufReader, Read},
- path,
+use {
+ super::CpuInfoError,
+ crate::minidump_format::*,
+ scroll::Pwrite,
+ std::{
+ collections::HashSet,
+ fs::File,
+ io::{BufRead, BufReader, Read},
+ path,
+ },
};
type Result = std::result::Result;
diff --git a/src/linux/dumper_cpu_info.rs b/src/linux/dumper_cpu_info/mod.rs
similarity index 75%
rename from src/linux/dumper_cpu_info.rs
rename to src/linux/dumper_cpu_info/mod.rs
index 72da20fe..54a4f3b7 100644
--- a/src/linux/dumper_cpu_info.rs
+++ b/src/linux/dumper_cpu_info/mod.rs
@@ -1,3 +1,8 @@
+use {
+ crate::{minidump_format::PlatformId, serializers::*},
+ nix::sys::utsname::uname,
+};
+
cfg_if::cfg_if! {
if #[cfg(any(
target_arch = "x86_64",
@@ -20,8 +25,25 @@ cfg_if::cfg_if! {
pub use imp::write_cpu_information;
-use crate::minidump_format::PlatformId;
-use nix::sys::utsname::uname;
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum CpuInfoError {
+ #[error("IO error for file /proc/cpuinfo")]
+ IOError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Not all entries of /proc/cpuinfo found!")]
+ NotAllProcEntriesFound,
+ #[error("Couldn't parse core from file")]
+ UnparsableInteger(
+ #[from]
+ #[serde(skip)]
+ std::num::ParseIntError,
+ ),
+ #[error("Couldn't parse cores: {0}")]
+ UnparsableCores(String),
+}
/// Retrieves the [`MDOSPlatform`] and synthesized version information
pub fn os_information() -> (PlatformId, String) {
diff --git a/src/linux/dumper_cpu_info/x86_mips.rs b/src/linux/dumper_cpu_info/x86_mips.rs
index 0999065e..ef0659e6 100644
--- a/src/linux/dumper_cpu_info/x86_mips.rs
+++ b/src/linux/dumper_cpu_info/x86_mips.rs
@@ -1,5 +1,6 @@
use {
- crate::{errors::CpuInfoError, minidump_format::*},
+ super::CpuInfoError,
+ crate::minidump_format::*,
failspot::failspot,
std::{
io::{BufRead, BufReader},
diff --git a/src/linux/errors.rs b/src/linux/errors.rs
deleted file mode 100644
index 95322f10..00000000
--- a/src/linux/errors.rs
+++ /dev/null
@@ -1,454 +0,0 @@
-use {
- super::{ptrace_dumper::InitError, serializers::*},
- crate::{
- dir_section::FileWriterError, maps_reader::MappingInfo, mem_writer::MemoryWriterError,
- serializers::*, Pid,
- },
- error_graph::ErrorList,
- std::ffi::OsString,
- thiserror::Error,
-};
-
-#[derive(Error, Debug, serde::Serialize)]
-pub enum MapsReaderError {
- #[error("Couldn't parse as ELF file")]
- ELFParsingFailed(
- #[from]
- #[serde(serialize_with = "serialize_goblin_error")]
- goblin::error::Error,
- ),
- #[error("No soname found (filename: {})", .0.to_string_lossy())]
- NoSoName(OsString, #[source] ModuleReaderError),
-
- // parse_from_line()
- #[error("Map entry malformed: No {0} found")]
- MapEntryMalformed(&'static str),
- #[error("Couldn't parse address")]
- UnparsableInteger(
- #[from]
- #[serde(skip)]
- std::num::ParseIntError,
- ),
- #[error("Linux gate location doesn't fit in the required integer type")]
- LinuxGateNotConvertable(
- #[from]
- #[serde(skip)]
- std::num::TryFromIntError,
- ),
-
- // get_mmap()
- #[error("Not safe to open mapping {}", .0.to_string_lossy())]
- NotSafeToOpenMapping(OsString),
- #[error("IO Error")]
- FileError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Mmapped file empty or not an ELF file")]
- MmapSanityCheckFailed,
- #[error("Symlink does not match ({0} vs. {1})")]
- SymlinkError(std::path::PathBuf, std::path::PathBuf),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum CpuInfoError {
- #[error("IO error for file /proc/cpuinfo")]
- IOError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Not all entries of /proc/cpuinfo found!")]
- NotAllProcEntriesFound,
- #[error("Couldn't parse core from file")]
- UnparsableInteger(
- #[from]
- #[serde(skip)]
- std::num::ParseIntError,
- ),
- #[error("Couldn't parse cores: {0}")]
- UnparsableCores(String),
-}
-
-#[derive(Error, Debug, serde::Serialize)]
-pub enum ThreadInfoError {
- #[error("Index out of bounds: Got {0}, only have {1}")]
- IndexOutOfBounds(usize, usize),
- #[error("Either ppid ({1}) or tgid ({2}) not found in {0}")]
- InvalidPid(String, Pid, Pid),
- #[error("IO error")]
- IOError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Couldn't parse address")]
- UnparsableInteger(
- #[from]
- #[serde(skip)]
- std::num::ParseIntError,
- ),
- #[error("nix::ptrace() error")]
- PtraceError(
- #[from]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("Invalid line in /proc/{0}/status: {1}")]
- InvalidProcStatusFile(Pid, String),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum AndroidError {
- #[error("Failed to copy memory from process")]
- CopyFromProcessError(#[from] DumperError),
- #[error("Failed slice conversion")]
- TryFromSliceError(
- #[from]
- #[serde(skip)]
- std::array::TryFromSliceError,
- ),
- #[error("No Android rel found")]
- NoRelFound,
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")]
-pub struct CopyFromProcessError {
- pub child: Pid,
- pub src: usize,
- pub offset: usize,
- pub length: usize,
- #[serde(serialize_with = "serialize_nix_error")]
- pub source: nix::Error,
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum DumperError {
- #[error("Failed to get PAGE_SIZE from system")]
- SysConfError(
- #[from]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("wait::waitpid(Pid={0}) failed")]
- WaitPidError(
- Pid,
- #[source]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("nix::ptrace::attach(Pid={0}) failed")]
- PtraceAttachError(
- Pid,
- #[source]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("nix::ptrace::detach(Pid={0}) failed")]
- PtraceDetachError(
- Pid,
- #[source]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error(transparent)]
- CopyFromProcessError(#[from] CopyFromProcessError),
- #[error("Skipped thread {0} due to it being part of the seccomp sandbox's trusted code")]
- DetachSkippedThread(Pid),
- #[error("No mapping for stack pointer found")]
- NoStackPointerMapping,
- #[error("Failed slice conversion")]
- TryFromSliceError(
- #[from]
- #[serde(skip)]
- std::array::TryFromSliceError,
- ),
- #[error("Couldn't parse as ELF file")]
- ELFParsingFailed(
- #[from]
- #[serde(serialize_with = "serialize_goblin_error")]
- goblin::error::Error,
- ),
- #[error("Could not read value from module")]
- ModuleReaderError(#[from] ModuleReaderError),
- #[error("Not safe to open mapping: {}", .0.to_string_lossy())]
- NotSafeToOpenMapping(OsString),
- #[error("Failed integer conversion")]
- TryFromIntError(
- #[from]
- #[serde(skip)]
- std::num::TryFromIntError,
- ),
- #[error("Maps reader error")]
- MapsReaderError(#[from] MapsReaderError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionAppMemoryError {
- #[error("Failed to copy memory from process")]
- CopyFromProcessError(#[from] DumperError),
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionExceptionStreamError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionHandleDataStreamError {
- #[error("Failed to access file")]
- IOError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed integer conversion")]
- TryFromIntError(
- #[from]
- #[serde(skip)]
- std::num::TryFromIntError,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionMappingsError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed to get effective path of mapping ({0:?})")]
- GetEffectivePathError(MappingInfo, #[source] MapsReaderError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionMemInfoListError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed to read from procfs")]
- ProcfsError(
- #[from]
- #[serde(serialize_with = "serialize_proc_error")]
- procfs_core::ProcError,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionMemListError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionSystemInfoError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed to get CPU Info")]
- CpuInfoError(#[from] CpuInfoError),
- #[error("Failed trying to write CPU information")]
- WriteCpuInformationFailed(#[source] CpuInfoError),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionThreadListError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed integer conversion")]
- TryFromIntError(
- #[from]
- #[serde(skip)]
- std::num::TryFromIntError,
- ),
- #[error("Failed to copy memory from process")]
- CopyFromProcessError(#[from] DumperError),
- #[error("Failed to get thread info")]
- ThreadInfoError(#[from] ThreadInfoError),
- #[error("Failed to write to memory buffer")]
- IOError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionThreadNamesError {
- #[error("Failed integer conversion")]
- TryFromIntError(
- #[from]
- #[serde(skip)]
- std::num::TryFromIntError,
- ),
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed to write to memory buffer")]
- IOError(
- #[from]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum SectionDsoDebugError {
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Could not find: {0}")]
- CouldNotFind(&'static str),
- #[error("Failed to copy memory from process")]
- CopyFromProcessError(#[from] DumperError),
- #[error("Failed to copy memory from process")]
- FromUTF8Error(
- #[from]
- #[serde(serialize_with = "serialize_from_utf8_error")]
- std::string::FromUtf8Error,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum WriterError {
- #[error("Error during init phase")]
- InitError(#[from] InitError),
- #[error(transparent)]
- DumperError(#[from] DumperError),
- #[error("Failed when writing section AppMemory")]
- SectionAppMemoryError(#[from] SectionAppMemoryError),
- #[error("Failed when writing section ExceptionStream")]
- SectionExceptionStreamError(#[from] SectionExceptionStreamError),
- #[error("Failed when writing section HandleDataStream")]
- SectionHandleDataStreamError(#[from] SectionHandleDataStreamError),
- #[error("Failed when writing section MappingsError")]
- SectionMappingsError(#[from] SectionMappingsError),
- #[error("Failed when writing section MemList")]
- SectionMemListError(#[from] SectionMemListError),
- #[error("Failed when writing section SystemInfo")]
- SectionSystemInfoError(#[from] SectionSystemInfoError),
- #[error("Failed when writing section MemoryInfoList")]
- SectionMemoryInfoListError(#[from] SectionMemInfoListError),
- #[error("Failed when writing section ThreadList")]
- SectionThreadListError(#[from] SectionThreadListError),
- #[error("Failed when writing section ThreadNameList")]
- SectionThreadNamesError(#[from] SectionThreadNamesError),
- #[error("Failed when writing section DsoDebug")]
- SectionDsoDebugError(#[from] SectionDsoDebugError),
- #[error("Failed to write to memory")]
- MemoryWriterError(#[from] MemoryWriterError),
- #[error("Failed to write to file")]
- FileWriterError(#[from] FileWriterError),
- #[error("Failed to get current timestamp when writing header of minidump")]
- SystemTimeError(
- #[from]
- #[serde(serialize_with = "serialize_system_time_error")]
- std::time::SystemTimeError,
- ),
- #[error("Errors occurred while initializing PTraceDumper")]
- InitErrors(#[source] ErrorList),
- #[error("Errors occurred while suspending threads")]
- SuspendThreadsErrors(#[source] ErrorList),
- #[error("Errors occurred while resuming threads")]
- ResumeThreadsErrors(#[source] ErrorList),
- #[error("Crash thread does not reference principal mapping")]
- PrincipalMappingNotReferenced,
- #[error("Errors occurred while writing system info")]
- WriteSystemInfoErrors(#[source] ErrorList),
- #[error("Failed writing cpuinfo")]
- WriteCpuInfoFailed(#[source] MemoryWriterError),
- #[error("Failed writing thread proc status")]
- WriteThreadProcStatusFailed(#[source] MemoryWriterError),
- #[error("Failed writing OS Release Information")]
- WriteOsReleaseInfoFailed(#[source] MemoryWriterError),
- #[error("Failed writing process command line")]
- WriteCommandLineFailed(#[source] MemoryWriterError),
- #[error("Writing process environment failed")]
- WriteEnvironmentFailed(#[source] MemoryWriterError),
- #[error("Failed to write auxv file")]
- WriteAuxvFailed(#[source] MemoryWriterError),
- #[error("Failed to write maps file")]
- WriteMapsFailed(#[source] MemoryWriterError),
- #[error("Failed writing DSO Debug Stream")]
- WriteDSODebugStreamFailed(#[source] SectionDsoDebugError),
- #[error("Failed writing limits file")]
- WriteLimitsFailed(#[source] MemoryWriterError),
- #[error("Failed writing handle data stream")]
- WriteHandleDataStreamFailed(#[source] SectionHandleDataStreamError),
- #[error("Failed writing handle data stream direction entry")]
- WriteHandleDataStreamDirentFailed(#[source] FileWriterError),
- #[error("No threads left to suspend out of {0}")]
- SuspendNoThreadsLeft(usize),
- #[error("Failed to convert soft error list to JSON")]
- ConvertToJsonFailed(
- #[source]
- #[serde(skip)]
- serde_json::Error,
- ),
-}
-
-#[derive(Debug, Error, serde::Serialize)]
-pub enum ModuleReaderError {
- #[error("failed to read module file ({path}): {error}")]
- MapFile {
- path: std::path::PathBuf,
- #[source]
- #[serde(serialize_with = "serialize_io_error")]
- error: std::io::Error,
- },
- #[error("failed to read module memory: {length} bytes at {offset}{}: {error}", .start_address.map(|addr| format!(" (start address: {addr})")).unwrap_or_default())]
- ReadModuleMemory {
- offset: u64,
- length: u64,
- start_address: Option,
- #[source]
- #[serde(serialize_with = "serialize_nix_error")]
- error: nix::Error,
- },
- #[error("failed to parse ELF memory: {0}")]
- Parsing(
- #[from]
- #[serde(serialize_with = "serialize_goblin_error")]
- goblin::error::Error,
- ),
- #[error("no build id notes in program headers")]
- NoProgramHeaderNote,
- #[error("no string table available to locate note sections")]
- NoStrTab,
- #[error("no build id note sections")]
- NoSectionNote,
- #[error("the ELF data contains no program headers")]
- NoProgramHeaders,
- #[error("the ELF data contains no sections")]
- NoSections,
- #[error("the ELF data does not have a .text section from which to generate a build id")]
- NoTextSection,
- #[error(
- "failed to calculate build id\n\
- ... from program headers: {program_headers}\n\
- ... from sections: {section}\n\
- ... from the text section: {section}"
- )]
- NoBuildId {
- program_headers: Box,
- section: Box,
- generated: Box,
- },
- #[error("no dynamic string table section")]
- NoDynStrSection,
- #[error("a string in the strtab did not have a terminating nul byte")]
- StrTabNoNulByte,
- #[error("no SONAME found in dynamic linking information")]
- NoSoNameEntry,
- #[error("no dynamic linking information section")]
- NoDynamicSection,
- #[error(
- "failed to retrieve soname\n\
- ... from program headers: {program_headers}\n\
- ... from sections: {section}"
- )]
- NoSoName {
- program_headers: Box,
- section: Box,
- },
-}
diff --git a/src/linux/maps_reader.rs b/src/linux/maps_reader.rs
index 3113d35f..7f6e81e2 100644
--- a/src/linux/maps_reader.rs
+++ b/src/linux/maps_reader.rs
@@ -1,5 +1,6 @@
use {
- crate::{auxv::AuxvType, errors::MapsReaderError},
+ super::{auxv::AuxvType, module_reader::ModuleReaderError, serializers::*},
+ crate::serializers::*,
byteorder::{NativeEndian, ReadBytesExt},
goblin::elf,
memmap2::{Mmap, MmapOptions},
@@ -55,10 +56,46 @@ pub struct MappingEntry {
// A list of
pub type MappingList = Vec;
-#[derive(Debug)]
-pub enum MappingInfoParsingResult {
- SkipLine,
- Success(MappingInfo),
+#[derive(thiserror::Error, Debug, serde::Serialize)]
+pub enum MapsReaderError {
+ #[error("Couldn't parse as ELF file")]
+ ELFParsingFailed(
+ #[from]
+ #[serde(serialize_with = "serialize_goblin_error")]
+ goblin::error::Error,
+ ),
+ #[error("No soname found (filename: {})", .0.to_string_lossy())]
+ NoSoName(OsString, #[source] ModuleReaderError),
+
+ // parse_from_line()
+ #[error("Map entry malformed: No {0} found")]
+ MapEntryMalformed(&'static str),
+ #[error("Couldn't parse address")]
+ UnparsableInteger(
+ #[from]
+ #[serde(skip)]
+ std::num::ParseIntError,
+ ),
+ #[error("Linux gate location doesn't fit in the required integer type")]
+ LinuxGateNotConvertable(
+ #[from]
+ #[serde(skip)]
+ std::num::TryFromIntError,
+ ),
+
+ // get_mmap()
+ #[error("Not safe to open mapping {}", .0.to_string_lossy())]
+ NotSafeToOpenMapping(OsString),
+ #[error("IO Error")]
+ FileError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Mmapped file empty or not an ELF file")]
+ MmapSanityCheckFailed,
+ #[error("Symlink does not match ({0} vs. {1})")]
+ SymlinkError(std::path::PathBuf, std::path::PathBuf),
}
fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
@@ -236,7 +273,7 @@ impl MappingInfo {
.as_ref()
.read_u64::()
.map(|u| u as usize),
- x => panic!("Unexpected type width: {}", x),
+ x => panic!("Unexpected type width: {x}"),
};
if let Ok(addr) = addr {
if low_addr <= addr && addr <= high_addr {
diff --git a/src/linux/mem_reader.rs b/src/linux/mem_reader.rs
index f8e4a040..e8d7d651 100644
--- a/src/linux/mem_reader.rs
+++ b/src/linux/mem_reader.rs
@@ -1,6 +1,6 @@
//! Functionality for reading a remote process's memory
-use crate::{errors::CopyFromProcessError, ptrace_dumper::PtraceDumper, Pid};
+use super::{minidump_writer::MinidumpWriter, serializers::*, Pid};
enum Style {
/// Uses [`process_vm_readv`](https://linux.die.net/man/2/process_vm_readv)
@@ -28,6 +28,17 @@ enum Style {
},
}
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+#[error("Copy from process {child} failed (source {src}, offset: {offset}, length: {length})")]
+pub struct CopyFromProcessError {
+ pub child: Pid,
+ pub src: usize,
+ pub offset: usize,
+ pub length: usize,
+ #[serde(serialize_with = "serialize_nix_error")]
+ pub source: nix::Error,
+}
+
pub struct MemReader {
/// The pid of the child to read
pid: nix::unistd::Pid,
@@ -215,7 +226,7 @@ impl MemReader {
}
}
-impl PtraceDumper {
+impl MinidumpWriter {
/// Copies a block of bytes from the target process, returning the heap
/// allocated copy
#[inline]
@@ -223,21 +234,19 @@ impl PtraceDumper {
pid: Pid,
src: usize,
length: usize,
- ) -> Result, crate::errors::DumperError> {
- let length = std::num::NonZeroUsize::new(length).ok_or(
- crate::errors::DumperError::CopyFromProcessError(CopyFromProcessError {
- src,
- child: pid,
- offset: 0,
- length,
- // TODO: We should make copy_from_process also take a NonZero,
- // as EINVAL could also come from the syscalls that actually read
- // memory as well which could be confusing
- source: nix::errno::Errno::EINVAL,
- }),
- )?;
+ ) -> Result, CopyFromProcessError> {
+ let length = std::num::NonZeroUsize::new(length).ok_or(CopyFromProcessError {
+ src,
+ child: pid,
+ offset: 0,
+ length,
+ // TODO: We should make copy_from_process also take a NonZero,
+ // as EINVAL could also come from the syscalls that actually read
+ // memory as well which could be confusing
+ source: nix::errno::Errno::EINVAL,
+ })?;
let mut mem = MemReader::new(pid);
- Ok(mem.read_to_vec(src, length)?)
+ mem.read_to_vec(src, length)
}
}
diff --git a/src/linux/minidump_writer.rs b/src/linux/minidump_writer.rs
deleted file mode 100644
index 0c6e385a..00000000
--- a/src/linux/minidump_writer.rs
+++ /dev/null
@@ -1,467 +0,0 @@
-pub use crate::linux::auxv::{AuxvType, DirectAuxvDumpInfo};
-use {
- crate::{
- auxv::AuxvDumpInfo,
- dir_section::{DirSection, DumpBuf},
- linux::{
- app_memory::AppMemoryList,
- crash_context::CrashContext,
- dso_debug,
- errors::WriterError,
- maps_reader::{MappingInfo, MappingList},
- ptrace_dumper::PtraceDumper,
- sections::*,
- },
- mem_writer::{Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError},
- minidump_format::*,
- Pid,
- },
- error_graph::{ErrorList, WriteErrorList},
- std::{
- io::{Seek, Write},
- time::Duration,
- },
-};
-
-pub enum CrashingThreadContext {
- None,
- CrashContext(MDLocationDescriptor),
- CrashContextPlusAddress((MDLocationDescriptor, usize)),
-}
-
-/// The default timeout after a `SIGSTOP` after which minidump writing proceeds
-/// regardless of the process state
-pub const STOP_TIMEOUT: Duration = Duration::from_millis(100);
-
-pub struct MinidumpWriter {
- pub process_id: Pid,
- pub blamed_thread: Pid,
- pub minidump_size_limit: Option,
- pub skip_stacks_if_mapping_unreferenced: bool,
- pub principal_mapping_address: Option,
- pub user_mapping_list: MappingList,
- pub app_memory: AppMemoryList,
- pub memory_blocks: Vec,
- pub principal_mapping: Option,
- pub sanitize_stack: bool,
- pub crash_context: Option,
- pub crashing_thread_context: CrashingThreadContext,
- pub stop_timeout: Duration,
- pub direct_auxv_dump_info: Option,
-}
-
-// This doesn't work yet:
-// https://github.com/rust-lang/rust/issues/43408
-// fn write>(path: P, value: T) -> Result<()> {
-// let mut file = std::fs::File::open(path)?;
-// let bytes: [u8; size_of::()] = unsafe { transmute(value) };
-// file.write_all(&bytes)?;
-// Ok(())
-// }
-
-type Result = std::result::Result;
-
-impl MinidumpWriter {
- pub fn new(process: Pid, blamed_thread: Pid) -> Self {
- Self {
- process_id: process,
- blamed_thread,
- minidump_size_limit: None,
- skip_stacks_if_mapping_unreferenced: false,
- principal_mapping_address: None,
- user_mapping_list: MappingList::new(),
- app_memory: AppMemoryList::new(),
- memory_blocks: Vec::new(),
- principal_mapping: None,
- sanitize_stack: false,
- crash_context: None,
- crashing_thread_context: CrashingThreadContext::None,
- stop_timeout: STOP_TIMEOUT,
- direct_auxv_dump_info: None,
- }
- }
-
- pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
- self.minidump_size_limit = Some(limit);
- self
- }
-
- pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
- self.user_mapping_list = user_mapping_list;
- self
- }
-
- pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
- self.principal_mapping_address = Some(principal_mapping_address);
- self
- }
-
- pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
- self.app_memory = app_memory;
- self
- }
-
- pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
- self.crash_context = Some(crash_context);
- self
- }
-
- pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
- self.skip_stacks_if_mapping_unreferenced = true; // Off by default
- self
- }
-
- pub fn sanitize_stack(&mut self) -> &mut Self {
- self.sanitize_stack = true; // Off by default
- self
- }
-
- /// Sets the timeout after `SIGSTOP` is sent to the process, if the process
- /// has not stopped by the time the timeout has reached, we proceed with
- /// minidump generation
- pub fn stop_timeout(&mut self, duration: Duration) -> &mut Self {
- self.stop_timeout = duration;
- self
- }
-
- /// Directly set important Auxv info determined by the crashing process
- ///
- /// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this
- /// information directly using the Linux `getauxval()` call (if possible).
- ///
- /// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques
- /// to obtain it (like reading `/proc/{pid}/auxv`).
- pub fn set_direct_auxv_dump_info(
- &mut self,
- direct_auxv_dump_info: DirectAuxvDumpInfo,
- ) -> &mut Self {
- self.direct_auxv_dump_info = Some(direct_auxv_dump_info);
- self
- }
-
- /// Generates a minidump and writes to the destination provided. Returns the in-memory
- /// version of the minidump as well.
- pub fn dump(&mut self, destination: &mut (impl Write + Seek)) -> Result> {
- let auxv = self
- .direct_auxv_dump_info
- .clone()
- .map(AuxvDumpInfo::from)
- .unwrap_or_default();
-
- let mut soft_errors = ErrorList::default();
-
- let mut dumper = PtraceDumper::new_report_soft_errors(
- self.process_id,
- self.stop_timeout,
- auxv,
- soft_errors.subwriter(WriterError::InitErrors),
- )?;
-
- let threads_count = dumper.threads.len();
-
- dumper.suspend_threads(soft_errors.subwriter(WriterError::SuspendThreadsErrors));
-
- if dumper.threads.is_empty() {
- soft_errors.push(WriterError::SuspendNoThreadsLeft(threads_count));
- }
-
- dumper.late_init()?;
-
- if self.skip_stacks_if_mapping_unreferenced {
- if let Some(address) = self.principal_mapping_address {
- self.principal_mapping = dumper.find_mapping_no_bias(address).cloned();
- }
-
- if !self.crash_thread_references_principal_mapping(&dumper) {
- soft_errors.push(WriterError::PrincipalMappingNotReferenced);
- }
- }
-
- let mut buffer = Buffer::with_capacity(0);
- self.generate_dump(&mut buffer, &mut dumper, soft_errors, destination)?;
-
- Ok(buffer.into())
- }
-
- fn crash_thread_references_principal_mapping(&self, dumper: &PtraceDumper) -> bool {
- if self.crash_context.is_none() || self.principal_mapping.is_none() {
- return false;
- }
-
- let low_addr = self
- .principal_mapping
- .as_ref()
- .unwrap()
- .system_mapping_info
- .start_address;
- let high_addr = self
- .principal_mapping
- .as_ref()
- .unwrap()
- .system_mapping_info
- .end_address;
-
- let pc = self
- .crash_context
- .as_ref()
- .unwrap()
- .get_instruction_pointer();
- let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
-
- if pc >= low_addr && pc < high_addr {
- return true;
- }
-
- let (valid_stack_pointer, stack_len) = match dumper.get_stack_info(stack_pointer) {
- Ok(x) => x,
- Err(_) => {
- return false;
- }
- };
-
- let stack_copy = match PtraceDumper::copy_from_process(
- self.blamed_thread,
- valid_stack_pointer,
- stack_len,
- ) {
- Ok(x) => x,
- Err(_) => {
- return false;
- }
- };
-
- let sp_offset = stack_pointer.saturating_sub(valid_stack_pointer);
- self.principal_mapping
- .as_ref()
- .unwrap()
- .stack_has_pointer_to_mapping(&stack_copy, sp_offset)
- }
-
- fn generate_dump(
- &mut self,
- buffer: &mut DumpBuf,
- dumper: &mut PtraceDumper,
- mut soft_errors: ErrorList,
- destination: &mut (impl Write + Seek),
- ) -> Result<()> {
- // A minidump file contains a number of tagged streams. This is the number
- // of streams which we write.
- let num_writers = 18u32;
-
- let mut header_section = MemoryWriter::::alloc(buffer)?;
-
- let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
-
- let header = MDRawHeader {
- signature: MD_HEADER_SIGNATURE,
- version: MD_HEADER_VERSION,
- stream_count: num_writers,
- // header.get()->stream_directory_rva = dir.position();
- stream_directory_rva: dir_section.position(),
- checksum: 0, /* Can be 0. In fact, that's all that's
- * been found in minidump files. */
- time_date_stamp: std::time::SystemTime::now()
- .duration_since(std::time::UNIX_EPOCH)?
- .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as
- flags: 0,
- };
- header_section.set_value(buffer, header)?;
-
- // Ensure the header gets flushed. If we crash somewhere below,
- // we should have a mostly-intact dump
- dir_section.write_to_file(buffer, None)?;
-
- let dirent = thread_list_stream::write(self, buffer, dumper)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = mappings::write(self, buffer, dumper)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- app_memory::write(self, buffer)?;
- dir_section.write_to_file(buffer, None)?;
-
- let dirent = memory_list_stream::write(self, buffer)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = exception_stream::write(self, buffer)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = systeminfo_stream::write(
- buffer,
- soft_errors.subwriter(WriterError::WriteSystemInfoErrors),
- )?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = memory_info_list_stream::write(self, buffer)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, "/proc/cpuinfo") {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxCpuInfo as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteCpuInfoFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/status", self.blamed_thread))
- {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxProcStatus as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteThreadProcStatusFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self
- .write_file(buffer, "/etc/lsb-release")
- .or_else(|_| self.write_file(buffer, "/etc/os-release"))
- {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxLsbRelease as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteOsReleaseInfoFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread))
- {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxCmdLine as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteCommandLineFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread))
- {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxEnviron as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteEnvironmentFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxAuxv as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteAuxvFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::LinuxMaps as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteMapsFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match dso_debug::write_dso_debug_stream(buffer, self.process_id, &dumper.auxv)
- {
- Ok(dirent) => dirent,
- Err(e) => {
- soft_errors.push(WriterError::WriteDSODebugStreamFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match self.write_file(buffer, &format!("/proc/{}/limits", self.blamed_thread))
- {
- Ok(location) => MDRawDirectory {
- stream_type: MDStreamType::MozLinuxLimits as u32,
- location,
- },
- Err(e) => {
- soft_errors.push(WriterError::WriteLimitsFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = thread_names_stream::write(buffer, dumper)?;
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- let dirent = match handle_data_stream::write(self, buffer) {
- Ok(dirent) => dirent,
- Err(e) => {
- soft_errors.push(WriterError::WriteHandleDataStreamFailed(e));
- Default::default()
- }
- };
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- // ========================================================================================
- //
- // PAST THIS BANNER, THE THREADS ARE RUNNING IN THE TARGET PROCESS AGAIN. IF YOU NEED TO
- // ADD NEW ENTRIES THAT ACCESS THE TARGET MEMORY, DO IT BEFORE HERE!
- //
- // ========================================================================================
-
- // Collect any last-minute soft errors when trying to restart threads
- dumper.resume_threads(soft_errors.subwriter(WriterError::ResumeThreadsErrors));
-
- // If this fails, there's really nothing we can do about that (other than ignore it).
- let dirent = write_soft_errors(buffer, soft_errors)
- .map(|location| MDRawDirectory {
- stream_type: MDStreamType::MozSoftErrors as u32,
- location,
- })
- .unwrap_or_default();
- dir_section.write_to_file(buffer, Some(dirent))?;
-
- // If you add more directory entries, don't forget to update num_writers, above.
- Ok(())
- }
-
- #[allow(clippy::unused_self)]
- fn write_file(
- &self,
- buffer: &mut DumpBuf,
- filename: &str,
- ) -> std::result::Result {
- let content = std::fs::read(filename)?;
-
- let section = MemoryArrayWriter::write_bytes(buffer, &content);
- Ok(section.location())
- }
-}
-
-fn write_soft_errors(
- buffer: &mut DumpBuf,
- soft_errors: ErrorList,
-) -> Result {
- let soft_errors_json_str =
- serde_json::to_string_pretty(&soft_errors).map_err(WriterError::ConvertToJsonFailed)?;
- let section = MemoryArrayWriter::write_bytes(buffer, soft_errors_json_str.as_bytes());
- Ok(section.location())
-}
diff --git a/src/linux/minidump_writer/app_memory.rs b/src/linux/minidump_writer/app_memory.rs
new file mode 100644
index 00000000..2d65b646
--- /dev/null
+++ b/src/linux/minidump_writer/app_memory.rs
@@ -0,0 +1,28 @@
+use super::*;
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionAppMemoryError {
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] CopyFromProcessError),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+impl MinidumpWriter {
+ /// Write application-provided memory regions.
+ pub fn write_app_memory(&mut self, buffer: &mut DumpBuf) -> Result<(), SectionAppMemoryError> {
+ let blamed_thread = self.blamed_thread;
+ for app_memory in &self.app_memory {
+ let data_copy =
+ Self::copy_from_process(blamed_thread, app_memory.ptr, app_memory.length)?;
+
+ let section = MemoryArrayWriter::write_bytes(buffer, &data_copy);
+ let desc = MDMemoryDescriptor {
+ start_of_memory_range: app_memory.ptr as u64,
+ memory: section.location(),
+ };
+ self.memory_blocks.push(desc);
+ }
+ Ok(())
+ }
+}
diff --git a/src/linux/minidump_writer/errors.rs b/src/linux/minidump_writer/errors.rs
new file mode 100644
index 00000000..d73a9ce3
--- /dev/null
+++ b/src/linux/minidump_writer/errors.rs
@@ -0,0 +1,243 @@
+use {
+ super::super::{
+ auxv::AuxvError,
+ dso_debug::SectionDsoDebugError,
+ maps_reader::MapsReaderError,
+ minidump_writer::{
+ app_memory::SectionAppMemoryError, exception_stream::SectionExceptionStreamError,
+ handle_data_stream::SectionHandleDataStreamError, mappings::SectionMappingsError,
+ memory_info_list_stream::SectionMemInfoListError,
+ memory_list_stream::SectionMemListError, systeminfo_stream::SectionSystemInfoError,
+ thread_list_stream::SectionThreadListError,
+ thread_names_stream::SectionThreadNamesError,
+ },
+ module_reader::ModuleReaderError,
+ serializers::*,
+ Pid,
+ },
+ crate::{dir_section::FileWriterError, mem_writer::MemoryWriterError, serializers::*},
+ error_graph::ErrorList,
+ nix::errno::Errno,
+ procfs_core::ProcError,
+ std::ffi::OsString,
+ thiserror::Error,
+};
+
+#[cfg(target_os = "android")]
+use super::super::android::AndroidError;
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum WriterError {
+ #[error("Error during init phase")]
+ InitError(#[from] InitError),
+ #[error("Failed when writing section AppMemory")]
+ SectionAppMemoryError(#[from] SectionAppMemoryError),
+ #[error("Failed when writing section ExceptionStream")]
+ SectionExceptionStreamError(#[from] SectionExceptionStreamError),
+ #[error("Failed when writing section HandleDataStream")]
+ SectionHandleDataStreamError(#[from] SectionHandleDataStreamError),
+ #[error("Failed when writing section MappingsError")]
+ SectionMappingsError(#[from] SectionMappingsError),
+ #[error("Failed when writing section MemList")]
+ SectionMemListError(#[from] SectionMemListError),
+ #[error("Failed when writing section SystemInfo")]
+ SectionSystemInfoError(#[from] SectionSystemInfoError),
+ #[error("Failed when writing section MemoryInfoList")]
+ SectionMemoryInfoListError(#[from] SectionMemInfoListError),
+ #[error("Failed when writing section ThreadList")]
+ SectionThreadListError(#[from] SectionThreadListError),
+ #[error("Failed when writing section ThreadNameList")]
+ SectionThreadNamesError(#[from] SectionThreadNamesError),
+ #[error("Failed when writing section DsoDebug")]
+ SectionDsoDebugError(#[from] SectionDsoDebugError),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to write to file")]
+ FileWriterError(#[from] FileWriterError),
+ #[error("Failed to get current timestamp when writing header of minidump")]
+ SystemTimeError(
+ #[from]
+ #[serde(serialize_with = "serialize_system_time_error")]
+ std::time::SystemTimeError,
+ ),
+ #[error("Errors occurred while initializing PTraceDumper")]
+ InitErrors(#[source] ErrorList),
+ #[error("Errors occurred while resuming threads")]
+ ResumeThreadsErrors(#[source] ErrorList),
+ #[error("Errors occurred while writing system info")]
+ WriteSystemInfoErrors(#[source] ErrorList),
+ #[error("Failed writing cpuinfo")]
+ WriteCpuInfoFailed(#[source] MemoryWriterError),
+ #[error("Failed writing thread proc status")]
+ WriteThreadProcStatusFailed(#[source] MemoryWriterError),
+ #[error("Failed writing OS Release Information")]
+ WriteOsReleaseInfoFailed(#[source] MemoryWriterError),
+ #[error("Failed writing process command line")]
+ WriteCommandLineFailed(#[source] MemoryWriterError),
+ #[error("Writing process environment failed")]
+ WriteEnvironmentFailed(#[source] MemoryWriterError),
+ #[error("Failed to write auxv file")]
+ WriteAuxvFailed(#[source] MemoryWriterError),
+ #[error("Failed to write maps file")]
+ WriteMapsFailed(#[source] MemoryWriterError),
+ #[error("Failed writing DSO Debug Stream")]
+ WriteDSODebugStreamFailed(#[source] SectionDsoDebugError),
+ #[error("Failed writing limits file")]
+ WriteLimitsFailed(#[source] MemoryWriterError),
+ #[error("Failed writing handle data stream")]
+ WriteHandleDataStreamFailed(#[source] SectionHandleDataStreamError),
+ #[error("Failed writing handle data stream direction entry")]
+ WriteHandleDataStreamDirentFailed(#[source] FileWriterError),
+ #[error("Failed to convert soft error list to JSON")]
+ ConvertToJsonFailed(
+ #[source]
+ #[serde(skip)]
+ serde_json::Error,
+ ),
+ #[error("nix::ptrace::attach(Pid={0}) failed")]
+ PtraceAttachError(
+ Pid,
+ #[source]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("nix::ptrace::detach(Pid={0}) failed")]
+ PtraceDetachError(
+ Pid,
+ #[source]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("wait::waitpid(Pid={0}) failed")]
+ WaitPidError(
+ Pid,
+ #[source]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("Skipped thread {0} due to it being part of the seccomp sandbox's trusted code")]
+ DetachSkippedThread(Pid),
+ #[error("Maps reader error")]
+ MapsReaderError(#[from] MapsReaderError),
+ #[error("Failed to get PAGE_SIZE from system")]
+ SysConfError(
+ #[from]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+
+ #[error("No mapping for stack pointer found")]
+ NoStackPointerMapping,
+ #[error("Failed slice conversion")]
+ TryFromSliceError(
+ #[from]
+ #[serde(skip)]
+ std::array::TryFromSliceError,
+ ),
+ #[error("Couldn't parse as ELF file")]
+ ELFParsingFailed(
+ #[from]
+ #[serde(serialize_with = "serialize_goblin_error")]
+ goblin::error::Error,
+ ),
+ #[error("Could not read value from module")]
+ ModuleReaderError(#[from] ModuleReaderError),
+ #[error("Not safe to open mapping: {}", .0.to_string_lossy())]
+ NotSafeToOpenMapping(OsString),
+ #[error("Failed integer conversion")]
+ TryFromIntError(
+ #[from]
+ #[serde(skip)]
+ std::num::TryFromIntError,
+ ),
+}
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum InitError {
+ #[error("failed to read auxv")]
+ ReadAuxvFailed(#[source] super::super::auxv::AuxvError),
+ #[error("IO error for file {0}")]
+ IOError(
+ String,
+ #[source]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[cfg(target_os = "android")]
+ #[error("Failed Android specific late init")]
+ AndroidLateInitError(#[from] AndroidError),
+ #[error("Failed to read the page size")]
+ PageSizeError(
+ #[from]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("Ptrace does not function within the same process")]
+ CannotPtraceSameProcess,
+ #[error("Failed to stop the target process")]
+ StopProcessFailed(#[source] StopProcessError),
+ #[error("Errors occurred while filling missing Auxv info")]
+ FillMissingAuxvInfoErrors(#[source] ErrorList),
+ #[error("Failed filling missing Auxv info")]
+ FillMissingAuxvInfoFailed(#[source] AuxvError),
+ #[error("Failed reading proc/pid/task entry for process")]
+ ReadProcessThreadEntryFailed(
+ #[source]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Process task entry `{0:?}` could not be parsed as a TID")]
+ ProcessTaskEntryNotTid(OsString),
+ #[error("Failed to read thread name")]
+ ReadThreadNameFailed(
+ #[source]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Proc task directory `{0:?}` is not a directory")]
+ ProcPidTaskNotDirectory(String),
+ #[error("Errors while enumerating threads")]
+ EnumerateThreadsErrors(#[source] ErrorList),
+ #[error("Failed to enumerate threads")]
+ EnumerateThreadsFailed(#[source] Box),
+ #[error("Failed to read process map file")]
+ ReadProcessMapFileFailed(
+ #[source]
+ #[serde(serialize_with = "serialize_proc_error")]
+ ProcError,
+ ),
+ #[error("Failed to aggregate process mappings")]
+ AggregateMappingsFailed(#[source] MapsReaderError),
+ #[error("Failed to enumerate process mappings")]
+ EnumerateMappingsFailed(#[source] Box),
+ #[error("Errors occurred while suspending threads")]
+ SuspendThreadsErrors(#[source] ErrorList),
+ #[error("No threads left to suspend out of {0}")]
+ SuspendNoThreadsLeft(usize),
+ #[error("Crash thread does not reference principal mapping")]
+ PrincipalMappingNotReferenced,
+}
+
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum StopProcessError {
+ #[error("Failed to stop the process")]
+ Stop(
+ #[from]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("Failed to get the process state")]
+ State(
+ #[from]
+ #[serde(serialize_with = "serialize_proc_error")]
+ ProcError,
+ ),
+ #[error("Timeout waiting for process to stop")]
+ Timeout,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ContinueProcessError {
+ #[error("Failed to continue the process")]
+ Continue(#[from] Errno),
+}
diff --git a/src/linux/minidump_writer/exception_stream.rs b/src/linux/minidump_writer/exception_stream.rs
new file mode 100644
index 00000000..7bc7e2db
--- /dev/null
+++ b/src/linux/minidump_writer/exception_stream.rs
@@ -0,0 +1,56 @@
+use {super::*, minidump_common::errors::ExceptionCodeLinux};
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionExceptionStreamError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+impl MinidumpWriter {
+ pub fn write_exception_stream(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let exception = if let Some(context) = &self.crash_context {
+ MDException {
+ exception_code: context.inner.siginfo.ssi_signo,
+ exception_flags: context.inner.siginfo.ssi_code as u32,
+ exception_address: context.inner.siginfo.ssi_addr,
+ ..Default::default()
+ }
+ } else {
+ let addr = match &self.crashing_thread_context {
+ CrashingThreadContext::CrashContextPlusAddress((_, addr)) => *addr,
+ _ => 0,
+ };
+ MDException {
+ exception_code: ExceptionCodeLinux::DUMP_REQUESTED as u32,
+ exception_address: addr as u64,
+ ..Default::default()
+ }
+ };
+
+ let thread_context = match self.crashing_thread_context {
+ CrashingThreadContext::CrashContextPlusAddress((ctx, _))
+ | CrashingThreadContext::CrashContext(ctx) => ctx,
+ CrashingThreadContext::None => MDLocationDescriptor {
+ data_size: 0,
+ rva: 0,
+ },
+ };
+
+ let stream = MDRawExceptionStream {
+ thread_id: self.blamed_thread as u32,
+ exception_record: exception,
+ __align: 0,
+ thread_context,
+ };
+ let exc = MemoryWriter::alloc_with_val(buffer, stream)?;
+ let dirent = MDRawDirectory {
+ stream_type: MDStreamType::ExceptionStream as u32,
+ location: exc.location(),
+ };
+
+ Ok(dirent)
+ }
+}
diff --git a/src/linux/minidump_writer/handle_data_stream.rs b/src/linux/minidump_writer/handle_data_stream.rs
new file mode 100644
index 00000000..ea901a3d
--- /dev/null
+++ b/src/linux/minidump_writer/handle_data_stream.rs
@@ -0,0 +1,104 @@
+use {
+ super::*,
+ crate::mem_writer::MemoryWriter,
+ std::{
+ ffi::{CString, OsString},
+ fs::{self, DirEntry},
+ mem::{self},
+ os::unix::prelude::OsStrExt,
+ path::{Path, PathBuf},
+ },
+};
+
+fn file_stat(path: &Path) -> Option {
+ let c_path = CString::new(path.as_os_str().as_bytes()).ok()?;
+ let mut stat = unsafe { std::mem::zeroed::() };
+ let result = unsafe { libc::stat(c_path.as_ptr(), &mut stat) };
+
+ if result == 0 {
+ Some(stat)
+ } else {
+ None
+ }
+}
+
+fn direntry_to_descriptor(buffer: &mut DumpBuf, entry: &DirEntry) -> Option {
+ let handle = filename_to_fd(&entry.file_name())?;
+ let realpath = fs::read_link(entry.path()).ok()?;
+ let path_rva = write_string_to_location(buffer, realpath.to_string_lossy().as_ref()).ok()?;
+ let stat = file_stat(&entry.path())?;
+
+ // TODO: We store the contents of `st_mode` into the `attributes` field, but
+ // we could also store a human-readable string of the file type inside
+ // `type_name_rva`. We might move this missing information (and
+ // more) inside a custom `MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE` blob.
+ // That would make this conversion loss-less.
+ Some(MDRawHandleDescriptor {
+ handle,
+ type_name_rva: 0,
+ object_name_rva: path_rva.rva,
+ attributes: stat.st_mode,
+ granted_access: 0,
+ handle_count: 0,
+ pointer_count: 0,
+ })
+}
+
+fn filename_to_fd(filename: &OsString) -> Option {
+ let filename = filename.to_string_lossy();
+ filename.parse::().ok()
+}
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionHandleDataStreamError {
+ #[error("Failed to access file")]
+ IOError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed integer conversion")]
+ TryFromIntError(
+ #[from]
+ #[serde(skip)]
+ std::num::TryFromIntError,
+ ),
+}
+
+impl MinidumpWriter {
+ pub fn write_handle_data_stream(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let proc_fd_path = PathBuf::from(format!("/proc/{}/fd", self.process_id));
+ let proc_fd_iter = fs::read_dir(proc_fd_path)?;
+ let descriptors: Vec<_> = proc_fd_iter
+ .filter_map(|entry| entry.ok())
+ .filter_map(|entry| direntry_to_descriptor(buffer, &entry))
+ .collect();
+ let number_of_descriptors = descriptors.len() as u32;
+
+ let stream_header = MemoryWriter::::alloc_with_val(
+ buffer,
+ MDRawHandleDataStream {
+ size_of_header: mem::size_of::() as u32,
+ size_of_descriptor: mem::size_of::() as u32,
+ number_of_descriptors,
+ reserved: 0,
+ },
+ )?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::HandleDataStream as u32,
+ location: stream_header.location(),
+ };
+
+ let descriptor_list =
+ MemoryArrayWriter::::alloc_from_iter(buffer, descriptors)?;
+
+ dirent.location.data_size += descriptor_list.location().data_size;
+ Ok(dirent)
+ }
+}
diff --git a/src/linux/minidump_writer/mappings.rs b/src/linux/minidump_writer/mappings.rs
new file mode 100644
index 00000000..b12e2511
--- /dev/null
+++ b/src/linux/minidump_writer/mappings.rs
@@ -0,0 +1,154 @@
+use super::{
+ super::{
+ maps_reader::MappingInfo,
+ module_reader::{BuildId, ReadFromModule, SoName},
+ },
+ *,
+};
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionMappingsError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to get effective path of mapping ({0:?})")]
+ GetEffectivePathError(MappingInfo, #[source] MapsReaderError),
+}
+
+impl MinidumpWriter {
+ /// Write information about the mappings in effect. Because we are using the
+ /// minidump format, the information about the mappings is pretty limited.
+ /// Because of this, we also include the full, unparsed, /proc/$x/maps file in
+ /// another stream in the file.
+ pub fn write_mappings(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let mut modules = Vec::new();
+
+ // First write all the mappings from the dumper
+ for map_idx in 0..self.mappings.len() {
+ // If the mapping is uninteresting, or if
+ // there is caller-provided information about this mapping
+ // in the user_mapping_list list, skip it
+
+ if !self.mappings[map_idx].is_interesting()
+ || self.mappings[map_idx].is_contained_in(&self.user_mapping_list)
+ {
+ continue;
+ }
+ log::debug!("retrieving build id for {:?}", &self.mappings[map_idx]);
+ let BuildId(identifier) = self
+ .from_process_memory_for_index(map_idx)
+ .or_else(|e| {
+ // If the mapping has an associated name that is a file, try to read the build id
+ // from the file. If there is no note segment with the build id in
+ // the program headers, we can't get to the note section if the section header
+ // table isn't loaded.
+ if let Some(path) = &self.mappings[map_idx].name {
+ let path = std::path::Path::new(&path);
+ if path.exists() {
+ log::debug!("failed to get build id from process memory ({e}), attempting to retrieve from {}", path.display());
+ return BuildId::read_from_file(path)
+ .map_err(errors::WriterError::ModuleReaderError);
+ }
+ log::debug!(
+ "not attempting to get build id from {}: path does not exist",
+ path.display()
+ );
+ }
+ Err(e)
+ })
+ .unwrap_or_else(|e| {
+ log::warn!("failed to get build id for mapping: {e}");
+ BuildId(Vec::new())
+ });
+
+ // If the identifier is all 0, its an uninteresting mapping (bmc#1676109)
+ if identifier.is_empty() || identifier.iter().all(|&x| x == 0) {
+ continue;
+ }
+
+ // SONAME should always be accessible through program headers alone, so we don't really
+ // need to fall back to trying to read from the mapping file.
+ let soname = self
+ .from_process_memory_for_index(map_idx)
+ .ok()
+ .map(|SoName(n)| n);
+
+ let module = fill_raw_module(buffer, &self.mappings[map_idx], &identifier, soname)?;
+ modules.push(module);
+ }
+
+ // Next write all the mappings provided by the caller
+ for user in &self.user_mapping_list {
+ // GUID was provided by caller.
+ let module = fill_raw_module(buffer, &user.mapping, &user.identifier, None)?;
+ modules.push(module);
+ }
+
+ let list_header = MemoryWriter::::alloc_with_val(buffer, modules.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ModuleListStream as u32,
+ location: list_header.location(),
+ };
+
+ if !modules.is_empty() {
+ let mapping_list = MemoryArrayWriter::::alloc_from_iter(buffer, modules)?;
+ dirent.location.data_size += mapping_list.location().data_size;
+ }
+
+ Ok(dirent)
+ }
+}
+fn fill_raw_module(
+ buffer: &mut DumpBuf,
+ mapping: &MappingInfo,
+ identifier: &[u8],
+ soname: Option,
+) -> Result {
+ let cv_record = if identifier.is_empty() {
+ // Just zeroes
+ Default::default()
+ } else {
+ let cv_signature = crate::minidump_format::format::CvSignature::Elf as u32;
+ let array_size = std::mem::size_of_val(&cv_signature) + identifier.len();
+
+ let mut sig_section = MemoryArrayWriter::::alloc_array(buffer, array_size)?;
+ for (index, val) in cv_signature
+ .to_ne_bytes()
+ .iter()
+ .chain(identifier.iter())
+ .enumerate()
+ {
+ sig_section.set_value_at(buffer, *val, index)?;
+ }
+ sig_section.location()
+ };
+
+ let (file_path, _, so_version) = mapping
+ .get_mapping_effective_path_name_and_version(soname)
+ .map_err(|e| SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?;
+ let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?;
+
+ let version_info = so_version.map_or(Default::default(), |sov| format::VS_FIXEDFILEINFO {
+ signature: format::VS_FFI_SIGNATURE,
+ struct_version: format::VS_FFI_STRUCVERSION,
+ file_version_hi: sov.major,
+ file_version_lo: sov.minor,
+ product_version_hi: sov.patch,
+ product_version_lo: sov.prerelease,
+ ..Default::default()
+ });
+
+ let raw_module = MDRawModule {
+ base_of_image: mapping.start_address as u64,
+ size_of_image: mapping.size as u32,
+ cv_record,
+ module_name_rva: name_header.rva,
+ version_info,
+ ..Default::default()
+ };
+
+ Ok(raw_module)
+}
diff --git a/src/linux/minidump_writer/memory_info_list_stream.rs b/src/linux/minidump_writer/memory_info_list_stream.rs
new file mode 100644
index 00000000..89581c37
--- /dev/null
+++ b/src/linux/minidump_writer/memory_info_list_stream.rs
@@ -0,0 +1,83 @@
+use {
+ super::*,
+ minidump_common::format::{MemoryProtection, MemoryState, MemoryType},
+ procfs_core::{process::MMPermissions, FromRead},
+};
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionMemInfoListError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to read from procfs")]
+ ProcfsError(
+ #[from]
+ #[serde(serialize_with = "serialize_proc_error")]
+ procfs_core::ProcError,
+ ),
+}
+
+impl MinidumpWriter {
+ /// Write a MemoryInfoListStream using information from procfs.
+ pub fn write_memory_info_list_stream(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let maps = procfs_core::process::MemoryMaps::from_file(std::path::PathBuf::from(format!(
+ "/proc/{}/maps",
+ self.blamed_thread
+ )))?;
+
+ let list_header = MemoryWriter::alloc_with_val(
+ buffer,
+ MDMemoryInfoList {
+ size_of_header: std::mem::size_of::() as u32,
+ size_of_entry: std::mem::size_of::() as u32,
+ number_of_entries: maps.len() as u64,
+ },
+ )?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::MemoryInfoListStream as u32,
+ location: list_header.location(),
+ };
+
+ let block_list = MemoryArrayWriter::::alloc_from_iter(
+ buffer,
+ maps.iter().map(|mm| MDMemoryInfo {
+ base_address: mm.address.0,
+ allocation_base: mm.address.0,
+ allocation_protection: get_memory_protection(mm.perms).bits(),
+ __alignment1: 0,
+ region_size: mm.address.1 - mm.address.0,
+ state: MemoryState::MEM_COMMIT.bits(),
+ protection: get_memory_protection(mm.perms).bits(),
+ _type: if mm.perms.contains(MMPermissions::PRIVATE) {
+ MemoryType::MEM_PRIVATE
+ } else {
+ MemoryType::MEM_MAPPED
+ }
+ .bits(),
+ __alignment2: 0,
+ }),
+ )?;
+
+ dirent.location.data_size += block_list.location().data_size;
+
+ Ok(dirent)
+ }
+}
+fn get_memory_protection(permissions: MMPermissions) -> MemoryProtection {
+ let read = permissions.contains(MMPermissions::READ);
+ let write = permissions.contains(MMPermissions::WRITE);
+ let exec = permissions.contains(MMPermissions::EXECUTE);
+ match (read, write, exec) {
+ (false, false, false) => MemoryProtection::PAGE_NOACCESS,
+ (false, false, true) => MemoryProtection::PAGE_EXECUTE,
+ (true, false, false) => MemoryProtection::PAGE_READONLY,
+ (true, false, true) => MemoryProtection::PAGE_EXECUTE_READ,
+ // No support for write-only
+ (true | false, true, false) => MemoryProtection::PAGE_READWRITE,
+ // No support for execute+write-only
+ (true | false, true, true) => MemoryProtection::PAGE_EXECUTE_READWRITE,
+ }
+}
diff --git a/src/linux/minidump_writer/memory_list_stream.rs b/src/linux/minidump_writer/memory_list_stream.rs
new file mode 100644
index 00000000..eea8ce8e
--- /dev/null
+++ b/src/linux/minidump_writer/memory_list_stream.rs
@@ -0,0 +1,29 @@
+use super::*;
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionMemListError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+}
+
+impl MinidumpWriter {
+ pub fn write_memory_list_stream(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let list_header =
+ MemoryWriter::::alloc_with_val(buffer, self.memory_blocks.len() as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::MemoryListStream as u32,
+ location: list_header.location(),
+ };
+
+ let block_list =
+ MemoryArrayWriter::::alloc_from_array(buffer, &self.memory_blocks)?;
+
+ dirent.location.data_size += block_list.location().data_size;
+
+ Ok(dirent)
+ }
+}
diff --git a/src/linux/ptrace_dumper.rs b/src/linux/minidump_writer/mod.rs
similarity index 50%
rename from src/linux/ptrace_dumper.rs
rename to src/linux/minidump_writer/mod.rs
index 7b04a2f5..8666e689 100644
--- a/src/linux/ptrace_dumper.rs
+++ b/src/linux/minidump_writer/mod.rs
@@ -1,21 +1,27 @@
use {
super::{
- auxv::AuxvError,
- errors::{AndroidError, MapsReaderError},
+ app_memory::AppMemoryList,
+ auxv::AuxvDumpInfo,
+ crash_context::CrashContext,
+ dso_debug,
+ dumper_cpu_info::CpuInfoError,
+ maps_reader::{MappingInfo, MappingList, MapsReaderError},
+ mem_reader::CopyFromProcessError,
+ module_reader,
serializers::*,
+ thread_info::{ThreadInfo, ThreadInfoError},
+ Pid,
},
crate::{
- linux::{
- auxv::AuxvDumpInfo,
- errors::{DumperError, ThreadInfoError},
- maps_reader::MappingInfo,
- module_reader,
- thread_info::ThreadInfo,
- Pid,
+ dir_section::{DirSection, DumpBuf},
+ mem_writer::{
+ write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError,
},
+ minidump_format::*,
serializers::*,
},
error_graph::{ErrorList, WriteErrorList},
+ errors::{ContinueProcessError, InitError, StopProcessError, WriterError},
failspot::failspot,
nix::{
errno::Errno,
@@ -23,190 +29,240 @@ use {
},
procfs_core::{
process::{MMPermissions, ProcState, Stat},
- FromRead, ProcError,
+ FromRead,
},
std::{
- ffi::OsString,
+ io::{Seek, Write},
path,
- result::Result,
time::{Duration, Instant},
},
thiserror::Error,
};
#[cfg(target_os = "android")]
-use crate::linux::android::late_process_mappings;
+use super::android::late_process_mappings;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-use crate::thread_info;
+use super::thread_info;
-#[derive(Debug, Clone)]
-pub struct Thread {
- pub tid: Pid,
- pub name: Option,
-}
+pub use super::auxv::{AuxvType, DirectAuxvDumpInfo};
-#[derive(Debug)]
-pub struct PtraceDumper {
- pub pid: Pid,
- threads_suspended: bool,
- pub threads: Vec,
- pub auxv: AuxvDumpInfo,
- pub mappings: Vec,
- pub page_size: usize,
-}
+pub mod app_memory;
+pub mod errors;
+pub mod exception_stream;
+pub mod handle_data_stream;
+pub mod mappings;
+pub mod memory_info_list_stream;
+pub mod memory_list_stream;
+pub mod systeminfo_stream;
+pub mod thread_list_stream;
+pub mod thread_names_stream;
+
+/// The default timeout after a `SIGSTOP` after which minidump writing proceeds
+/// regardless of the process state
+pub const STOP_TIMEOUT: Duration = Duration::from_millis(100);
#[cfg(target_pointer_width = "32")]
pub const AT_SYSINFO_EHDR: u32 = 33;
#[cfg(target_pointer_width = "64")]
pub const AT_SYSINFO_EHDR: u64 = 33;
-impl Drop for PtraceDumper {
- fn drop(&mut self) {
- // Always try to resume all threads (e.g. in case of error)
- self.resume_threads(error_graph::strategy::DontCare);
- // Always allow the process to continue.
- let _ = self.continue_process();
- }
+#[derive(Debug)]
+pub struct MinidumpWriterConfig {
+ process_id: Pid,
+ blamed_thread: Pid,
+ minidump_size_limit: Option,
+ skip_stacks_if_mapping_unreferenced: bool,
+ principal_mapping_address: Option,
+ user_mapping_list: MappingList,
+ app_memory: AppMemoryList,
+ memory_blocks: Vec,
+ principal_mapping: Option,
+ sanitize_stack: bool,
+ crash_context: Option,
+ crashing_thread_context: CrashingThreadContext,
+ stop_timeout: Duration,
+ direct_auxv_dump_info: Option,
}
-#[derive(Debug, Error, serde::Serialize)]
-pub enum InitError {
- #[error("failed to read auxv")]
- ReadAuxvFailed(#[source] crate::auxv::AuxvError),
- #[error("IO error for file {0}")]
- IOError(
- String,
- #[source]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Failed Android specific late init")]
- AndroidLateInitError(#[from] AndroidError),
- #[error("Failed to read the page size")]
- PageSizeError(
- #[from]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("Ptrace does not function within the same process")]
- CannotPtraceSameProcess,
- #[error("Failed to stop the target process")]
- StopProcessFailed(#[source] StopProcessError),
- #[error("Errors occurred while filling missing Auxv info")]
- FillMissingAuxvInfoErrors(#[source] ErrorList),
- #[error("Failed filling missing Auxv info")]
- FillMissingAuxvInfoFailed(#[source] AuxvError),
- #[error("Failed reading proc/pid/task entry for process")]
- ReadProcessThreadEntryFailed(
- #[source]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Process task entry `{0:?}` could not be parsed as a TID")]
- ProcessTaskEntryNotTid(OsString),
- #[error("Failed to read thread name")]
- ReadThreadNameFailed(
- #[source]
- #[serde(serialize_with = "serialize_io_error")]
- std::io::Error,
- ),
- #[error("Proc task directory `{0:?}` is not a directory")]
- ProcPidTaskNotDirectory(String),
- #[error("Errors while enumerating threads")]
- EnumerateThreadsErrors(#[source] ErrorList),
- #[error("Failed to enumerate threads")]
- EnumerateThreadsFailed(#[source] Box),
- #[error("Failed to read process map file")]
- ReadProcessMapFileFailed(
- #[source]
- #[serde(serialize_with = "serialize_proc_error")]
- ProcError,
- ),
- #[error("Failed to aggregate process mappings")]
- AggregateMappingsFailed(#[source] MapsReaderError),
- #[error("Failed to enumerate process mappings")]
- EnumerateMappingsFailed(#[source] Box),
+#[derive(Debug)]
+pub struct MinidumpWriter {
+ pub process_id: Pid,
+ threads_suspended: bool,
+ pub threads: Vec,
+ pub auxv: AuxvDumpInfo,
+ pub mappings: Vec,
+ pub page_size: usize,
+ pub sanitize_stack: bool,
+ pub minidump_size_limit: Option,
+ pub user_mapping_list: MappingList,
+ pub crashing_thread_context: CrashingThreadContext,
+ stop_timeout: Duration,
+ pub skip_stacks_if_mapping_unreferenced: bool,
+ principal_mapping_address: Option,
+ pub principal_mapping: Option,
+ pub blamed_thread: Pid,
+ pub crash_context: Option,
+ pub app_memory: AppMemoryList,
+ pub memory_blocks: Vec,
}
-#[derive(Debug, thiserror::Error, serde::Serialize)]
-pub enum StopProcessError {
- #[error("Failed to stop the process")]
- Stop(
- #[from]
- #[serde(serialize_with = "serialize_nix_error")]
- nix::Error,
- ),
- #[error("Failed to get the process state")]
- State(
- #[from]
- #[serde(serialize_with = "serialize_proc_error")]
- ProcError,
- ),
- #[error("Timeout waiting for process to stop")]
- Timeout,
+#[derive(Debug, Clone)]
+pub struct Thread {
+ pub tid: Pid,
+ pub name: Option,
}
-#[derive(Debug, thiserror::Error)]
-pub enum ContinueProcessError {
- #[error("Failed to continue the process")]
- Continue(#[from] Errno),
+#[derive(Debug, Default)]
+pub enum CrashingThreadContext {
+ #[default]
+ None,
+ CrashContext(MDLocationDescriptor),
+ CrashContextPlusAddress((MDLocationDescriptor, usize)),
}
-/// PTRACE_DETACH the given pid.
-///
-/// This handles special errno cases (ESRCH) which we won't consider errors.
-fn ptrace_detach(child: Pid) -> Result<(), DumperError> {
- let pid = nix::unistd::Pid::from_raw(child);
- ptrace::detach(pid, None).or_else(|e| {
- // errno is set to ESRCH if the pid no longer exists, but we don't want to error in that
- // case.
- if e == nix::Error::ESRCH {
- Ok(())
- } else {
- Err(DumperError::PtraceDetachError(child, e))
+impl MinidumpWriterConfig {
+ pub fn new(process_id: Pid, blamed_thread: Pid) -> Self {
+ Self {
+ process_id,
+ blamed_thread,
+ minidump_size_limit: Default::default(),
+ skip_stacks_if_mapping_unreferenced: Default::default(),
+ principal_mapping_address: Default::default(),
+ user_mapping_list: Default::default(),
+ app_memory: Default::default(),
+ memory_blocks: Default::default(),
+ principal_mapping: Default::default(),
+ sanitize_stack: Default::default(),
+ crash_context: Default::default(),
+ crashing_thread_context: Default::default(),
+ stop_timeout: STOP_TIMEOUT,
+ direct_auxv_dump_info: Default::default(),
}
- })
-}
+ }
-impl PtraceDumper {
- /// Constructs a dumper for extracting information from the specified process id
- pub fn new_report_soft_errors(
- pid: Pid,
- stop_timeout: Duration,
- auxv: AuxvDumpInfo,
- soft_errors: impl WriteErrorList,
- ) -> Result {
- if pid == std::process::id() as i32 {
- return Err(InitError::CannotPtraceSameProcess);
- }
+ pub fn set_minidump_size_limit(&mut self, limit: u64) -> &mut Self {
+ self.minidump_size_limit = Some(limit);
+ self
+ }
+
+ pub fn set_user_mapping_list(&mut self, user_mapping_list: MappingList) -> &mut Self {
+ self.user_mapping_list = user_mapping_list;
+ self
+ }
+
+ pub fn set_principal_mapping_address(&mut self, principal_mapping_address: usize) -> &mut Self {
+ self.principal_mapping_address = Some(principal_mapping_address);
+ self
+ }
+
+ pub fn set_app_memory(&mut self, app_memory: AppMemoryList) -> &mut Self {
+ self.app_memory = app_memory;
+ self
+ }
+
+ pub fn set_crash_context(&mut self, crash_context: CrashContext) -> &mut Self {
+ self.crash_context = Some(crash_context);
+ self
+ }
+
+ pub fn skip_stacks_if_mapping_unreferenced(&mut self) -> &mut Self {
+ self.skip_stacks_if_mapping_unreferenced = true; // Off by default
+ self
+ }
+
+ pub fn sanitize_stack(&mut self) -> &mut Self {
+ self.sanitize_stack = true; // Off by default
+ self
+ }
+
+ /// Sets the timeout after `SIGSTOP` is sent to the process, if the process
+ /// has not stopped by the time the timeout has reached, we proceed with
+ /// minidump generation
+ pub fn stop_timeout(&mut self, duration: Duration) -> &mut Self {
+ self.stop_timeout = duration;
+ self
+ }
+
+ /// Directly set important Auxv info determined by the crashing process
+ ///
+ /// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this
+ /// information directly using the Linux `getauxval()` call (if possible).
+ ///
+ /// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques
+ /// to obtain it (like reading `/proc/{pid}/auxv`).
+ pub fn set_direct_auxv_dump_info(
+ &mut self,
+ direct_auxv_dump_info: DirectAuxvDumpInfo,
+ ) -> &mut Self {
+ self.direct_auxv_dump_info = Some(direct_auxv_dump_info);
+ self
+ }
+ /// Generates a minidump and writes to the destination provided. Returns the in-memory
+ /// version of the minidump as well.
+ pub fn write(self, destination: &mut (impl Write + Seek)) -> Result, WriterError> {
+ let mut soft_errors = ErrorList::default();
+
+ let mut writer = self.build();
+ writer.init(soft_errors.subwriter(WriterError::InitErrors))?;
- let mut dumper = Self {
- pid,
- threads_suspended: false,
- threads: Vec::new(),
+ let mut buffer = Buffer::with_capacity(0);
+ writer.write_dump(&mut buffer, destination, soft_errors)?;
+ Ok(buffer.into())
+ }
+ /// Allows testing code to inspect the pre-output state of the MinidumpWriter
+ pub fn build_for_testing(
+ self,
+ soft_errors: impl WriteErrorList,
+ ) -> Result {
+ let mut writer = self.build();
+ writer.init(soft_errors)?;
+ Ok(writer)
+ }
+ fn build(self) -> MinidumpWriter {
+ let auxv = self
+ .direct_auxv_dump_info
+ .map(AuxvDumpInfo::from)
+ .unwrap_or_default();
+
+ MinidumpWriter {
+ process_id: self.process_id,
+ threads_suspended: Default::default(),
+ threads: Default::default(),
auxv,
- mappings: Vec::new(),
- page_size: 0,
- };
- dumper.init(stop_timeout, soft_errors)?;
- Ok(dumper)
+ mappings: Default::default(),
+ page_size: Default::default(),
+ sanitize_stack: self.sanitize_stack,
+ minidump_size_limit: self.minidump_size_limit,
+ user_mapping_list: self.user_mapping_list,
+ crashing_thread_context: self.crashing_thread_context,
+ stop_timeout: self.stop_timeout,
+ skip_stacks_if_mapping_unreferenced: self.skip_stacks_if_mapping_unreferenced,
+ principal_mapping_address: self.principal_mapping_address,
+ principal_mapping: self.principal_mapping,
+ blamed_thread: self.blamed_thread,
+ crash_context: self.crash_context,
+ app_memory: self.app_memory,
+ memory_blocks: self.memory_blocks,
+ }
}
+}
+impl MinidumpWriter {
// TODO: late_init for chromeos and android
- pub fn init(
- &mut self,
- stop_timeout: Duration,
- mut soft_errors: impl WriteErrorList,
- ) -> Result<(), InitError> {
+ fn init(&mut self, mut soft_errors: impl WriteErrorList) -> Result<(), InitError> {
+ if self.process_id == std::process::id() as i32 {
+ return Err(InitError::CannotPtraceSameProcess);
+ }
+
// Stopping the process is best-effort.
- if let Err(e) = self.stop_process(stop_timeout) {
+ if let Err(e) = self.stop_process(self.stop_timeout) {
soft_errors.push(InitError::StopProcessFailed(e));
}
// Even if we completely fail to fill in any additional Auxv info, we can still press
// forward.
if let Err(e) = self.auxv.try_filling_missing_info(
- self.pid,
+ self.process_id,
soft_errors.subwriter(InitError::FillMissingAuxvInfoErrors),
) {
soft_errors.push(InitError::FillMissingAuxvInfoFailed(e));
@@ -229,21 +285,279 @@ impl PtraceDumper {
.expect("page size apparently unlimited: doesn't make sense.")
as usize;
- Ok(())
- }
+ let threads_count = self.threads.len();
+
+ self.suspend_threads(soft_errors.subwriter(InitError::SuspendThreadsErrors));
+
+ if self.threads.is_empty() {
+ soft_errors.push(InitError::SuspendNoThreadsLeft(threads_count));
+ }
- #[cfg_attr(not(target_os = "android"), allow(clippy::unused_self))]
- pub fn late_init(&mut self) -> Result<(), InitError> {
#[cfg(target_os = "android")]
{
- late_process_mappings(self.pid, &mut self.mappings)?;
+ late_process_mappings(self.process_id, &mut self.mappings)?;
}
+
+ if self.skip_stacks_if_mapping_unreferenced {
+ if let Some(address) = self.principal_mapping_address {
+ self.principal_mapping = self.find_mapping_no_bias(address).cloned();
+ }
+
+ if !self.crash_thread_references_principal_mapping() {
+ soft_errors.push(InitError::PrincipalMappingNotReferenced);
+ }
+ }
+
+ Ok(())
+ }
+ /// Generates a minidump and writes to the destination provided. Returns the in-memory
+ /// version of the minidump as well.
+ fn write_dump(
+ &mut self,
+ buffer: &mut DumpBuf,
+ destination: &mut (impl Write + Seek),
+ mut soft_errors: ErrorList,
+ ) -> Result<(), WriterError> {
+ // A minidump file contains a number of tagged streams. This is the number
+ // of streams which we write.
+ let num_writers = 18u32;
+
+ let mut header_section = MemoryWriter::::alloc(buffer)?;
+
+ let mut dir_section = DirSection::new(buffer, num_writers, destination)?;
+
+ let header = MDRawHeader {
+ signature: MD_HEADER_SIGNATURE,
+ version: MD_HEADER_VERSION,
+ stream_count: num_writers,
+ // header.get()->stream_directory_rva = dir.position();
+ stream_directory_rva: dir_section.position(),
+ checksum: 0, /* Can be 0. In fact, that's all that's
+ * been found in minidump files. */
+ time_date_stamp: std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)?
+ .as_secs() as u32, // TODO: This is not Y2038 safe, but thats how its currently defined as
+ flags: 0,
+ };
+ header_section.set_value(buffer, header)?;
+
+ // Ensure the header gets flushed. If we crash somewhere below,
+ // we should have a mostly-intact dump
+ dir_section.write_to_file(buffer, None)?;
+
+ let dirent = self.write_thread_list_stream(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = self.write_mappings(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ self.write_app_memory(buffer)?;
+ dir_section.write_to_file(buffer, None)?;
+
+ let dirent = self.write_memory_list_stream(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = self.write_exception_stream(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = systeminfo_stream::write(
+ buffer,
+ soft_errors.subwriter(WriterError::WriteSystemInfoErrors),
+ )?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = self.write_memory_info_list_stream(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, "/proc/cpuinfo") {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCpuInfo as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteCpuInfoFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/status", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxProcStatus as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteThreadProcStatusFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, "/etc/lsb-release")
+ .or_else(|_| write_file(buffer, "/etc/os-release"))
+ {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxLsbRelease as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteOsReleaseInfoFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/cmdline", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxCmdLine as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteCommandLineFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/environ", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxEnviron as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteEnvironmentFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/auxv", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxAuxv as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteAuxvFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/maps", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::LinuxMaps as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteMapsFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match dso_debug::write_dso_debug_stream(buffer, self.process_id, &self.auxv) {
+ Ok(dirent) => dirent,
+ Err(e) => {
+ soft_errors.push(WriterError::WriteDSODebugStreamFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match write_file(buffer, &format!("/proc/{}/limits", self.blamed_thread)) {
+ Ok(location) => MDRawDirectory {
+ stream_type: MDStreamType::MozLinuxLimits as u32,
+ location,
+ },
+ Err(e) => {
+ soft_errors.push(WriterError::WriteLimitsFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = self.write_thread_names_stream(buffer)?;
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ let dirent = match self.write_handle_data_stream(buffer) {
+ Ok(dirent) => dirent,
+ Err(e) => {
+ soft_errors.push(WriterError::WriteHandleDataStreamFailed(e));
+ Default::default()
+ }
+ };
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ // If this fails, there's really nothing we can do about that (other than ignore it).
+ let dirent = write_soft_errors(buffer, soft_errors)
+ .map(|location| MDRawDirectory {
+ stream_type: MDStreamType::MozSoftErrors as u32,
+ location,
+ })
+ .unwrap_or_default();
+ dir_section.write_to_file(buffer, Some(dirent))?;
+
+ // If you add more directory entries, don't forget to update num_writers, above.
Ok(())
}
+ fn crash_thread_references_principal_mapping(&self) -> bool {
+ if self.crash_context.is_none() || self.principal_mapping.is_none() {
+ return false;
+ }
+
+ let low_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .start_address;
+ let high_addr = self
+ .principal_mapping
+ .as_ref()
+ .unwrap()
+ .system_mapping_info
+ .end_address;
+
+ let pc = self
+ .crash_context
+ .as_ref()
+ .unwrap()
+ .get_instruction_pointer();
+ let stack_pointer = self.crash_context.as_ref().unwrap().get_stack_pointer();
+
+ if pc >= low_addr && pc < high_addr {
+ return true;
+ }
+
+ let (valid_stack_pointer, stack_len) = match self.get_stack_info(stack_pointer) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+
+ let stack_copy = match MinidumpWriter::copy_from_process(
+ self.blamed_thread,
+ valid_stack_pointer,
+ stack_len,
+ ) {
+ Ok(x) => x,
+ Err(_) => {
+ return false;
+ }
+ };
+
+ let sp_offset = stack_pointer.saturating_sub(valid_stack_pointer);
+ self.principal_mapping
+ .as_ref()
+ .unwrap()
+ .stack_has_pointer_to_mapping(&stack_copy, sp_offset)
+ }
+
/// Suspends a thread by attaching to it.
- pub fn suspend_thread(child: Pid) -> Result<(), DumperError> {
- use DumperError::PtraceAttachError as AttachErr;
+ fn suspend_thread(child: Pid) -> Result<(), WriterError> {
+ use WriterError::PtraceAttachError as AttachErr;
let pid = nix::unistd::Pid::from_raw(child);
// This may fail if the thread has just died or debugged.
@@ -252,7 +566,7 @@ impl PtraceDumper {
match wait::waitpid(pid, Some(wait::WaitPidFlag::__WALL)) {
Ok(status) => {
let wait::WaitStatus::Stopped(_, status) = status else {
- return Err(DumperError::WaitPidError(
+ return Err(WriterError::WaitPidError(
child,
nix::errno::Errno::UnknownErrno,
));
@@ -268,13 +582,13 @@ impl PtraceDumper {
// Signals other than SIGSTOP that are received need to be reinjected,
// or they will otherwise get lost.
if let Err(err) = ptrace::cont(pid, status) {
- return Err(DumperError::WaitPidError(child, err));
+ return Err(WriterError::WaitPidError(child, err));
}
}
Err(Errno::EINTR) => continue,
Err(e) => {
ptrace_detach(child)?;
- return Err(DumperError::WaitPidError(child, e));
+ return Err(WriterError::WaitPidError(child, e));
}
}
}
@@ -303,18 +617,18 @@ impl PtraceDumper {
}
if skip_thread {
ptrace_detach(child)?;
- return Err(DumperError::DetachSkippedThread(child));
+ return Err(WriterError::DetachSkippedThread(child));
}
}
Ok(())
}
/// Resumes a thread by detaching from it.
- pub fn resume_thread(child: Pid) -> Result<(), DumperError> {
+ fn resume_thread(child: Pid) -> Result<(), WriterError> {
ptrace_detach(child)
}
- pub fn suspend_threads(&mut self, mut soft_errors: impl WriteErrorList) {
+ fn suspend_threads(&mut self, mut soft_errors: impl WriteErrorList) {
// Iterate over all threads and try to suspend them.
// If the thread either disappeared before we could attach to it, or if
// it was part of the seccomp sandbox's trusted code, it is OK to
@@ -329,10 +643,10 @@ impl PtraceDumper {
self.threads_suspended = true;
- failspot::failspot!(::SuspendThreads soft_errors.push(DumperError::PtraceAttachError(1234, nix::Error::EPERM)))
+ failspot::failspot!(::SuspendThreads soft_errors.push(WriterError::PtraceAttachError(1234, nix::Error::EPERM)))
}
- pub fn resume_threads(&mut self, mut soft_errors: impl WriteErrorList) {
+ fn resume_threads(&mut self, mut soft_errors: impl WriteErrorList) {
if self.threads_suspended {
for thread in &self.threads {
match Self::resume_thread(thread.tid) {
@@ -352,12 +666,15 @@ impl PtraceDumper {
fn stop_process(&mut self, timeout: Duration) -> Result<(), StopProcessError> {
failspot!(StopProcess bail(nix::Error::EPERM));
- signal::kill(nix::unistd::Pid::from_raw(self.pid), Some(signal::SIGSTOP))?;
+ signal::kill(
+ nix::unistd::Pid::from_raw(self.process_id),
+ Some(signal::SIGSTOP),
+ )?;
// Something like waitpid for non-child processes would be better, but we have no such
// tool, so we poll the status.
const POLL_INTERVAL: Duration = Duration::from_millis(1);
- let proc_file = format!("/proc/{}/stat", self.pid);
+ let proc_file = format!("/proc/{}/stat", self.process_id);
let end = Instant::now() + timeout;
loop {
@@ -376,7 +693,10 @@ impl PtraceDumper {
///
/// Unlike `stop_process`, this function does not wait for the process to continue.
fn continue_process(&mut self) -> Result<(), ContinueProcessError> {
- signal::kill(nix::unistd::Pid::from_raw(self.pid), Some(signal::SIGCONT))?;
+ signal::kill(
+ nix::unistd::Pid::from_raw(self.process_id),
+ Some(signal::SIGCONT),
+ )?;
Ok(())
}
@@ -386,8 +706,8 @@ impl PtraceDumper {
&mut self,
mut soft_errors: impl WriteErrorList,
) -> Result<(), InitError> {
- let pid = self.pid;
- let filename = format!("/proc/{}/task", pid);
+ let pid = self.process_id;
+ let filename = format!("/proc/{pid}/task");
let task_path = path::PathBuf::from(&filename);
if !task_path.is_dir() {
return Err(InitError::ProcPidTaskNotDirectory(filename));
@@ -416,7 +736,7 @@ impl PtraceDumper {
"testing requested failure reading thread name",
))
} else {
- std::fs::read_to_string(format!("/proc/{}/task/{}/comm", pid, tid))
+ std::fs::read_to_string(format!("/proc/{pid}/task/{tid}/comm"))
});
let name = match name_result {
@@ -441,7 +761,7 @@ impl PtraceDumper {
// case its entry when creating the list of mappings.
// See http://www.trilithium.com/johan/2005/08/linux-gate/ for more
// information.
- let maps_path = format!("/proc/{}/maps", self.pid);
+ let maps_path = format!("/proc/{}/maps", self.process_id);
let maps_file =
std::fs::File::open(&maps_path).map_err(|e| InitError::IOError(maps_path, e))?;
@@ -483,14 +803,14 @@ impl PtraceDumper {
return Err(ThreadInfoError::IndexOutOfBounds(index, self.threads.len()));
}
- ThreadInfo::create(self.pid, self.threads[index].tid)
+ ThreadInfo::create(self.process_id, self.threads[index].tid)
}
// Returns a valid stack pointer and the mapping that contains the stack.
// The stack pointer will usually point within this mapping, but it might
// not in case of stack overflows, hence the returned pointer might be
// different from the one that was passed in.
- pub fn get_stack_info(&self, int_stack_pointer: usize) -> Result<(usize, usize), DumperError> {
+ pub fn get_stack_info(&self, int_stack_pointer: usize) -> Result<(usize, usize), WriterError> {
// Round the stack pointer to the nearest page, this will cause us to
// capture data below the stack pointer which might still be relevant.
let mut stack_pointer = int_stack_pointer & !(self.page_size - 1);
@@ -522,7 +842,7 @@ impl PtraceDumper {
let stack_len = mapping.size - (valid_stack_pointer - mapping.start_address);
(valid_stack_pointer, stack_len)
})
- .ok_or(DumperError::NoStackPointerMapping)
+ .ok_or(WriterError::NoStackPointerMapping)
}
fn may_be_stack(mapping: Option<&MappingInfo>) -> bool {
@@ -540,7 +860,7 @@ impl PtraceDumper {
stack_copy: &mut [u8],
stack_pointer: usize,
sp_offset: usize,
- ) -> Result<(), DumperError> {
+ ) -> Result<(), WriterError> {
// We optimize the search for containing mappings in three ways:
// 1) We expect that pointers into the stack mapping will be common, so
// we cache that address range.
@@ -664,18 +984,63 @@ impl PtraceDumper {
pub fn from_process_memory_for_index(
&mut self,
idx: usize,
- ) -> Result {
+ ) -> Result {
assert!(idx < self.mappings.len());
- Self::from_process_memory_for_mapping(&self.mappings[idx], self.pid)
+ Self::from_process_memory_for_mapping(&self.mappings[idx], self.process_id)
}
pub fn from_process_memory_for_mapping(
mapping: &MappingInfo,
pid: Pid,
- ) -> Result {
+ ) -> Result {
Ok(T::read_from_module(
module_reader::ProcessReader::new(pid, mapping.start_address).into(),
)?)
}
}
+
+impl Drop for MinidumpWriter {
+ fn drop(&mut self) {
+ // Always try to resume all threads (e.g. in case of error)
+ self.resume_threads(error_graph::strategy::DontCare);
+ // Always allow the process to continue.
+ let _ = self.continue_process();
+ }
+}
+
+/// PTRACE_DETACH the given pid.
+///
+/// This handles special errno cases (ESRCH) which we won't consider errors.
+fn ptrace_detach(child: Pid) -> Result<(), WriterError> {
+ let pid = nix::unistd::Pid::from_raw(child);
+ ptrace::detach(pid, None).or_else(|e| {
+ // errno is set to ESRCH if the pid no longer exists, but we don't want to error in that
+ // case.
+ if e == nix::Error::ESRCH {
+ Ok(())
+ } else {
+ Err(WriterError::PtraceDetachError(child, e))
+ }
+ })
+}
+
+fn write_file(
+ buffer: &mut DumpBuf,
+ filename: &str,
+) -> std::result::Result {
+ let content = std::fs::read(filename)?;
+
+ let section = MemoryArrayWriter::write_bytes(buffer, &content);
+ Ok(section.location())
+}
+
+fn write_soft_errors(
+ buffer: &mut DumpBuf,
+ soft_errors: ErrorList,
+) -> Result {
+ let soft_errors_json_str =
+ serde_json::to_string_pretty(&soft_errors).map_err(WriterError::ConvertToJsonFailed)?;
+ let section = MemoryArrayWriter::write_bytes(buffer, soft_errors_json_str.as_bytes());
+ Ok(section.location())
+}
diff --git a/src/linux/sections/systeminfo_stream.rs b/src/linux/minidump_writer/systeminfo_stream.rs
similarity index 67%
rename from src/linux/sections/systeminfo_stream.rs
rename to src/linux/minidump_writer/systeminfo_stream.rs
index 62ca332c..4e077701 100644
--- a/src/linux/sections/systeminfo_stream.rs
+++ b/src/linux/minidump_writer/systeminfo_stream.rs
@@ -1,8 +1,18 @@
use {
- super::*, crate::linux::dumper_cpu_info as dci, error_graph::WriteErrorList,
- errors::SectionSystemInfoError,
+ super::{super::dumper_cpu_info as dci, *},
+ error_graph::WriteErrorList,
};
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionSystemInfoError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to get CPU Info")]
+ CpuInfoError(#[from] CpuInfoError),
+ #[error("Failed trying to write CPU information")]
+ WriteCpuInformationFailed(#[source] CpuInfoError),
+}
+
pub fn write(
buffer: &mut DumpBuf,
mut soft_errors: impl WriteErrorList,
diff --git a/src/linux/minidump_writer/thread_list_stream.rs b/src/linux/minidump_writer/thread_list_stream.rs
new file mode 100644
index 00000000..9fa573aa
--- /dev/null
+++ b/src/linux/minidump_writer/thread_list_stream.rs
@@ -0,0 +1,249 @@
+use {super::*, crate::minidump_cpu::RawContextCPU, std::cmp::min};
+
+// The following kLimit* constants are for when minidump_size_limit_ is set
+// and the minidump size might exceed it.
+//
+// Estimate for how big each thread's stack will be (in bytes).
+const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024;
+// Number of threads whose stack size we don't want to limit. These base
+// threads will simply be the first N threads returned by the dumper (although
+// the crashing thread will never be limited). Threads beyond this count are
+// the extra threads.
+const LIMIT_BASE_THREAD_COUNT: usize = 20;
+// Maximum stack size to dump for any extra thread (in bytes).
+const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024;
+// Make sure this number of additional bytes can fit in the minidump
+// (exclude the stack data).
+const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024;
+
+#[derive(Debug, Clone, Copy)]
+enum MaxStackLen {
+ None,
+ Len(usize),
+}
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionThreadListError {
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed integer conversion")]
+ TryFromIntError(
+ #[from]
+ #[serde(skip)]
+ std::num::TryFromIntError,
+ ),
+ #[error("Failed to copy memory from process")]
+ CopyFromProcessError(#[from] CopyFromProcessError),
+ #[error("Failed to get thread info")]
+ ThreadInfoError(#[from] ThreadInfoError),
+ #[error("Failed to write to memory buffer")]
+ IOError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Failed to sanitize stack copy")]
+ SanitizeStackCopyFailed(#[source] Box),
+}
+
+impl MinidumpWriter {
+ pub fn write_thread_list_stream(
+ &mut self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ let num_threads = self.threads.len();
+ // Memory looks like this:
+ // ...
+
+ let list_header = MemoryWriter::::alloc_with_val(buffer, num_threads as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ThreadListStream as u32,
+ location: list_header.location(),
+ };
+
+ let mut thread_list = MemoryArrayWriter::::alloc_array(buffer, num_threads)?;
+ dirent.location.data_size += thread_list.location().data_size;
+ // If there's a minidump size limit, check if it might be exceeded. Since
+ // most of the space is filled with stack data, just check against that.
+ // If this expects to exceed the limit, set extra_thread_stack_len such
+ // that any thread beyond the first kLimitBaseThreadCount threads will
+ // have only kLimitMaxExtraThreadStackLen bytes dumped.
+ let mut extra_thread_stack_len = MaxStackLen::None; // default to no maximum
+ if let Some(minidump_size_limit) = self.minidump_size_limit {
+ let estimated_total_stack_size =
+ (num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64;
+ let curr_pos = buffer.position();
+ let estimated_minidump_size =
+ curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR;
+ if estimated_minidump_size > minidump_size_limit {
+ extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN);
+ }
+ }
+
+ for (idx, item) in self.threads.clone().iter().enumerate() {
+ let mut thread = MDRawThread {
+ thread_id: item.tid.try_into()?,
+ suspend_count: 0,
+ priority_class: 0,
+ priority: 0,
+ teb: 0,
+ stack: MDMemoryDescriptor::default(),
+ thread_context: MDLocationDescriptor::default(),
+ };
+
+ // We have a different source of information for the crashing thread. If
+ // we used the actual state of the thread we would find it running in the
+ // signal handler with the alternative stack, which would be deeply
+ // unhelpful.
+ if self.crash_context.is_some() && thread.thread_id == self.blamed_thread as u32 {
+ let crash_context = self.crash_context.as_ref().unwrap();
+ let instruction_ptr = crash_context.get_instruction_pointer();
+ let stack_pointer = crash_context.get_stack_pointer();
+ self.fill_thread_stack(
+ buffer,
+ &mut thread,
+ instruction_ptr,
+ stack_pointer,
+ MaxStackLen::None,
+ )?;
+ // Copy 256 bytes around crashing instruction pointer to minidump.
+ let ip_memory_size: usize = 256;
+ // Bound it to the upper and lower bounds of the memory map
+ // it's contained within. If it's not in mapped memory,
+ // don't bother trying to write it.
+ for mapping in &self.mappings {
+ if instruction_ptr < mapping.start_address
+ || instruction_ptr >= mapping.start_address + mapping.size
+ {
+ continue;
+ }
+ // Try to get 128 bytes before and after the IP, but
+ // settle for whatever's available.
+ let mut ip_memory_d = MDMemoryDescriptor {
+ start_of_memory_range: std::cmp::max(
+ mapping.start_address,
+ instruction_ptr - ip_memory_size / 2,
+ ) as u64,
+ ..Default::default()
+ };
+
+ let end_of_range = std::cmp::min(
+ mapping.start_address + mapping.size,
+ instruction_ptr + ip_memory_size / 2,
+ ) as u64;
+ ip_memory_d.memory.data_size =
+ (end_of_range - ip_memory_d.start_of_memory_range) as u32;
+
+ let memory_copy = MinidumpWriter::copy_from_process(
+ thread.thread_id as i32,
+ ip_memory_d.start_of_memory_range as _,
+ ip_memory_d.memory.data_size as usize,
+ )?;
+
+ let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?;
+ ip_memory_d.memory = mem_section.location();
+ self.memory_blocks.push(ip_memory_d);
+ break;
+ }
+ // let cpu = MemoryWriter::alloc(buffer, &memory_copy)?;
+ let mut cpu: RawContextCPU = Default::default();
+ let crash_context = self.crash_context.as_ref().unwrap();
+ crash_context.fill_cpu_context(&mut cpu);
+ let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
+ thread.thread_context = cpu_section.location();
+
+ self.crashing_thread_context =
+ CrashingThreadContext::CrashContext(cpu_section.location());
+ } else {
+ let info = self.get_thread_info_by_index(idx)?;
+ let max_stack_len =
+ if self.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
+ extra_thread_stack_len
+ } else {
+ MaxStackLen::None // default to no maximum for this thread
+ };
+ let instruction_ptr = info.get_instruction_pointer();
+ self.fill_thread_stack(
+ buffer,
+ &mut thread,
+ instruction_ptr,
+ info.stack_pointer,
+ max_stack_len,
+ )?;
+
+ let mut cpu = RawContextCPU::default();
+ info.fill_cpu_context(&mut cpu);
+ let cpu_section = MemoryWriter::::alloc_with_val(buffer, cpu)?;
+ thread.thread_context = cpu_section.location();
+ if item.tid == self.blamed_thread {
+ // This is the crashing thread of a live process, but
+ // no context was provided, so set the crash address
+ // while the instruction pointer is already here.
+ self.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress(
+ (cpu_section.location(), instruction_ptr),
+ );
+ }
+ }
+ thread_list.set_value_at(buffer, thread, idx)?;
+ }
+ Ok(dirent)
+ }
+
+ fn fill_thread_stack(
+ &mut self,
+ buffer: &mut DumpBuf,
+ thread: &mut MDRawThread,
+ instruction_ptr: usize,
+ stack_ptr: usize,
+ max_stack_len: MaxStackLen,
+ ) -> Result<(), SectionThreadListError> {
+ thread.stack.start_of_memory_range = stack_ptr.try_into()?;
+ thread.stack.memory.data_size = 0;
+ thread.stack.memory.rva = buffer.position() as u32;
+
+ if let Ok((valid_stack_ptr, stack_len)) = self.get_stack_info(stack_ptr) {
+ let stack_len = if let MaxStackLen::Len(max_stack_len) = max_stack_len {
+ min(stack_len, max_stack_len)
+ } else {
+ stack_len
+ };
+
+ let mut stack_bytes = MinidumpWriter::copy_from_process(
+ thread.thread_id.try_into()?,
+ valid_stack_ptr,
+ stack_len,
+ )?;
+ let stack_pointer_offset = stack_ptr.saturating_sub(valid_stack_ptr);
+ if self.skip_stacks_if_mapping_unreferenced {
+ if let Some(principal_mapping) = &self.principal_mapping {
+ let low_addr = principal_mapping.system_mapping_info.start_address;
+ let high_addr = principal_mapping.system_mapping_info.end_address;
+ if (instruction_ptr < low_addr || instruction_ptr > high_addr)
+ && !principal_mapping
+ .stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset)
+ {
+ return Ok(());
+ }
+ } else {
+ return Ok(());
+ }
+ }
+
+ if self.sanitize_stack {
+ self.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)
+ .map_err(|e| SectionThreadListError::SanitizeStackCopyFailed(Box::new(e)))?;
+ }
+
+ let stack_location = MDLocationDescriptor {
+ data_size: stack_bytes.len() as u32,
+ rva: buffer.position() as u32,
+ };
+ buffer.write_all(&stack_bytes);
+ thread.stack.start_of_memory_range = valid_stack_ptr as u64;
+ thread.stack.memory = stack_location;
+ self.memory_blocks.push(thread.stack);
+ }
+ Ok(())
+ }
+}
diff --git a/src/linux/minidump_writer/thread_names_stream.rs b/src/linux/minidump_writer/thread_names_stream.rs
new file mode 100644
index 00000000..2d5bf453
--- /dev/null
+++ b/src/linux/minidump_writer/thread_names_stream.rs
@@ -0,0 +1,54 @@
+use super::*;
+
+#[derive(Debug, Error, serde::Serialize)]
+pub enum SectionThreadNamesError {
+ #[error("Failed integer conversion")]
+ TryFromIntError(
+ #[from]
+ #[serde(skip)]
+ std::num::TryFromIntError,
+ ),
+ #[error("Failed to write to memory")]
+ MemoryWriterError(#[from] MemoryWriterError),
+ #[error("Failed to write to memory buffer")]
+ IOError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+}
+
+impl MinidumpWriter {
+ pub fn write_thread_names_stream(
+ &self,
+ buffer: &mut DumpBuf,
+ ) -> Result {
+ // Only count threads that have a name
+ let num_threads = self.threads.iter().filter(|t| t.name.is_some()).count();
+ // Memory looks like this:
+ // ...
+
+ let list_header = MemoryWriter::::alloc_with_val(buffer, num_threads as u32)?;
+
+ let mut dirent = MDRawDirectory {
+ stream_type: MDStreamType::ThreadNamesStream as u32,
+ location: list_header.location(),
+ };
+
+ let mut thread_list =
+ MemoryArrayWriter::::alloc_array(buffer, num_threads)?;
+ dirent.location.data_size += thread_list.location().data_size;
+
+ for (idx, item) in self.threads.iter().enumerate() {
+ if let Some(name) = &item.name {
+ let pos = write_string_to_location(buffer, name)?;
+ let thread = MDRawThreadName {
+ thread_id: item.tid.try_into()?,
+ thread_name_rva: pos.rva.into(),
+ };
+ thread_list.set_value_at(buffer, thread, idx)?;
+ }
+ }
+ Ok(dirent)
+ }
+}
diff --git a/src/linux.rs b/src/linux/mod.rs
similarity index 87%
rename from src/linux.rs
rename to src/linux/mod.rs
index dc605084..9e7b46bd 100644
--- a/src/linux.rs
+++ b/src/linux/mod.rs
@@ -8,13 +8,10 @@ pub(crate) mod auxv;
pub mod crash_context;
mod dso_debug;
mod dumper_cpu_info;
-pub mod errors;
pub mod maps_reader;
pub mod mem_reader;
pub mod minidump_writer;
pub mod module_reader;
-pub mod ptrace_dumper;
-pub(crate) mod sections;
mod serializers;
pub mod thread_info;
diff --git a/src/linux/module_reader.rs b/src/linux/module_reader.rs
index ff4c6aec..048d0233 100644
--- a/src/linux/module_reader.rs
+++ b/src/linux/module_reader.rs
@@ -1,13 +1,15 @@
-use crate::errors::ModuleReaderError as Error;
-use crate::mem_reader::MemReader;
-use crate::minidump_format::GUID;
-use goblin::{
- container::{Container, Ctx, Endian},
- elf,
+use {
+ super::{mem_reader::MemReader, serializers::*},
+ crate::{minidump_format::GUID, serializers::*},
+ goblin::{
+ container::{Container, Ctx, Endian},
+ elf,
+ },
+ std::{borrow::Cow, ffi::CStr},
};
-use std::{borrow::Cow, ffi::CStr};
type Buf<'buf> = Cow<'buf, [u8]>;
+type Error = ModuleReaderError;
const NOTE_SECTION_NAME: &[u8] = b".note.gnu.build-id\0";
@@ -16,6 +18,72 @@ pub struct ProcessReader {
start_address: u64,
}
+#[derive(Debug, thiserror::Error, serde::Serialize)]
+pub enum ModuleReaderError {
+ #[error("failed to read module file ({path}): {error}")]
+ MapFile {
+ path: std::path::PathBuf,
+ #[source]
+ #[serde(serialize_with = "serialize_io_error")]
+ error: std::io::Error,
+ },
+ #[error("failed to read module memory: {length} bytes at {offset}{}: {error}", .start_address.map(|addr| format!(" (start address: {addr})")).unwrap_or_default())]
+ ReadModuleMemory {
+ offset: u64,
+ length: u64,
+ start_address: Option,
+ #[source]
+ #[serde(serialize_with = "serialize_nix_error")]
+ error: nix::Error,
+ },
+ #[error("failed to parse ELF memory: {0}")]
+ Parsing(
+ #[from]
+ #[serde(serialize_with = "serialize_goblin_error")]
+ goblin::error::Error,
+ ),
+ #[error("no build id notes in program headers")]
+ NoProgramHeaderNote,
+ #[error("no string table available to locate note sections")]
+ NoStrTab,
+ #[error("no build id note sections")]
+ NoSectionNote,
+ #[error("the ELF data contains no program headers")]
+ NoProgramHeaders,
+ #[error("the ELF data contains no sections")]
+ NoSections,
+ #[error("the ELF data does not have a .text section from which to generate a build id")]
+ NoTextSection,
+ #[error(
+ "failed to calculate build id\n\
+ ... from program headers: {program_headers}\n\
+ ... from sections: {section}\n\
+ ... from the text section: {section}"
+ )]
+ NoBuildId {
+ program_headers: Box,
+ section: Box,
+ generated: Box,
+ },
+ #[error("no dynamic string table section")]
+ NoDynStrSection,
+ #[error("a string in the strtab did not have a terminating nul byte")]
+ StrTabNoNulByte,
+ #[error("no SONAME found in dynamic linking information")]
+ NoSoNameEntry,
+ #[error("no dynamic linking information section")]
+ NoDynamicSection,
+ #[error(
+ "failed to retrieve soname\n\
+ ... from program headers: {program_headers}\n\
+ ... from sections: {section}"
+ )]
+ NoSoName {
+ program_headers: Box,
+ section: Box,
+ },
+}
+
impl ProcessReader {
pub fn new(pid: i32, start_address: usize) -> Self {
Self {
@@ -134,7 +202,7 @@ fn section_header_with_name<'sc>(
for header in section_headers {
let sh_name = header.sh_name as u64;
if sh_name >= strtab_section_header.sh_size {
- log::warn!("invalid sh_name offset for {:?}", name);
+ log::warn!("invalid sh_name offset for {name:?}");
continue;
}
if sh_name + name.len() as u64 >= strtab_section_header.sh_size {
diff --git a/src/linux/sections.rs b/src/linux/sections.rs
deleted file mode 100644
index 88d19f51..00000000
--- a/src/linux/sections.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-pub mod app_memory;
-pub mod exception_stream;
-pub mod handle_data_stream;
-pub mod mappings;
-pub mod memory_info_list_stream;
-pub mod memory_list_stream;
-pub mod systeminfo_stream;
-pub mod thread_list_stream;
-pub mod thread_names_stream;
-
-use crate::{
- dir_section::DumpBuf,
- errors::{self},
- linux::{
- minidump_writer::{self, MinidumpWriter},
- ptrace_dumper::PtraceDumper,
- },
- mem_writer::*,
- minidump_format::*,
-};
diff --git a/src/linux/sections/app_memory.rs b/src/linux/sections/app_memory.rs
deleted file mode 100644
index 9cf16e80..00000000
--- a/src/linux/sections/app_memory.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use super::*;
-
-/// Write application-provided memory regions.
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
-) -> Result<(), errors::SectionAppMemoryError> {
- for app_memory in &config.app_memory {
- let data_copy = PtraceDumper::copy_from_process(
- config.blamed_thread,
- app_memory.ptr,
- app_memory.length,
- )?;
-
- let section = MemoryArrayWriter::write_bytes(buffer, &data_copy);
- let desc = MDMemoryDescriptor {
- start_of_memory_range: app_memory.ptr as u64,
- memory: section.location(),
- };
- config.memory_blocks.push(desc);
- }
- Ok(())
-}
diff --git a/src/linux/sections/exception_stream.rs b/src/linux/sections/exception_stream.rs
deleted file mode 100644
index f7edda8d..00000000
--- a/src/linux/sections/exception_stream.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use super::minidump_writer::CrashingThreadContext;
-use super::*;
-use minidump_common::errors::ExceptionCodeLinux;
-
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
-) -> Result {
- let exception = if let Some(context) = &config.crash_context {
- MDException {
- exception_code: context.inner.siginfo.ssi_signo,
- exception_flags: context.inner.siginfo.ssi_code as u32,
- exception_address: context.inner.siginfo.ssi_addr,
- ..Default::default()
- }
- } else {
- let addr = match &config.crashing_thread_context {
- CrashingThreadContext::CrashContextPlusAddress((_, addr)) => *addr,
- _ => 0,
- };
- MDException {
- exception_code: ExceptionCodeLinux::DUMP_REQUESTED as u32,
- exception_address: addr as u64,
- ..Default::default()
- }
- };
-
- let thread_context = match config.crashing_thread_context {
- CrashingThreadContext::CrashContextPlusAddress((ctx, _))
- | CrashingThreadContext::CrashContext(ctx) => ctx,
- CrashingThreadContext::None => MDLocationDescriptor {
- data_size: 0,
- rva: 0,
- },
- };
-
- let stream = MDRawExceptionStream {
- thread_id: config.blamed_thread as u32,
- exception_record: exception,
- __align: 0,
- thread_context,
- };
- let exc = MemoryWriter::alloc_with_val(buffer, stream)?;
- let dirent = MDRawDirectory {
- stream_type: MDStreamType::ExceptionStream as u32,
- location: exc.location(),
- };
-
- Ok(dirent)
-}
diff --git a/src/linux/sections/handle_data_stream.rs b/src/linux/sections/handle_data_stream.rs
deleted file mode 100644
index b41c542d..00000000
--- a/src/linux/sections/handle_data_stream.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use std::{
- ffi::{CString, OsString},
- fs::{self, DirEntry},
- mem::{self},
- os::unix::prelude::OsStrExt,
- path::{Path, PathBuf},
-};
-
-use crate::mem_writer::MemoryWriter;
-
-use super::*;
-
-fn file_stat(path: &Path) -> Option {
- let c_path = CString::new(path.as_os_str().as_bytes()).ok()?;
- let mut stat = unsafe { std::mem::zeroed::() };
- let result = unsafe { libc::stat(c_path.as_ptr(), &mut stat) };
-
- if result == 0 {
- Some(stat)
- } else {
- None
- }
-}
-
-fn direntry_to_descriptor(buffer: &mut DumpBuf, entry: &DirEntry) -> Option {
- let handle = filename_to_fd(&entry.file_name())?;
- let realpath = fs::read_link(entry.path()).ok()?;
- let path_rva = write_string_to_location(buffer, realpath.to_string_lossy().as_ref()).ok()?;
- let stat = file_stat(&entry.path())?;
-
- // TODO: We store the contents of `st_mode` into the `attributes` field, but
- // we could also store a human-readable string of the file type inside
- // `type_name_rva`. We might move this missing information (and
- // more) inside a custom `MINIDUMP_HANDLE_OBJECT_INFORMATION_TYPE` blob.
- // That would make this conversion loss-less.
- Some(MDRawHandleDescriptor {
- handle,
- type_name_rva: 0,
- object_name_rva: path_rva.rva,
- attributes: stat.st_mode,
- granted_access: 0,
- handle_count: 0,
- pointer_count: 0,
- })
-}
-
-fn filename_to_fd(filename: &OsString) -> Option {
- let filename = filename.to_string_lossy();
- filename.parse::().ok()
-}
-
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
-) -> Result {
- let proc_fd_path = PathBuf::from(format!("/proc/{}/fd", config.process_id));
- let proc_fd_iter = fs::read_dir(proc_fd_path)?;
- let descriptors: Vec<_> = proc_fd_iter
- .filter_map(|entry| entry.ok())
- .filter_map(|entry| direntry_to_descriptor(buffer, &entry))
- .collect();
- let number_of_descriptors = descriptors.len() as u32;
-
- let stream_header = MemoryWriter::::alloc_with_val(
- buffer,
- MDRawHandleDataStream {
- size_of_header: mem::size_of::() as u32,
- size_of_descriptor: mem::size_of::() as u32,
- number_of_descriptors,
- reserved: 0,
- },
- )?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::HandleDataStream as u32,
- location: stream_header.location(),
- };
-
- let descriptor_list =
- MemoryArrayWriter::::alloc_from_iter(buffer, descriptors)?;
-
- dirent.location.data_size += descriptor_list.location().data_size;
- Ok(dirent)
-}
diff --git a/src/linux/sections/mappings.rs b/src/linux/sections/mappings.rs
deleted file mode 100644
index 6688387c..00000000
--- a/src/linux/sections/mappings.rs
+++ /dev/null
@@ -1,142 +0,0 @@
-use super::*;
-use crate::linux::maps_reader::MappingInfo;
-use crate::linux::module_reader::{BuildId, ReadFromModule, SoName};
-
-/// Write information about the mappings in effect. Because we are using the
-/// minidump format, the information about the mappings is pretty limited.
-/// Because of this, we also include the full, unparsed, /proc/$x/maps file in
-/// another stream in the file.
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
- dumper: &mut PtraceDumper,
-) -> Result {
- let mut modules = Vec::new();
-
- // First write all the mappings from the dumper
- for map_idx in 0..dumper.mappings.len() {
- // If the mapping is uninteresting, or if
- // there is caller-provided information about this mapping
- // in the user_mapping_list list, skip it
-
- if !dumper.mappings[map_idx].is_interesting()
- || dumper.mappings[map_idx].is_contained_in(&config.user_mapping_list)
- {
- continue;
- }
- log::debug!("retrieving build id for {:?}", &dumper.mappings[map_idx]);
- let BuildId(identifier) = dumper
- .from_process_memory_for_index(map_idx)
- .or_else(|e| {
- // If the mapping has an associated name that is a file, try to read the build id
- // from the file. If there is no note segment with the build id in
- // the program headers, we can't get to the note section if the section header
- // table isn't loaded.
- if let Some(path) = &dumper.mappings[map_idx].name {
- let path = std::path::Path::new(&path);
- if path.exists() {
- log::debug!("failed to get build id from process memory ({e}), attempting to retrieve from {}", path.display());
- return BuildId::read_from_file(path)
- .map_err(errors::DumperError::ModuleReaderError);
- }
- log::debug!(
- "not attempting to get build id from {}: path does not exist",
- path.display()
- );
- }
- Err(e)
- })
- .unwrap_or_else(|e| {
- log::warn!("failed to get build id for mapping: {e}");
- BuildId(Vec::new())
- });
-
- // If the identifier is all 0, its an uninteresting mapping (bmc#1676109)
- if identifier.is_empty() || identifier.iter().all(|&x| x == 0) {
- continue;
- }
-
- // SONAME should always be accessible through program headers alone, so we don't really
- // need to fall back to trying to read from the mapping file.
- let soname = dumper
- .from_process_memory_for_index(map_idx)
- .ok()
- .map(|SoName(n)| n);
-
- let module = fill_raw_module(buffer, &dumper.mappings[map_idx], &identifier, soname)?;
- modules.push(module);
- }
-
- // Next write all the mappings provided by the caller
- for user in &config.user_mapping_list {
- // GUID was provided by caller.
- let module = fill_raw_module(buffer, &user.mapping, &user.identifier, None)?;
- modules.push(module);
- }
-
- let list_header = MemoryWriter::::alloc_with_val(buffer, modules.len() as u32)?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::ModuleListStream as u32,
- location: list_header.location(),
- };
-
- if !modules.is_empty() {
- let mapping_list = MemoryArrayWriter::::alloc_from_iter(buffer, modules)?;
- dirent.location.data_size += mapping_list.location().data_size;
- }
-
- Ok(dirent)
-}
-
-fn fill_raw_module(
- buffer: &mut DumpBuf,
- mapping: &MappingInfo,
- identifier: &[u8],
- soname: Option,
-) -> Result {
- let cv_record = if identifier.is_empty() {
- // Just zeroes
- Default::default()
- } else {
- let cv_signature = crate::minidump_format::format::CvSignature::Elf as u32;
- let array_size = std::mem::size_of_val(&cv_signature) + identifier.len();
-
- let mut sig_section = MemoryArrayWriter::::alloc_array(buffer, array_size)?;
- for (index, val) in cv_signature
- .to_ne_bytes()
- .iter()
- .chain(identifier.iter())
- .enumerate()
- {
- sig_section.set_value_at(buffer, *val, index)?;
- }
- sig_section.location()
- };
-
- let (file_path, _, so_version) = mapping
- .get_mapping_effective_path_name_and_version(soname)
- .map_err(|e| errors::SectionMappingsError::GetEffectivePathError(mapping.clone(), e))?;
- let name_header = write_string_to_location(buffer, file_path.to_string_lossy().as_ref())?;
-
- let version_info = so_version.map_or(Default::default(), |sov| format::VS_FIXEDFILEINFO {
- signature: format::VS_FFI_SIGNATURE,
- struct_version: format::VS_FFI_STRUCVERSION,
- file_version_hi: sov.major,
- file_version_lo: sov.minor,
- product_version_hi: sov.patch,
- product_version_lo: sov.prerelease,
- ..Default::default()
- });
-
- let raw_module = MDRawModule {
- base_of_image: mapping.start_address as u64,
- size_of_image: mapping.size as u32,
- cv_record,
- module_name_rva: name_header.rva,
- version_info,
- ..Default::default()
- };
-
- Ok(raw_module)
-}
diff --git a/src/linux/sections/memory_info_list_stream.rs b/src/linux/sections/memory_info_list_stream.rs
deleted file mode 100644
index c3cd728c..00000000
--- a/src/linux/sections/memory_info_list_stream.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-use super::*;
-use minidump_common::format::{MemoryProtection, MemoryState, MemoryType};
-use procfs_core::{process::MMPermissions, FromRead};
-
-/// Write a MemoryInfoListStream using information from procfs.
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
-) -> Result {
- let maps = procfs_core::process::MemoryMaps::from_file(std::path::PathBuf::from(format!(
- "/proc/{}/maps",
- config.blamed_thread
- )))?;
-
- let list_header = MemoryWriter::alloc_with_val(
- buffer,
- MDMemoryInfoList {
- size_of_header: std::mem::size_of::() as u32,
- size_of_entry: std::mem::size_of::() as u32,
- number_of_entries: maps.len() as u64,
- },
- )?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::MemoryInfoListStream as u32,
- location: list_header.location(),
- };
-
- let block_list = MemoryArrayWriter::::alloc_from_iter(
- buffer,
- maps.iter().map(|mm| MDMemoryInfo {
- base_address: mm.address.0,
- allocation_base: mm.address.0,
- allocation_protection: get_memory_protection(mm.perms).bits(),
- __alignment1: 0,
- region_size: mm.address.1 - mm.address.0,
- state: MemoryState::MEM_COMMIT.bits(),
- protection: get_memory_protection(mm.perms).bits(),
- _type: if mm.perms.contains(MMPermissions::PRIVATE) {
- MemoryType::MEM_PRIVATE
- } else {
- MemoryType::MEM_MAPPED
- }
- .bits(),
- __alignment2: 0,
- }),
- )?;
-
- dirent.location.data_size += block_list.location().data_size;
-
- Ok(dirent)
-}
-
-fn get_memory_protection(permissions: MMPermissions) -> MemoryProtection {
- let read = permissions.contains(MMPermissions::READ);
- let write = permissions.contains(MMPermissions::WRITE);
- let exec = permissions.contains(MMPermissions::EXECUTE);
- match (read, write, exec) {
- (false, false, false) => MemoryProtection::PAGE_NOACCESS,
- (false, false, true) => MemoryProtection::PAGE_EXECUTE,
- (true, false, false) => MemoryProtection::PAGE_READONLY,
- (true, false, true) => MemoryProtection::PAGE_EXECUTE_READ,
- // No support for write-only
- (true | false, true, false) => MemoryProtection::PAGE_READWRITE,
- // No support for execute+write-only
- (true | false, true, true) => MemoryProtection::PAGE_EXECUTE_READWRITE,
- }
-}
diff --git a/src/linux/sections/memory_list_stream.rs b/src/linux/sections/memory_list_stream.rs
deleted file mode 100644
index 7f497792..00000000
--- a/src/linux/sections/memory_list_stream.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-use super::*;
-
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
-) -> Result {
- let list_header =
- MemoryWriter::::alloc_with_val(buffer, config.memory_blocks.len() as u32)?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::MemoryListStream as u32,
- location: list_header.location(),
- };
-
- let block_list =
- MemoryArrayWriter::::alloc_from_array(buffer, &config.memory_blocks)?;
-
- dirent.location.data_size += block_list.location().data_size;
-
- Ok(dirent)
-}
diff --git a/src/linux/sections/thread_list_stream.rs b/src/linux/sections/thread_list_stream.rs
deleted file mode 100644
index a6dc059e..00000000
--- a/src/linux/sections/thread_list_stream.rs
+++ /dev/null
@@ -1,232 +0,0 @@
-use std::cmp::min;
-
-use super::*;
-use crate::{minidump_cpu::RawContextCPU, minidump_writer::CrashingThreadContext};
-
-// The following kLimit* constants are for when minidump_size_limit_ is set
-// and the minidump size might exceed it.
-//
-// Estimate for how big each thread's stack will be (in bytes).
-const LIMIT_AVERAGE_THREAD_STACK_LENGTH: usize = 8 * 1024;
-// Number of threads whose stack size we don't want to limit. These base
-// threads will simply be the first N threads returned by the dumper (although
-// the crashing thread will never be limited). Threads beyond this count are
-// the extra threads.
-const LIMIT_BASE_THREAD_COUNT: usize = 20;
-// Maximum stack size to dump for any extra thread (in bytes).
-const LIMIT_MAX_EXTRA_THREAD_STACK_LEN: usize = 2 * 1024;
-// Make sure this number of additional bytes can fit in the minidump
-// (exclude the stack data).
-const LIMIT_MINIDUMP_FUDGE_FACTOR: u64 = 64 * 1024;
-
-#[derive(Debug, Clone, Copy)]
-enum MaxStackLen {
- None,
- Len(usize),
-}
-
-pub fn write(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
- dumper: &PtraceDumper,
-) -> Result {
- let num_threads = dumper.threads.len();
- // Memory looks like this:
- // ...
-
- let list_header = MemoryWriter::::alloc_with_val(buffer, num_threads as u32)?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::ThreadListStream as u32,
- location: list_header.location(),
- };
-
- let mut thread_list = MemoryArrayWriter::::alloc_array(buffer, num_threads)?;
- dirent.location.data_size += thread_list.location().data_size;
- // If there's a minidump size limit, check if it might be exceeded. Since
- // most of the space is filled with stack data, just check against that.
- // If this expects to exceed the limit, set extra_thread_stack_len such
- // that any thread beyond the first kLimitBaseThreadCount threads will
- // have only kLimitMaxExtraThreadStackLen bytes dumped.
- let mut extra_thread_stack_len = MaxStackLen::None; // default to no maximum
- if let Some(minidump_size_limit) = config.minidump_size_limit {
- let estimated_total_stack_size = (num_threads * LIMIT_AVERAGE_THREAD_STACK_LENGTH) as u64;
- let curr_pos = buffer.position();
- let estimated_minidump_size =
- curr_pos + estimated_total_stack_size + LIMIT_MINIDUMP_FUDGE_FACTOR;
- if estimated_minidump_size > minidump_size_limit {
- extra_thread_stack_len = MaxStackLen::Len(LIMIT_MAX_EXTRA_THREAD_STACK_LEN);
- }
- }
-
- for (idx, item) in dumper.threads.clone().iter().enumerate() {
- let mut thread = MDRawThread {
- thread_id: item.tid.try_into()?,
- suspend_count: 0,
- priority_class: 0,
- priority: 0,
- teb: 0,
- stack: MDMemoryDescriptor::default(),
- thread_context: MDLocationDescriptor::default(),
- };
-
- // We have a different source of information for the crashing thread. If
- // we used the actual state of the thread we would find it running in the
- // signal handler with the alternative stack, which would be deeply
- // unhelpful.
- if config.crash_context.is_some() && thread.thread_id == config.blamed_thread as u32 {
- let crash_context = config.crash_context.as_ref().unwrap();
- let instruction_ptr = crash_context.get_instruction_pointer();
- let stack_pointer = crash_context.get_stack_pointer();
- fill_thread_stack(
- config,
- buffer,
- dumper,
- &mut thread,
- instruction_ptr,
- stack_pointer,
- MaxStackLen::None,
- )?;
- // Copy 256 bytes around crashing instruction pointer to minidump.
- let ip_memory_size: usize = 256;
- // Bound it to the upper and lower bounds of the memory map
- // it's contained within. If it's not in mapped memory,
- // don't bother trying to write it.
- for mapping in &dumper.mappings {
- if instruction_ptr < mapping.start_address
- || instruction_ptr >= mapping.start_address + mapping.size
- {
- continue;
- }
- // Try to get 128 bytes before and after the IP, but
- // settle for whatever's available.
- let mut ip_memory_d = MDMemoryDescriptor {
- start_of_memory_range: std::cmp::max(
- mapping.start_address,
- instruction_ptr - ip_memory_size / 2,
- ) as u64,
- ..Default::default()
- };
-
- let end_of_range = std::cmp::min(
- mapping.start_address + mapping.size,
- instruction_ptr + ip_memory_size / 2,
- ) as u64;
- ip_memory_d.memory.data_size =
- (end_of_range - ip_memory_d.start_of_memory_range) as u32;
-
- let memory_copy = PtraceDumper::copy_from_process(
- thread.thread_id as i32,
- ip_memory_d.start_of_memory_range as _,
- ip_memory_d.memory.data_size as usize,
- )?;
-
- let mem_section = MemoryArrayWriter::alloc_from_array(buffer, &memory_copy)?;
- ip_memory_d.memory = mem_section.location();
- config.memory_blocks.push(ip_memory_d);
-
- break;
- }
- // let cpu = MemoryWriter::alloc(buffer, &memory_copy)?;
- let mut cpu: RawContextCPU = Default::default();
- let crash_context = config.crash_context.as_ref().unwrap();
- crash_context.fill_cpu_context(&mut cpu);
- let cpu_section = MemoryWriter::alloc_with_val(buffer, cpu)?;
- thread.thread_context = cpu_section.location();
-
- config.crashing_thread_context =
- CrashingThreadContext::CrashContext(cpu_section.location());
- } else {
- let info = dumper.get_thread_info_by_index(idx)?;
- let max_stack_len =
- if config.minidump_size_limit.is_some() && idx >= LIMIT_BASE_THREAD_COUNT {
- extra_thread_stack_len
- } else {
- MaxStackLen::None // default to no maximum for this thread
- };
- let instruction_ptr = info.get_instruction_pointer();
- fill_thread_stack(
- config,
- buffer,
- dumper,
- &mut thread,
- instruction_ptr,
- info.stack_pointer,
- max_stack_len,
- )?;
-
- let mut cpu = RawContextCPU::default();
- info.fill_cpu_context(&mut cpu);
- let cpu_section = MemoryWriter::::alloc_with_val(buffer, cpu)?;
- thread.thread_context = cpu_section.location();
- if item.tid == config.blamed_thread {
- // This is the crashing thread of a live process, but
- // no context was provided, so set the crash address
- // while the instruction pointer is already here.
- config.crashing_thread_context = CrashingThreadContext::CrashContextPlusAddress((
- cpu_section.location(),
- instruction_ptr,
- ));
- }
- }
- thread_list.set_value_at(buffer, thread, idx)?;
- }
- Ok(dirent)
-}
-
-fn fill_thread_stack(
- config: &mut MinidumpWriter,
- buffer: &mut DumpBuf,
- dumper: &PtraceDumper,
- thread: &mut MDRawThread,
- instruction_ptr: usize,
- stack_ptr: usize,
- max_stack_len: MaxStackLen,
-) -> Result<(), errors::SectionThreadListError> {
- thread.stack.start_of_memory_range = stack_ptr.try_into()?;
- thread.stack.memory.data_size = 0;
- thread.stack.memory.rva = buffer.position() as u32;
-
- if let Ok((valid_stack_ptr, stack_len)) = dumper.get_stack_info(stack_ptr) {
- let stack_len = if let MaxStackLen::Len(max_stack_len) = max_stack_len {
- min(stack_len, max_stack_len)
- } else {
- stack_len
- };
-
- let mut stack_bytes = PtraceDumper::copy_from_process(
- thread.thread_id.try_into()?,
- valid_stack_ptr,
- stack_len,
- )?;
- let stack_pointer_offset = stack_ptr.saturating_sub(valid_stack_ptr);
- if config.skip_stacks_if_mapping_unreferenced {
- if let Some(principal_mapping) = &config.principal_mapping {
- let low_addr = principal_mapping.system_mapping_info.start_address;
- let high_addr = principal_mapping.system_mapping_info.end_address;
- if (instruction_ptr < low_addr || instruction_ptr > high_addr)
- && !principal_mapping
- .stack_has_pointer_to_mapping(&stack_bytes, stack_pointer_offset)
- {
- return Ok(());
- }
- } else {
- return Ok(());
- }
- }
-
- if config.sanitize_stack {
- dumper.sanitize_stack_copy(&mut stack_bytes, stack_ptr, stack_pointer_offset)?;
- }
-
- let stack_location = MDLocationDescriptor {
- data_size: stack_bytes.len() as u32,
- rva: buffer.position() as u32,
- };
- buffer.write_all(&stack_bytes);
- thread.stack.start_of_memory_range = valid_stack_ptr as u64;
- thread.stack.memory = stack_location;
- config.memory_blocks.push(thread.stack);
- }
- Ok(())
-}
diff --git a/src/linux/sections/thread_names_stream.rs b/src/linux/sections/thread_names_stream.rs
deleted file mode 100644
index bd8682b2..00000000
--- a/src/linux/sections/thread_names_stream.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use super::*;
-
-pub fn write(
- buffer: &mut DumpBuf,
- dumper: &PtraceDumper,
-) -> Result {
- // Only count threads that have a name
- let num_threads = dumper.threads.iter().filter(|t| t.name.is_some()).count();
- // Memory looks like this:
- // ...
-
- let list_header = MemoryWriter::::alloc_with_val(buffer, num_threads as u32)?;
-
- let mut dirent = MDRawDirectory {
- stream_type: MDStreamType::ThreadNamesStream as u32,
- location: list_header.location(),
- };
-
- let mut thread_list = MemoryArrayWriter::::alloc_array(buffer, num_threads)?;
- dirent.location.data_size += thread_list.location().data_size;
-
- for (idx, item) in dumper.threads.iter().enumerate() {
- if let Some(name) = &item.name {
- let pos = write_string_to_location(buffer, name)?;
- let thread = MDRawThreadName {
- thread_id: item.tid.try_into()?,
- thread_name_rva: pos.rva.into(),
- };
- thread_list.set_value_at(buffer, thread, idx)?;
- }
- }
- Ok(dirent)
-}
diff --git a/src/linux/thread_info/aarch64.rs b/src/linux/thread_info/aarch64.rs
index d6d39092..4bf4c6f4 100644
--- a/src/linux/thread_info/aarch64.rs
+++ b/src/linux/thread_info/aarch64.rs
@@ -1,10 +1,8 @@
-use super::{CommonThreadInfo, NT_Elf};
-use crate::{
- errors::ThreadInfoError,
- minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
- Pid,
+use {
+ super::{CommonThreadInfo, NT_Elf, Pid, ThreadInfoError},
+ crate::minidump_cpu::{RawContextCPU, FP_REG_COUNT, GP_REG_COUNT},
+ nix::sys::ptrace,
};
-use nix::sys::ptrace;
/// https://github.com/rust-lang/libc/pull/2719
#[derive(Debug)]
diff --git a/src/linux/thread_info/arm.rs b/src/linux/thread_info/arm.rs
index 4346c2e5..b5f12636 100644
--- a/src/linux/thread_info/arm.rs
+++ b/src/linux/thread_info/arm.rs
@@ -1,6 +1,8 @@
-use super::{CommonThreadInfo, NT_Elf};
-use crate::{errors::ThreadInfoError, minidump_cpu::RawContextCPU, Pid};
-use nix::sys::ptrace;
+use {
+ super::{CommonThreadInfo, NT_Elf, Pid, ThreadInfoError},
+ crate::minidump_cpu::RawContextCPU,
+ nix::sys::ptrace,
+};
type Result = std::result::Result;
diff --git a/src/linux/thread_info/mips.rs b/src/linux/thread_info/mips.rs
index 7965ec13..2a99f077 100644
--- a/src/linux/thread_info/mips.rs
+++ b/src/linux/thread_info/mips.rs
@@ -1,5 +1,4 @@
-use crate::{errors::ThreadInfoError, Pid};
-use libc;
+use super::{errors::ThreadInfoError, Pid};
type Result = std::result::Result;
diff --git a/src/linux/thread_info.rs b/src/linux/thread_info/mod.rs
similarity index 80%
rename from src/linux/thread_info.rs
rename to src/linux/thread_info/mod.rs
index adce589b..eed23426 100644
--- a/src/linux/thread_info.rs
+++ b/src/linux/thread_info/mod.rs
@@ -1,12 +1,43 @@
-use crate::{errors::ThreadInfoError, Pid};
-use nix::{errno::Errno, sys::ptrace, unistd};
-use std::{
- io::{self, BufRead},
- path,
+use {
+ super::{serializers::*, Pid},
+ crate::serializers::*,
+ nix::{errno::Errno, sys::ptrace},
+ std::{
+ io::{self, BufRead},
+ path,
+ },
};
type Result = std::result::Result;
+#[derive(thiserror::Error, Debug, serde::Serialize)]
+pub enum ThreadInfoError {
+ #[error("Index out of bounds: Got {0}, only have {1}")]
+ IndexOutOfBounds(usize, usize),
+ #[error("Either ppid ({1}) or tgid ({2}) not found in {0}")]
+ InvalidPid(String, Pid, Pid),
+ #[error("IO error")]
+ IOError(
+ #[from]
+ #[serde(serialize_with = "serialize_io_error")]
+ std::io::Error,
+ ),
+ #[error("Couldn't parse address")]
+ UnparsableInteger(
+ #[from]
+ #[serde(skip)]
+ std::num::ParseIntError,
+ ),
+ #[error("nix::ptrace() error")]
+ PtraceError(
+ #[from]
+ #[serde(serialize_with = "serialize_nix_error")]
+ nix::Error,
+ ),
+ #[error("Invalid line in /proc/{0}/status: {1}")]
+ InvalidProcStatusFile(Pid, String),
+}
+
cfg_if::cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
mod x86;
@@ -35,6 +66,7 @@ enum NT_Elf {
NT_ARM_VFP = 0x400, // ARM VFP/NEON registers
}
+#[cfg(target_arch = "x86_64")]
#[inline]
pub fn copy_u32_registers(dst: &mut [u128], src: &[u32]) {
// SAFETY: We are copying a block of memory from ptrace as u32s to the u128
@@ -54,7 +86,7 @@ trait CommonThreadInfo {
let mut ppid = -1;
let mut tgid = -1;
- let status_path = path::PathBuf::from(format!("/proc/{}/status", tid));
+ let status_path = path::PathBuf::from(format!("/proc/{tid}/status"));
let status_file = std::fs::File::open(status_path)?;
for line in io::BufReader::new(status_file).lines() {
let l = line?;
@@ -79,7 +111,7 @@ trait CommonThreadInfo {
}
if ppid == -1 || tgid == -1 {
return Err(ThreadInfoError::InvalidPid(
- format!("/proc/{}/status", tid),
+ format!("/proc/{tid}/status"),
ppid,
tgid,
));
@@ -138,9 +170,10 @@ trait CommonThreadInfo {
}
/// COPY FROM CRATE nix BECAUSE ITS NOT PUBLIC
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn ptrace_peek(
request: ptrace::RequestType,
- pid: unistd::Pid,
+ pid: nix::unistd::Pid,
addr: ptrace::AddressType,
data: *mut libc::c_void,
) -> nix::Result {
diff --git a/src/linux/thread_info/x86.rs b/src/linux/thread_info/x86.rs
index 827cbe78..55da247c 100644
--- a/src/linux/thread_info/x86.rs
+++ b/src/linux/thread_info/x86.rs
@@ -1,12 +1,15 @@
-use super::{CommonThreadInfo, NT_Elf};
-use crate::{errors::ThreadInfoError, minidump_cpu::RawContextCPU, minidump_format::format, Pid};
-use core::mem::size_of_val;
+use {
+ super::{CommonThreadInfo, NT_Elf, Pid, ThreadInfoError},
+ crate::{minidump_cpu::RawContextCPU, minidump_format::format},
+ core::mem::size_of_val,
+ nix::sys::ptrace,
+ scroll::Pwrite,
+};
+
#[cfg(all(not(target_os = "android"), target_arch = "x86"))]
use libc::user_fpxregs_struct;
#[cfg(not(all(target_os = "android", target_arch = "x86")))]
use libc::{user, user_fpregs_struct, user_regs_struct};
-use nix::sys::ptrace;
-use scroll::Pwrite;
type Result = std::result::Result;
diff --git a/src/mac/errors.rs b/src/mac/errors.rs
index 96ddb88c..44d2f9d7 100644
--- a/src/mac/errors.rs
+++ b/src/mac/errors.rs
@@ -1,6 +1,4 @@
-use thiserror::Error;
-
-#[derive(Debug, Error)]
+#[derive(Debug, thiserror::Error)]
pub enum WriterError {
#[error(transparent)]
TaskDumpError(#[from] crate::mac::task_dumper::TaskDumpError),
diff --git a/src/mac/minidump_writer.rs b/src/mac/minidump_writer.rs
index b05662fd..b1061a81 100644
--- a/src/mac/minidump_writer.rs
+++ b/src/mac/minidump_writer.rs
@@ -1,10 +1,12 @@
-use crate::{
- dir_section::{DirSection, DumpBuf},
- mac::{errors::WriterError, task_dumper::TaskDumper},
- mem_writer::*,
- minidump_format::{self, MDMemoryDescriptor, MDRawDirectory, MDRawHeader},
+use {
+ super::{errors::WriterError, task_dumper::TaskDumper},
+ crate::{
+ dir_section::{DirSection, DumpBuf},
+ mem_writer::*,
+ minidump_format::{self, MDMemoryDescriptor, MDRawDirectory, MDRawHeader},
+ },
+ std::io::{Seek, Write},
};
-use std::io::{Seek, Write};
pub use mach2::mach_types::{task_t, thread_t};
diff --git a/src/mac.rs b/src/mac/mod.rs
similarity index 100%
rename from src/mac.rs
rename to src/mac/mod.rs
diff --git a/src/mac/streams.rs b/src/mac/streams.rs
deleted file mode 100644
index bec3b225..00000000
--- a/src/mac/streams.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-mod breakpad_info;
-mod exception;
-mod memory_list;
-mod misc_info;
-mod module_list;
-mod system_info;
-mod thread_list;
-mod thread_names;
-
-use super::{
- errors::WriterError,
- mach,
- minidump_writer::MinidumpWriter,
- task_dumper::{self, ImageInfo, TaskDumpError, TaskDumper},
-};
-use crate::{dir_section::DumpBuf, mem_writer::*, minidump_format::*};
diff --git a/src/mac/streams/breakpad_info.rs b/src/mac/streams/breakpad_info.rs
index 5196a95c..b7025191 100644
--- a/src/mac/streams/breakpad_info.rs
+++ b/src/mac/streams/breakpad_info.rs
@@ -1,5 +1,7 @@
-use super::*;
-use format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO as BreakpadInfo};
+use {
+ super::*,
+ format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO as BreakpadInfo},
+};
impl MinidumpWriter {
/// Writes the [`BreakpadInfo`] stream.
diff --git a/src/mac/streams/exception.rs b/src/mac/streams/exception.rs
index 7dd7f8fa..c0494f7f 100644
--- a/src/mac/streams/exception.rs
+++ b/src/mac/streams/exception.rs
@@ -1,6 +1,4 @@
-use super::*;
-
-use mach2::exception_types as et;
+use {super::*, mach2::exception_types as et};
impl MinidumpWriter {
/// Writes the [`minidump_common::format::MINIDUMP_EXCEPTION_STREAM`] stream.
diff --git a/src/mac/streams/misc_info.rs b/src/mac/streams/misc_info.rs
index 629b94ce..f3860041 100644
--- a/src/mac/streams/misc_info.rs
+++ b/src/mac/streams/misc_info.rs
@@ -1,6 +1,8 @@
-use super::*;
-use format::{MiscInfoFlags, MINIDUMP_MISC_INFO_2 as MDRawMiscInfo};
-use std::time::Duration;
+use {
+ super::*,
+ format::{MiscInfoFlags, MINIDUMP_MISC_INFO_2 as MDRawMiscInfo},
+ std::time::Duration,
+};
/// From
#[repr(C)]
diff --git a/src/mac/streams/mod.rs b/src/mac/streams/mod.rs
new file mode 100644
index 00000000..4b1942e0
--- /dev/null
+++ b/src/mac/streams/mod.rs
@@ -0,0 +1,18 @@
+mod breakpad_info;
+mod exception;
+mod memory_list;
+mod misc_info;
+mod module_list;
+mod system_info;
+mod thread_list;
+mod thread_names;
+
+use {
+ super::{
+ errors::WriterError,
+ mach,
+ minidump_writer::MinidumpWriter,
+ task_dumper::{self, ImageInfo, TaskDumpError, TaskDumper},
+ },
+ crate::{dir_section::DumpBuf, mem_writer::*, minidump_format::*},
+};
diff --git a/src/mac/streams/system_info.rs b/src/mac/streams/system_info.rs
index aac2de57..30f43d06 100644
--- a/src/mac/streams/system_info.rs
+++ b/src/mac/streams/system_info.rs
@@ -1,5 +1,4 @@
-use super::*;
-use crate::minidump_format::*;
+use {super::*, crate::minidump_format::*};
/// Retrieve the OS version information.
///
diff --git a/src/mac/streams/thread_list.rs b/src/mac/streams/thread_list.rs
index 180bb2f6..d5283de8 100644
--- a/src/mac/streams/thread_list.rs
+++ b/src/mac/streams/thread_list.rs
@@ -1,5 +1,4 @@
-use super::*;
-use crate::minidump_cpu::RawContextCPU;
+use {super::*, crate::minidump_cpu::RawContextCPU};
impl MinidumpWriter {
/// Writes the [`MDStreamType::ThreadListStream`] which is an array of
diff --git a/src/mac/task_dumper.rs b/src/mac/task_dumper.rs
index eeda3632..e479ee45 100644
--- a/src/mac/task_dumper.rs
+++ b/src/mac/task_dumper.rs
@@ -1,6 +1,4 @@
-use crate::mac::mach;
-use mach2::mach_types as mt;
-use thiserror::Error;
+use {super::mach, mach2::mach_types as mt, thiserror::Error};
#[derive(Error, Debug)]
pub enum TaskDumpError {
diff --git a/src/windows/minidump_writer.rs b/src/windows/minidump_writer.rs
index a5b7bebd..2576b59c 100644
--- a/src/windows/minidump_writer.rs
+++ b/src/windows/minidump_writer.rs
@@ -1,17 +1,21 @@
#![allow(unsafe_code)]
-use crate::windows::errors::Error;
-use crate::windows::ffi::{
- capture_context, CloseHandle, GetCurrentProcess, GetCurrentThreadId, GetThreadContext,
- MiniDumpWriteDump, MinidumpType, OpenProcess, OpenThread, ResumeThread, SuspendThread,
- EXCEPTION_POINTERS, EXCEPTION_RECORD, FALSE, HANDLE, MINIDUMP_EXCEPTION_INFORMATION,
- MINIDUMP_USER_STREAM, MINIDUMP_USER_STREAM_INFORMATION, PROCESS_ALL_ACCESS,
- STATUS_NONCONTINUABLE_EXCEPTION, THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION,
- THREAD_SUSPEND_RESUME,
+use {
+ super::{
+ errors::Error,
+ ffi::{
+ capture_context, CloseHandle, GetCurrentProcess, GetCurrentThreadId, GetThreadContext,
+ MiniDumpWriteDump, MinidumpType, OpenProcess, OpenThread, ResumeThread, SuspendThread,
+ EXCEPTION_POINTERS, EXCEPTION_RECORD, FALSE, HANDLE, MINIDUMP_EXCEPTION_INFORMATION,
+ MINIDUMP_USER_STREAM, MINIDUMP_USER_STREAM_INFORMATION, PROCESS_ALL_ACCESS,
+ STATUS_NONCONTINUABLE_EXCEPTION, THREAD_GET_CONTEXT, THREAD_QUERY_INFORMATION,
+ THREAD_SUSPEND_RESUME,
+ },
+ },
+ minidump_common::format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO, MINIDUMP_STREAM_TYPE},
+ scroll::Pwrite,
+ std::os::windows::io::AsRawHandle,
};
-use minidump_common::format::{BreakpadInfoValid, MINIDUMP_BREAKPAD_INFO, MINIDUMP_STREAM_TYPE};
-use scroll::Pwrite;
-use std::os::windows::io::AsRawHandle;
pub struct MinidumpWriter {
/// Optional exception information
diff --git a/src/windows.rs b/src/windows/mod.rs
similarity index 98%
rename from src/windows.rs
rename to src/windows/mod.rs
index 34b72444..aebd6dee 100644
--- a/src/windows.rs
+++ b/src/windows/mod.rs
@@ -1,5 +1,6 @@
-pub mod errors;
+pub use ffi::MinidumpType;
+
mod ffi;
-pub mod minidump_writer;
-pub use ffi::MinidumpType;
+pub mod errors;
+pub mod minidump_writer;
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index 745e3a9a..232d79b9 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -1,7 +1,9 @@
-use std::error;
-use std::io::{BufRead, BufReader, Write};
-use std::process::{Child, Command, Stdio};
-use std::result;
+use std::{
+ error,
+ io::{BufRead, BufReader, Write},
+ process::{Child, Command, Stdio},
+ result,
+};
#[allow(unused)]
type Error = Box;
@@ -135,6 +137,8 @@ pub fn assert_soft_errors_in_minidump<'a, 'b, T, I>(
for expected_error in expected_errors {
assert!(actual_errors
.iter()
- .any(|actual_error| actual_error == expected_error));
+ .any(|actual_error| actual_error == expected_error),
+ "soft error list missing expected error `{expected_error:#?}`\nError_list: {actual_errors:#?}"
+ );
}
}
diff --git a/tests/linux_minidump_writer.rs b/tests/linux_minidump_writer.rs
index d62f56ff..0c7a1884 100644
--- a/tests/linux_minidump_writer.rs
+++ b/tests/linux_minidump_writer.rs
@@ -8,11 +8,9 @@ use {
minidump_writer::{
app_memory::AppMemory,
crash_context::CrashContext,
- errors::*,
maps_reader::{MappingEntry, MappingInfo, SystemMappingInfo},
- minidump_writer::MinidumpWriter,
+ minidump_writer::{errors::WriterError, MinidumpWriter, MinidumpWriterConfig},
module_reader::{BuildId, ReadFromModule},
- ptrace_dumper::PtraceDumper,
Pid,
},
nix::{errno::Errno, sys::signal::Signal},
@@ -35,8 +33,8 @@ enum Context {
}
impl Context {
- pub fn minidump_writer(&self, pid: Pid) -> MinidumpWriter {
- let mut mw = MinidumpWriter::new(pid, pid);
+ pub fn minidump_writer(&self, pid: Pid) -> MinidumpWriterConfig {
+ let mut mw = MinidumpWriterConfig::new(pid, pid);
#[cfg(not(target_arch = "mips"))]
if self == &Context::With {
let crash_context = get_crash_context(pid);
@@ -109,8 +107,8 @@ contextual_test! {
.tempfile()
.unwrap();
- let mut tmp = context.minidump_writer(pid);
- let in_memory_buffer = tmp.dump(&mut tmpfile).expect("Could not write minidump");
+ let tmp = context.minidump_writer(pid);
+ let in_memory_buffer = tmp.write(&mut tmpfile).expect("Could not write minidump");
child.kill().expect("Failed to kill process");
// Reap child
@@ -179,8 +177,9 @@ contextual_test! {
let mut tmp = context.minidump_writer(pid);
- tmp.set_user_mapping_list(vec![entry])
- .dump(&mut tmpfile)
+ tmp.set_user_mapping_list(vec![entry]);
+ tmp
+ .write(&mut tmpfile)
.expect("Could not write minidump");
child.kill().expect("Failed to kill process");
@@ -267,8 +266,9 @@ contextual_test! {
let mut tmp = context.minidump_writer(pid);
- tmp.set_app_memory(vec![app_memory])
- .dump(&mut tmpfile)
+ tmp.set_app_memory(vec![app_memory]);
+ tmp
+ .write(&mut tmpfile)
.expect("Could not write minidump");
child.kill().expect("Failed to kill process");
@@ -302,7 +302,9 @@ contextual_test! {
contextual_test! {
fn skip_if_requested(context: Context) {
let expected_errors = vec![
- json!("PrincipalMappingNotReferenced"),
+ json!({
+ "InitErrors": ["PrincipalMappingNotReferenced"]
+ }),
];
let num_of_threads = 1;
@@ -325,10 +327,11 @@ contextual_test! {
{
pr_mapping_addr = 0x010203040;
};
- let res = tmp
+ tmp
.skip_stacks_if_mapping_unreferenced()
- .set_principal_mapping_address(pr_mapping_addr)
- .dump(&mut tmpfile);
+ .set_principal_mapping_address(pr_mapping_addr);
+ let res = tmp
+ .write(&mut tmpfile);
child.kill().expect("Failed to kill process");
// Reap child
@@ -361,8 +364,9 @@ contextual_test! {
.unwrap();
let mut tmp = context.minidump_writer(pid);
- tmp.sanitize_stack()
- .dump(&mut tmpfile)
+ tmp.sanitize_stack();
+ tmp
+ .write(&mut tmpfile)
.expect("Faild to dump minidump");
child.kill().expect("Failed to kill process");
@@ -431,9 +435,10 @@ contextual_test! {
};
let mut tmp = context.minidump_writer(pid);
+ tmp.set_app_memory(vec![app_memory]);
// 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) {
+ match tmp.write(&mut tmpfile) {
Err(WriterError::SectionAppMemoryError(_)) => (),
_ => panic!("Wrong kind of error returned"),
}
@@ -467,8 +472,8 @@ contextual_test! {
.tempfile()
.unwrap();
- let mut tmp = context.minidump_writer(pid);
- let _ = tmp.dump(&mut tmpfile).expect("Could not write minidump");
+ let tmp = context.minidump_writer(pid);
+ let _ = tmp.write(&mut tmpfile).expect("Could not write minidump");
child.kill().expect("Failed to kill process");
// Reap child
@@ -495,7 +500,7 @@ contextual_test! {
let mut expected = HashSet::new();
expected.insert("test".to_string());
for id in 1..num_of_threads {
- expected.insert(format!("thread_{}", id));
+ expected.insert(format!("thread_{id}"));
}
assert_eq!(expected, names);
}
@@ -512,8 +517,8 @@ contextual_test! {
.tempfile()
.unwrap();
- let mut tmp = context.minidump_writer(pid);
- let _ = tmp.dump(&mut tmpfile).expect("Could not write minidump");
+ let tmp = context.minidump_writer(pid);
+ let _ = tmp.write(&mut tmpfile).expect("Could not write minidump");
child.kill().expect("Failed to kill process");
// Reap child
@@ -567,8 +572,8 @@ fn minidump_size_limit() {
.tempfile()
.unwrap();
- MinidumpWriter::new(pid, pid)
- .dump(&mut tmpfile)
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
.expect("Could not write minidump");
let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile");
@@ -599,10 +604,9 @@ fn minidump_size_limit() {
.tempfile()
.unwrap();
- MinidumpWriter::new(pid, pid)
- .set_minidump_size_limit(minidump_size_limit)
- .dump(&mut tmpfile)
- .expect("Could not write minidump");
+ let mut tmp = MinidumpWriterConfig::new(pid, pid);
+ tmp.set_minidump_size_limit(minidump_size_limit);
+ tmp.write(&mut tmpfile).expect("Could not write minidump");
let meta = std::fs::metadata(tmpfile.path()).expect("Couldn't get metadata for tempfile");
@@ -646,10 +650,9 @@ fn minidump_size_limit() {
.tempfile()
.unwrap();
- MinidumpWriter::new(pid, pid)
- .set_minidump_size_limit(minidump_size_limit)
- .dump(&mut tmpfile)
- .expect("Could not write minidump");
+ let mut tmp = MinidumpWriterConfig::new(pid, pid);
+ tmp.set_minidump_size_limit(minidump_size_limit);
+ tmp.write(&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);
@@ -733,8 +736,8 @@ fn with_deleted_binary() {
.tempfile()
.unwrap();
- MinidumpWriter::new(pid, pid)
- .dump(&mut tmpfile)
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
.expect("Could not write minidump");
child.kill().expect("Failed to kill process");
@@ -794,8 +797,8 @@ fn memory_info_list_stream() {
.unwrap();
// Write a minidump
- MinidumpWriter::new(pid, pid)
- .dump(&mut tmpfile)
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
.expect("cound not write minidump");
child.kill().expect("Failed to kill process");
child.wait().expect("Failed to wait on killed process");
diff --git a/tests/linux_minidump_writer_soft_error.rs b/tests/linux_minidump_writer_soft_error.rs
index 5c5df212..736009e1 100644
--- a/tests/linux_minidump_writer_soft_error.rs
+++ b/tests/linux_minidump_writer_soft_error.rs
@@ -3,7 +3,7 @@
use {
common::*,
minidump::Minidump,
- minidump_writer::{minidump_writer::MinidumpWriter, FailSpotName},
+ minidump_writer::{minidump_writer::MinidumpWriterConfig, FailSpotName},
serde_json::json,
};
@@ -23,8 +23,8 @@ fn soft_error_stream() {
fail_client.set_enabled(FailSpotName::StopProcess, true);
// Write a minidump
- MinidumpWriter::new(pid, pid)
- .dump(&mut tmpfile)
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
.expect("cound not write minidump");
child.kill().expect("Failed to kill process");
child.wait().expect("Failed to wait on killed process");
@@ -47,9 +47,9 @@ fn soft_error_stream_content() {
error: \"testing requested failure reading thread name\",\n\
}"
}
- ]}
+ ]},
+ {"SuspendThreadsErrors": [{"PtraceAttachError": [1234, "EPERM"]}]}
]}),
- json!({"SuspendThreadsErrors": [{"PtraceAttachError": [1234, "EPERM"]}]}),
json!({"WriteSystemInfoErrors": [
{"WriteCpuInformationFailed": {"IOError": "\
Custom {\n \
@@ -80,8 +80,8 @@ fn soft_error_stream_content() {
}
// Write a minidump
- MinidumpWriter::new(pid, pid)
- .dump(&mut tmpfile)
+ MinidumpWriterConfig::new(pid, pid)
+ .write(&mut tmpfile)
.expect("cound not write minidump");
child.kill().expect("Failed to kill process");
child.wait().expect("Failed to wait on killed process");
diff --git a/tests/mac_minidump_writer.rs b/tests/mac_minidump_writer.rs
index dcc4aa3e..1087951a 100644
--- a/tests/mac_minidump_writer.rs
+++ b/tests/mac_minidump_writer.rs
@@ -1,13 +1,15 @@
#![cfg(target_os = "macos")]
-mod common;
-use common::start_child_and_return;
-
-use minidump::{
- CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpMiscInfo,
- MinidumpModuleList, MinidumpSystemInfo, MinidumpThreadList,
+use {
+ common::start_child_and_return,
+ minidump::{
+ CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpMiscInfo,
+ MinidumpModuleList, MinidumpSystemInfo, MinidumpThreadList,
+ },
+ minidump_writer::minidump_writer::MinidumpWriter,
};
-use minidump_writer::minidump_writer::MinidumpWriter;
+
+mod common;
fn get_crash_reason<'a, T: std::ops::Deref + 'a>(
md: &Minidump<'a, T>,
diff --git a/tests/ptrace_dumper.rs b/tests/ptrace_dumper.rs
index b166c256..d0de9eeb 100644
--- a/tests/ptrace_dumper.rs
+++ b/tests/ptrace_dumper.rs
@@ -2,8 +2,9 @@
#![cfg(any(target_os = "linux", target_os = "android"))]
use {
+ common::*,
error_graph::ErrorList,
- minidump_writer::ptrace_dumper::PtraceDumper,
+ minidump_writer::minidump_writer::MinidumpWriterConfig,
nix::{
sys::mman::{mmap, MapFlags, ProtFlags},
sys::signal::Signal,
@@ -17,7 +18,6 @@ use {
};
mod common;
-use common::*;
/// These tests generally aren't consistent in resource-deprived environments like CI runners and
/// android emulators.
@@ -106,24 +106,17 @@ fn test_thread_list_from_parent() {
let mut child = start_child_and_wait_for_threads(num_of_threads);
let pid = child.id() as i32;
- let mut dumper = assert_no_soft_errors!(
+ let dumper = assert_no_soft_errors!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- pid,
- minidump_writer::minidump_writer::STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )
+ MinidumpWriterConfig::new(pid, pid).build_for_testing(&mut soft_errors)
)
.expect("Couldn't init dumper");
assert_eq!(dumper.threads.len(), num_of_threads);
- assert_no_soft_errors!(soft_errors, dumper.suspend_threads(&mut soft_errors));
-
// let mut matching_threads = 0;
for (idx, curr_thread) in dumper.threads.iter().enumerate() {
- println!("curr_thread: {:?}", curr_thread);
+ println!("curr_thread: {curr_thread:?}");
let info = dumper
.get_thread_info_by_index(idx)
.expect("Could not get thread info by index");
@@ -167,7 +160,7 @@ fn test_thread_list_from_parent() {
0
}; */
}
- assert_no_soft_errors!(soft_errors, dumper.resume_threads(&mut soft_errors));
+ drop(dumper);
child.kill().expect("Failed to kill process");
// Reap child
@@ -293,20 +286,13 @@ fn test_sanitize_stack_copy() {
let heap_addr = usize::from_str_radix(output.next().unwrap().trim_start_matches("0x"), 16)
.expect("unable to parse mmap_addr");
- let mut dumper = assert_no_soft_errors!(
+ let dumper = assert_no_soft_errors!(
soft_errors,
- PtraceDumper::new_report_soft_errors(
- pid,
- minidump_writer::minidump_writer::STOP_TIMEOUT,
- Default::default(),
- &mut soft_errors,
- )
+ MinidumpWriterConfig::new(pid, pid).build_for_testing(&mut soft_errors)
)
.expect("Couldn't init dumper");
assert_eq!(dumper.threads.len(), num_of_threads);
- assert_no_soft_errors!(soft_errors, dumper.suspend_threads(&mut soft_errors));
-
let thread_info = dumper
.get_thread_info_by_index(0)
.expect("Couldn't find thread_info");
@@ -401,7 +387,7 @@ fn test_sanitize_stack_copy() {
assert_eq!(simulated_stack[0..size_of::()], defaced);
- assert_no_soft_errors!(soft_errors, dumper.resume_threads(&mut soft_errors));
+ drop(dumper);
child.kill().expect("Failed to kill process");
diff --git a/tests/task_dumper.rs b/tests/task_dumper.rs
index 1411acc3..146dc7c4 100644
--- a/tests/task_dumper.rs
+++ b/tests/task_dumper.rs
@@ -1,8 +1,10 @@
//! All of these tests are specific to the MacOS task dumper
#![cfg(target_os = "macos")]
-use minidump_writer::{mach::LoadCommand, task_dumper::TaskDumper};
-use std::fmt::Write;
+use {
+ minidump_writer::{mach::LoadCommand, task_dumper::TaskDumper},
+ std::fmt::Write,
+};
fn call_otool(args: &[&str]) -> String {
let mut cmd = std::process::Command::new("otool");
diff --git a/tests/windows_minidump_writer.rs b/tests/windows_minidump_writer.rs
index 95f08467..531f2ae3 100644
--- a/tests/windows_minidump_writer.rs
+++ b/tests/windows_minidump_writer.rs
@@ -1,12 +1,15 @@
#![cfg(all(target_os = "windows", target_arch = "x86_64"))]
-use minidump::{
- CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpSystemInfo,
- MinidumpThreadList,
+use {
+ common::start_child_and_return,
+ minidump::{
+ CrashReason, Minidump, MinidumpBreakpadInfo, MinidumpMemoryList, MinidumpSystemInfo,
+ MinidumpThreadList,
+ },
+ minidump_writer::minidump_writer::MinidumpWriter,
};
-use minidump_writer::minidump_writer::MinidumpWriter;
+
mod common;
-use common::start_child_and_return;
const EXCEPTION_ILLEGAL_INSTRUCTION: i32 = -1073741795;
const STATUS_INVALID_PARAMETER: i32 = -1073741811;