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 1 commit
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
23 changes: 18 additions & 5 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ cfg-if = "1.0"
crash-context = "0.6"
log = "0.4"
memoffset = "0.9"
minidump-common = "0.22"
minidump-common = { path = "../rust-minidump/minidump-common" }
scroll = "0.12"
tempfile = "3.8"
thiserror = "1.0"
Expand Down
54 changes: 40 additions & 14 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ mod linux {

fn test_setup() -> Result<()> {
let ppid = getppid();
PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let (_dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
Ok(())
}

fn test_thread_list() -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
let (dumper, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
test!(!dumper.threads.is_empty(), "No threads");
test!(
dumper
Expand All @@ -59,8 +61,12 @@ 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, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;

if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}

// 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 +119,17 @@ mod linux {

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

dumper.resume_threads()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}

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, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
dumper
.find_mapping(addr1)
.ok_or("No mapping for addr1 found")?;
Expand All @@ -136,8 +146,12 @@ 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, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}

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 +161,21 @@ mod linux {
}
let idx = found_exe.unwrap();
let module_reader::BuildId(id) = dumper.from_process_memory_for_index(idx)?;
dumper.resume_threads()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}
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, _soft_errors) = PtraceDumper::new_report_soft_errors(
getppid().as_raw(),
STOP_TIMEOUT,
Default::default(),
)?;
let mut mapping_count = 0;
for map in &dumper.mappings {
if map
Expand All @@ -177,17 +197,22 @@ 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, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
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()?;
if let Some(soft_errors) = dumper.suspend_threads().some() {
return Err(soft_errors.into());
}
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()?;
if let Some(soft_errors) = dumper.resume_threads().some() {
return Err(soft_errors.into());
}
break;
}
}
Expand All @@ -197,7 +222,8 @@ 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, _soft_errors) =
PtraceDumper::new_report_soft_errors(ppid, STOP_TIMEOUT, Default::default())?;
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;
Expand Down
101 changes: 101 additions & 0 deletions src/error_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Handling of "soft errors" while generating the minidump

/// Encapsulates a list of "soft error"s
///
/// A "soft error" is an error that is encounted while generating the minidump that doesn't
/// totally prevent the minidump from being useful, but it may have missing or invalid
/// information.
///
/// It should be returned by a function when the function was able to at-least partially achieve
/// its goals, and when further use of functions in the same API is permissible and can still be
/// at-least partially functional.
///
/// Admittedly, this concept makes layers of abstraction a bit more difficult, as something that
/// is considered "soft" by one layer may be a deal-breaker for the layer above it, or visa-versa
/// -- an error that a lower layer considers a total failure might just be a nuissance for the layer
/// above it.
///
/// An example of the former might be the act of suspending all the threads -- The `PTraceDumper``
/// API will actually work just fine even if none of the threads are suspended, so it only returns
/// a soft error; however, the dumper itself considers it to be a critical failure if not even one
/// thread could be stopped.
///
/// An example of the latter might trying to stop the process -- Being unable to send SIGSTOP to
/// the process would be considered a critical failure by `stop_process()`, but merely an
/// inconvenience by the code that's calling it.
#[must_use]
pub struct SoftErrorList<E> {
errors: Vec<E>,
}

impl<E> SoftErrorList<E> {
/// Returns `Some(Self)` if the list contains at least one soft error
pub fn some(self) -> Option<Self> {
if !self.is_empty() {
Some(self)
} else {
None
}
}
/// Returns `true` if the list is empty
pub fn is_empty(&self) -> bool {
self.errors.is_empty()
}
/// Add a soft error to the list
pub fn push(&mut self, error: E) {
self.errors.push(error);
}
}

impl<E> Default for SoftErrorList<E> {
fn default() -> Self {
Self { errors: Vec::new() }
}
}

impl<E: std::error::Error> SoftErrorList<E> {
// Helper function for the Debug and Display traits
fn fmt(&self, f: &mut std::fmt::Formatter<'_>, write_sources: bool) -> std::fmt::Result {
writeln!(f, "one or more soft errors occurred:")?;
writeln!(f)?;
for (i, e) in self.errors.iter().enumerate() {
writeln!(f, " {i}:")?;

for line in e.to_string().lines() {
writeln!(f, " {line}")?;
}

writeln!(f)?;

if write_sources {
let mut source = e.source();
while let Some(e) = source {
writeln!(f, " caused by:")?;

for line in e.to_string().lines() {
writeln!(f, " {line}")?;
}

writeln!(f)?;

source = e.source();
}
}
}
Ok(())
}
}

impl<E: std::error::Error> std::fmt::Debug for SoftErrorList<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt(f, true)
}
}

impl<E: std::error::Error> std::fmt::Display for SoftErrorList<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt(f, false)
}
}

impl<E: std::error::Error> std::error::Error for SoftErrorList<E> {}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ pub mod minidump_format;

pub mod dir_section;
pub mod mem_writer;

mod error_list;
24 changes: 18 additions & 6 deletions src/linux/auxv/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::error_list::SoftErrorList;
pub use reader::ProcfsAuxvIter;

use {
crate::Pid,
std::{fs::File, io::BufReader},
Expand Down Expand Up @@ -79,17 +81,27 @@ pub struct AuxvDumpInfo {
}

impl AuxvDumpInfo {
pub fn try_filling_missing_info(&mut self, pid: Pid) -> Result<(), AuxvError> {
pub fn try_filling_missing_info(
&mut self,
pid: Pid,
) -> Result<SoftErrorList<AuxvError>, AuxvError> {
if self.is_complete() {
return Ok(());
return Ok(SoftErrorList::default());
}

let auxv_path = format!("/proc/{pid}/auxv");
let auxv_file = File::open(&auxv_path).map_err(|e| AuxvError::OpenError(auxv_path, e))?;

for AuxvPair { key, value } in
ProcfsAuxvIter::new(BufReader::new(auxv_file)).filter_map(Result::ok)
{
let mut soft_errors = SoftErrorList::default();

for pair_result in ProcfsAuxvIter::new(BufReader::new(auxv_file)) {
let AuxvPair { key, value } = match pair_result {
Ok(pair) => pair,
Err(e) => {
soft_errors.push(e);
continue;
}
};
let dest_field = match key {
consts::AT_PHNUM => &mut self.program_header_count,
consts::AT_PHDR => &mut self.program_header_address,
Expand All @@ -102,7 +114,7 @@ impl AuxvDumpInfo {
}
}

Ok(())
Ok(soft_errors)
}
pub fn get_program_header_count(&self) -> Option<AuxvType> {
self.program_header_count
Expand Down
Loading
Loading