Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 34 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ bitflags = "2.4"
byteorder = "1.4"
cfg-if = "1.0"
crash-context = "0.6"
error-graph = { version = "0.1.1", features = ["serde"] }
failspot = "0.2.0"
log = "0.4"
memoffset = "0.9"
minidump-common = "0.24"
scroll = "0.12"
serde = { version = "1.0.208", features = ["derive"] }
serde_json = "1.0.116"
tempfile = "3.8"
thiserror = "1.0"

Expand All @@ -36,7 +40,7 @@ nix = { version = "0.29", default-features = false, features = [
] }
# Used for parsing procfs info.
# default-features is disabled since it pulls in chrono
procfs-core = { version = "0.16", default-features = false }
procfs-core = { version = "0.17", default-features = false, features = ["serde1"] }

[target.'cfg(target_os = "windows")'.dependencies]
bitflags = "2.4"
Expand All @@ -49,6 +53,7 @@ mach2 = "0.4"
# We auto-detect what the test binary that is spawned for most tests should be
# compiled for
current_platform = "0.2"
failspot = { version = "0.2.0", features = ["enabled"] }
# Minidump-processor is async so we need an executor
futures = { version = "0.3", features = ["executor"] }
minidump = "0.24"
Expand Down
135 changes: 111 additions & 24 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ pub type Result<T> = std::result::Result<T, Error>;

#[cfg(any(target_os = "linux", target_os = "android"))]
mod linux {
use super::*;
use minidump_writer::{
minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper,
LINUX_GATE_LIBRARY_NAME,
};
use nix::{
sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
unistd::getppid,
use {
super::*,
error_graph::ErrorList,
minidump_writer::{
minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper,
LINUX_GATE_LIBRARY_NAME,
},
nix::{
sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
unistd::getppid,
},
};

macro_rules! test {
Expand All @@ -24,15 +27,40 @@ mod linux {
};
}

macro_rules! fail_on_soft_error(($n: ident, $e: expr) => {{
let mut $n = ErrorList::default();
let __result = $e;
if !$n.is_empty() {
return Err($n.into());
}
__result
}});

fn test_setup() -> Result<()> {
let ppid = getppid();
PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid.as_raw(),
STOP_TIMEOUT,
Default::default(),
&mut soft_errors,
)?
);
Ok(())
}

fn test_thread_list() -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid.as_raw(),
STOP_TIMEOUT,
Default::default(),
&mut soft_errors,
)?
);
test!(!dumper.threads.is_empty(), "No threads");
test!(
dumper
Expand All @@ -59,8 +87,17 @@ mod linux {
use minidump_writer::mem_reader::MemReader;

let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;
let mut dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid,
STOP_TIMEOUT,
Default::default(),
&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
Expand Down Expand Up @@ -113,13 +150,22 @@ mod linux {

test!(heap_res == expected_heap, "heap var not correct");

dumper.resume_threads()?;
fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));

Ok(())
}

fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid.as_raw(),
STOP_TIMEOUT,
Default::default(),
&mut soft_errors,
)?
);
dumper
.find_mapping(addr1)
.ok_or("No mapping for addr1 found")?;
Expand All @@ -136,8 +182,19 @@ mod linux {
let ppid = getppid().as_raw();
let exe_link = format!("/proc/{ppid}/exe");
let exe_name = std::fs::read_link(exe_link)?.into_os_string();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;

let mut dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid,
STOP_TIMEOUT,
Default::default(),
&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) {
Expand All @@ -147,21 +204,31 @@ mod linux {
}
let idx = found_exe.unwrap();
let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?;
dumper.resume_threads()?;

fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));

assert!(!id.is_empty());
assert!(id.iter().any(|&x| x > 0));
Ok(())
}

fn test_merged_mappings(path: String, mapped_mem: usize, mem_size: usize) -> Result<()> {
// Now check that PtraceDumper interpreted the mappings properly.
let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT, Default::default())?;
let dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
getppid().as_raw(),
STOP_TIMEOUT,
Default::default(),
&mut soft_errors,
)?
);
let mut mapping_count = 0;
for map in &dumper.mappings {
if map
.name
.as_ref()
.map_or(false, |name| name.to_string_lossy().starts_with(&path))
.is_some_and(|name| name.to_string_lossy().starts_with(&path))
{
mapping_count += 1;
// This mapping should encompass the entire original mapped
Expand All @@ -177,17 +244,29 @@ mod linux {

fn test_linux_gate_mapping_id() -> Result<()> {
let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
let mut dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid,
STOP_TIMEOUT,
Default::default(),
&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;
dumper.suspend_threads()?;

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)?;
test!(!id.is_empty(), "id-vec is empty");
test!(id.iter().any(|&x| x > 0), "all id elements are 0");
dumper.resume_threads()?;

fail_on_soft_error!(soft_errors, dumper.resume_threads(&mut soft_errors));

break;
}
}
Expand All @@ -197,15 +276,23 @@ mod linux {

fn test_mappings_include_linux_gate() -> Result<()> {
let ppid = getppid().as_raw();
let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
let dumper = fail_on_soft_error!(
soft_errors,
PtraceDumper::new_report_soft_errors(
ppid,
STOP_TIMEOUT,
Default::default(),
&mut soft_errors
)?
);
let linux_gate_loc = dumper.auxv.get_linux_gate_address().unwrap();
test!(linux_gate_loc != 0, "linux_gate_loc == 0");
let mut found_linux_gate = false;
for mapping in &dumper.mappings {
if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
found_linux_gate = true;
test!(
linux_gate_loc == mapping.start_address.try_into()?,
usize::try_from(linux_gate_loc)? == mapping.start_address,
"linux_gate_loc != start_address"
);

Expand Down
19 changes: 13 additions & 6 deletions src/dir_section.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use crate::{
mem_writer::{Buffer, MemoryArrayWriter, MemoryWriterError},
minidump_format::MDRawDirectory,
use {
crate::{
mem_writer::{Buffer, MemoryArrayWriter, MemoryWriterError},
minidump_format::MDRawDirectory,
serializers::*,
},
std::io::{Error, Seek, Write},
};
use std::io::{Error, Seek, Write};

pub type DumpBuf = Buffer;

#[derive(Debug, thiserror::Error)]
#[derive(Debug, thiserror::Error, serde::Serialize)]
pub enum FileWriterError {
#[error("IO error")]
IOError(#[from] Error),
IOError(
#[from]
#[serde(serialize_with = "serialize_io_error")]
Error,
),
#[error("Failed to write to memory")]
MemoryWriterError(#[from] MemoryWriterError),
}
Expand Down
Loading