Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add soft errors to minidump #138

Merged
merged 7 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ linker = "x86_64-linux-android30-clang"
# By default the linker _doesn't_ generate a build-id, however we want one for our tests.
rustflags = ["-C", "link-args=-Wl,--build-id=sha1"]
runner = [".cargo/android-runner.sh"]

[patch.crates-io]
minidump-common = { path = "../rust-minidump/minidump-common" }
34 changes: 32 additions & 2 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.22"
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.16", 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.22"
Expand Down
133 changes: 110 additions & 23 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,15 +204,25 @@ 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
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
15 changes: 13 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@ cfg_if::cfg_if! {
}
}

pub mod dir_section;
pub mod mem_writer;
pub mod minidump_cpu;
pub mod minidump_format;

pub mod dir_section;
pub mod mem_writer;
mod serializers;

failspot::failspot_name! {
pub enum FailSpotName {
StopProcess,
FillMissingAuxvInfo,
ThreadName,
SuspendThreads,
CpuInfoFileOpen,
}
}
Loading
Loading