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
1 change: 1 addition & 0 deletions Cargo.lock

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

11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ license = "MIT"
bitflags = "2.10"
cfg-if = "1.0"
crash-context = "0.6"
error-graph = { version = "0.1.1", features = ["serde"] }
failspot = "0.2.0"
# goblin >= 0.10 uses scroll 0.13
goblin = "0.9.2"
log = "0.4"
# Type definitions and utilities for working with the Minidump format
minidump-common = "0.26"
Expand All @@ -24,8 +28,6 @@ thiserror = "2.0"

[target.'cfg(unix)'.dependencies]
libc = "0.2"
# goblin >= 0.10 uses scroll 0.13
goblin = "0.9"
memmap2 = "0.9"

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
Expand All @@ -46,6 +48,11 @@ procfs-core = { version = "0.18", default-features = false, features = ["serde1"

[target.'cfg(target_os = "windows")'.dependencies]
bitflags = "2.4"
windows-sys = { version = ">=0.52", features = [
"Win32_Foundation",
"Win32_System_Diagnostics_Debug",
"Win32_System_ProcessStatus",
] }

[target.'cfg(target_os = "macos")'.dependencies]
# Binds some additional mac specifics not in libc, note that other dependents
Expand Down
10 changes: 5 additions & 5 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ mod linux {
}

fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> {
use minidump_writer::mem_reader::MemReader;
use minidump_writer::process_reader::ProcessReader;

let ppid = getppid().as_raw();
let dumper = fail_on_soft_error!(
Expand All @@ -91,7 +91,7 @@ mod linux {
let expected_stack = 0x11223344usize.to_ne_bytes();
let expected_heap = 0x55667788usize.to_ne_bytes();

let validate = |reader: &mut MemReader| -> Result<()> {
let validate = |reader: &mut ProcessReader| -> Result<()> {
let mut val = [0u8; std::mem::size_of::<usize>()];
let read = reader.read(stack_var, &mut val)?;
assert_eq!(read, val.len());
Expand All @@ -106,22 +106,22 @@ mod linux {

// virtual mem
{
let mut mr = MemReader::for_virtual_mem(ppid);
let mut mr = ProcessReader::for_virtual_mem(ppid);
validate(&mut mr)
.map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
}

// file
{
let mut mr = MemReader::for_file(ppid)
let mut mr = ProcessReader::for_file(ppid)
.map_err(|err| format!("failed to open `/proc/{ppid}/mem`: {err}"))?;
validate(&mut mr)
.map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
}

// ptrace
{
let mut mr = MemReader::for_ptrace(ppid);
let mut mr = ProcessReader::for_ptrace(ppid);
validate(&mut mr)
.map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ pub mod dir_section;
pub mod mem_writer;
pub mod minidump_cpu;
pub mod minidump_format;
pub mod module_reader;
pub mod process_reader;

mod serializers;
4 changes: 2 additions & 2 deletions src/linux/android.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
super::{
Pid, maps_reader::MappingInfo, mem_reader::CopyFromProcessError,
minidump_writer::MinidumpWriter,
Pid, maps_reader::MappingInfo, minidump_writer::MinidumpWriter,
process_reader::CopyFromProcessError,
},
goblin::elf,
};
Expand Down
2 changes: 1 addition & 1 deletion src/linux/dso_debug.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
super::{
auxv::AuxvDumpInfo, mem_reader::CopyFromProcessError, minidump_writer::MinidumpWriter,
auxv::AuxvDumpInfo, minidump_writer::MinidumpWriter, process_reader::CopyFromProcessError,
serializers::*,
},
crate::{
Expand Down
27 changes: 26 additions & 1 deletion src/linux/maps_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use {
byteorder::{NativeEndian, ReadBytesExt},
goblin::elf,
memmap2::{Mmap, MmapOptions},
procfs_core::process::{MMPermissions, MMapPath, MemoryMaps},
procfs_core::{
FromRead,
process::{MMPermissions, MMapPath, MemoryMaps},
},
std::{
ffi::{OsStr, OsString},
fs::File,
Expand Down Expand Up @@ -96,6 +99,14 @@ pub enum MapsReaderError {
MmapSanityCheckFailed,
#[error("Symlink does not match ({0} vs. {1})")]
SymlinkError(std::path::PathBuf, std::path::PathBuf),
#[error("Mappings file missing for pid {pid} (is the process still alive?)")]
MappingFileMissing { pid: i32 },
#[error("Failed to parse memory maps file")]
ParsingError(
#[from]
#[serde(serialize_with = "serialize_proc_error")]
procfs_core::ProcError,
),
}

fn is_mapping_a_path(pathname: Option<&OsStr>) -> bool {
Expand All @@ -117,6 +128,20 @@ fn sanitize_path(pathname: OsString) -> OsString {
}

impl MappingInfo {
/// Get the mappings for the given process.
pub fn for_pid(pid: i32, linux_gate_loc: Option<AuxvType>) -> Result<Vec<Self>> {
let maps_path = format!("/proc/{}/maps", pid);
let maps_file = File::open(&maps_path).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
MapsReaderError::MappingFileMissing { pid }
} else {
e.into()
}
})?;
let maps = MemoryMaps::from_read(maps_file)?;
Self::aggregate(maps, linux_gate_loc)
}

/// Return whether the `name` field is a path (contains a `/`).
pub fn name_is_path(&self) -> bool {
is_mapping_a_path(self.name.as_deref())
Expand Down
6 changes: 0 additions & 6 deletions src/linux/minidump_writer/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,6 @@ pub enum InitError {
EnumerateThreadsErrors(#[source] ErrorList<InitError>),
#[error("Failed to enumerate threads")]
EnumerateThreadsFailed(#[source] Box<InitError>),
#[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")]
Expand Down
39 changes: 28 additions & 11 deletions src/linux/minidump_writer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ use {
dso_debug,
dumper_cpu_info::CpuInfoError,
maps_reader::{MappingInfo, MappingList, MapsReaderError},
mem_reader::CopyFromProcessError,
module_reader,
process_reader::{CopyFromProcessError, ProcessReader},
serializers::*,
thread_info::{ThreadInfo, ThreadInfoError},
},
Expand All @@ -18,6 +17,7 @@ use {
Buffer, MemoryArrayWriter, MemoryWriter, MemoryWriterError, write_string_to_location,
},
minidump_format::*,
module_reader,
serializers::*,
},
error_graph::{ErrorList, WriteErrorList},
Expand Down Expand Up @@ -720,14 +720,7 @@ impl MinidumpWriter {
// 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.process_id);
let maps_file =
std::fs::File::open(&maps_path).map_err(|e| InitError::IOError(maps_path, e))?;

let maps = procfs_core::process::MemoryMaps::from_read(maps_file)
.map_err(InitError::ReadProcessMapFileFailed)?;

self.mappings = MappingInfo::aggregate(maps, self.auxv.get_linux_gate_address())
self.mappings = MappingInfo::for_pid(self.process_id, self.auxv.get_linux_gate_address())
.map_err(InitError::AggregateMappingsFailed)?;

// Although the initial executable is usually the first mapping, it's not
Expand Down Expand Up @@ -953,10 +946,34 @@ impl MinidumpWriter {
mapping: &MappingInfo,
pid: Pid,
) -> Result<T, WriterError> {
let reader = ProcessReader::new(pid);
Ok(T::read_from_module(
module_reader::ProcessReader::new(pid, mapping.start_address).into(),
module_reader::ModuleMemory::from_process(&reader, mapping.start_address),
)?)
}

/// Copies a block of bytes from the target process, returning the heap
/// allocated copy
#[inline]
pub fn copy_from_process(
pid: Pid,
src: usize,
length: usize,
) -> Result<Vec<u8>, 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 mem = ProcessReader::new(pid);
mem.read_to_vec(src, length)
}
}

impl Drop for MinidumpWriter {
Expand Down
5 changes: 3 additions & 2 deletions src/linux/minidump_writer/thread_list_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ impl MinidumpWriter {
// 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();
if let Some(crash_context) = &self.crash_context
&& thread.thread_id == self.blamed_thread as u32
{
let instruction_ptr = crash_context.get_instruction_pointer();
let stack_pointer = crash_context.get_stack_pointer();
self.fill_thread_stack(
Expand Down
2 changes: 1 addition & 1 deletion src/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ pub mod crash_context;
mod dso_debug;
mod dumper_cpu_info;
pub mod maps_reader;
pub mod mem_reader;
pub mod minidump_writer;
pub mod module_reader;
pub mod process_reader;
mod serializers;
pub mod thread_info;

Expand Down
Loading
Loading