Skip to content
Open
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
4 changes: 2 additions & 2 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ memmap2 = "0.9"

[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies]
nix = { version = "0.29", default-features = false, features = [
"fs",
"mman",
"process",
"ptrace",
Expand Down Expand Up @@ -67,3 +68,7 @@ dump_syms = { version = "2.2", default-features = false }
minidump-unwind = { version = "0.26", features = ["debuginfo"] }
similar-asserts = "1.6"
uuid = "1.12"

[features]
default = ["testing"]
testing = []
32 changes: 18 additions & 14 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ mod linux {
error_graph::ErrorList,
minidump_writer::{
minidump_writer::{MinidumpWriter, MinidumpWriterConfig},
module_reader, LINUX_GATE_LIBRARY_NAME,
module_reader,
process_inspection::{DirectInspector, ProcessInspector},
LINUX_GATE_LIBRARY_NAME,
},
nix::{
sys::mman::{mmap_anonymous, MapFlags, ProtFlags},
Expand Down Expand Up @@ -76,8 +78,6 @@ mod linux {
}

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

let ppid = getppid().as_raw();
let dumper = fail_on_soft_error!(
soft_errors,
Expand All @@ -90,13 +90,13 @@ 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 dyn ProcessInspector| -> Result<()> {
let mut val = [0u8; std::mem::size_of::<usize>()];
let read = reader.read(stack_var, &mut val)?;
let read = reader.read_memory(stack_var, &mut val)?;
assert_eq!(read, val.len());
test!(val == expected_stack, "stack var not correct");

let read = reader.read(heap_var, &mut val)?;
let read = reader.read_memory(heap_var, &mut val)?;
assert_eq!(read, val.len());
test!(val == expected_heap, "heap var not correct");

Expand All @@ -105,33 +105,35 @@ mod linux {

// virtual mem
{
let mut mr = MemReader::for_virtual_mem(ppid);
let mut mr = DirectInspector::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 = DirectInspector::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 = DirectInspector::for_ptrace(ppid);
validate(&mut mr)
.map_err(|err| format!("failed to validate memory for {mr:?}: {err}"))?;
}

let process_inspector = DirectInspector::new(ppid);

let stack_res =
MinidumpWriter::copy_from_process(ppid, stack_var, std::mem::size_of::<usize>())?;
process_inspector.read_memory_to_vec(stack_var, std::mem::size_of::<usize>())?;

test!(stack_res == expected_stack, "stack var not correct");

let heap_res =
MinidumpWriter::copy_from_process(ppid, heap_var, std::mem::size_of::<usize>())?;
process_inspector.read_memory_to_vec(heap_var, std::mem::size_of::<usize>())?;

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

Expand Down Expand Up @@ -215,7 +217,7 @@ mod linux {

fn test_linux_gate_mapping_id() -> Result<()> {
let ppid = getppid().as_raw();
let dumper = fail_on_soft_error!(
let mut dumper = fail_on_soft_error!(
soft_errors,
MinidumpWriterConfig::new(ppid, ppid).build_for_testing(&mut soft_errors)?
);
Expand All @@ -224,8 +226,10 @@ mod linux {
if mapping.name == Some(LINUX_GATE_LIBRARY_NAME.into()) {
found_linux_gate = true;

let module_reader::BuildId(id) =
MinidumpWriter::from_process_memory_for_mapping(&mapping, ppid)?;
let module_reader::BuildId(id) = MinidumpWriter::from_process_memory_for_mapping(
dumper.process_inspector.as_mut(),
&mapping,
)?;
test!(!id.is_empty(), "id-vec is empty");
test!(id.iter().any(|&x| x > 0), "all id elements are 0");
drop(dumper);
Expand Down
50 changes: 30 additions & 20 deletions src/linux/android.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use {
super::{
maps_reader::MappingInfo, mem_reader::CopyFromProcessError,
minidump_writer::MinidumpWriter, Pid,
maps_reader::MappingInfo,
process_inspection::{self, ProcessInspector},
},
goblin::elf,
};
Expand Down Expand Up @@ -31,7 +31,7 @@ type Result<T> = std::result::Result<T, AndroidError>;
#[derive(Debug, thiserror::Error, serde::Serialize)]
pub enum AndroidError {
#[error("Failed to copy memory from process")]
CopyFromProcessError(#[from] CopyFromProcessError),
CopyFromProcessError(#[from] process_inspection::Error),
#[error("Failed slice conversion")]
TryFromSliceError(
#[from]
Expand All @@ -48,11 +48,15 @@ struct DynVaddresses {
dyn_count: usize,
}

fn has_android_packed_relocations(pid: Pid, load_bias: usize, vaddrs: DynVaddresses) -> Result<()> {
fn has_android_packed_relocations(
process_inspector: &mut dyn ProcessInspector,
load_bias: usize,
vaddrs: DynVaddresses,
) -> Result<()> {
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 = MinidumpWriter::copy_from_process(pid, addr, SIZEOF_DYN)?;
let dyn_data = process_inspector.read_memory_to_vec(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 {
Expand All @@ -66,14 +70,18 @@ fn has_android_packed_relocations(pid: Pid, load_bias: usize, vaddrs: DynVaddres
Err(AndroidError::NoRelFound)
}

fn get_effective_load_bias(pid: Pid, ehdr: &elf_header::Header, address: usize) -> usize {
let ph = parse_loaded_elf_program_headers(pid, ehdr, address);
fn get_effective_load_bias(
process_inspector: &mut dyn ProcessInspector,
ehdr: &elf_header::Header,
address: usize,
) -> usize {
let ph = parse_loaded_elf_program_headers(process_inspector, ehdr, address);
// If |min_vaddr| is non-zero and we find Android packed relocation tags,
// return the effective load bias.

if ph.min_vaddr != 0 {
let load_bias = address - ph.min_vaddr;
if has_android_packed_relocations(pid, load_bias, ph).is_ok() {
if has_android_packed_relocations(process_inspector, load_bias, ph).is_ok() {
return load_bias;
}
}
Expand All @@ -83,7 +91,7 @@ fn get_effective_load_bias(pid: Pid, ehdr: &elf_header::Header, address: usize)
}

fn parse_loaded_elf_program_headers(
pid: Pid,
process_inspector: &mut dyn ProcessInspector,
ehdr: &elf_header::Header,
address: usize,
) -> DynVaddresses {
Expand All @@ -92,11 +100,9 @@ fn parse_loaded_elf_program_headers(
let mut dyn_vaddr = 0;
let mut dyn_count = 0;

let phdr_opt = MinidumpWriter::copy_from_process(
pid,
phdr_addr,
elf_header::SIZEOF_EHDR * ehdr.e_phnum as usize,
);
let phdr_opt = process_inspector
.read_memory_to_vec(phdr_addr, elf_header::SIZEOF_EHDR * ehdr.e_phnum as usize);

if let Ok(ph_data) = phdr_opt {
// TODO: The original C code doesn't have error-handling here at all.
// We silently ignore "not parsable" for now, but might bubble it up.
Expand All @@ -122,17 +128,20 @@ fn parse_loaded_elf_program_headers(
}
}

pub fn late_process_mappings(pid: Pid, mappings: &mut [MappingInfo]) -> Result<()> {
pub fn late_process_mappings(
process_inspector: &mut dyn ProcessInspector,
mappings: &mut [MappingInfo],
) -> Result<()> {
// Only consider exec mappings that indicate a file path was mapped, and
// where the ELF header indicates a mapped shared library.
for map in mappings
.iter_mut()
.filter(|m| m.is_executable() && m.name_is_path())
{
let ehdr_opt =
MinidumpWriter::copy_from_process(pid, map.start_address, elf_header::SIZEOF_EHDR)
.ok()
.and_then(|x| elf_header::Header::parse(&x).ok());
let ehdr_opt = process_inspector
.read_memory_to_vec(map.start_address, elf_header::SIZEOF_EHDR)
.ok()
.and_then(|x| elf_header::Header::parse(&x).ok());

if let Some(ehdr) = ehdr_opt {
if ehdr.e_type == elf_header::ET_DYN {
Expand All @@ -142,7 +151,8 @@ pub fn late_process_mappings(pid: Pid, mappings: &mut [MappingInfo]) -> Result<(
// the library does not contain Android packed relocations,
// GetEffectiveLoadBias() returns |start_addr| and the mapping entry
// is not changed.
let load_bias = get_effective_load_bias(pid, &ehdr, map.start_address);
let load_bias =
get_effective_load_bias(process_inspector, &ehdr, map.start_address);
map.size += map.start_address - load_bias;
map.start_address = load_bias;
}
Expand Down
17 changes: 11 additions & 6 deletions src/linux/auxv/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use {
self::reader::ProcfsAuxvIter,
super::Pid,
crate::serializers::*,
crate::{
linux::process_inspection::{self, ProcessInspector},
serializers::*,
},
error_graph::WriteErrorList,
failspot::failspot,
std::{fs::File, io::BufReader},
thiserror::Error,
};

Expand Down Expand Up @@ -84,17 +86,18 @@ pub struct AuxvDumpInfo {
impl AuxvDumpInfo {
pub fn try_filling_missing_info(
&mut self,
pid: Pid,
process_inspector: &mut dyn ProcessInspector,
mut soft_errors: impl WriteErrorList<AuxvError>,
) -> Result<(), AuxvError> {
if self.is_complete() {
return Ok(());
}

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

for pair_result in ProcfsAuxvIter::new(BufReader::new(auxv_file)) {
for pair_result in ProcfsAuxvIter::new(auxv_reader) {
let AuxvPair { key, value } = match pair_result {
Ok(pair) => pair,
Err(e) => {
Expand Down Expand Up @@ -147,6 +150,8 @@ pub enum AuxvError {
#[serde(serialize_with = "serialize_io_error")]
std::io::Error,
),
#[error("Failed to read /proc/<pid>/auxv file")]
ReadFile(#[source] process_inspection::Error),
#[error("No auxv entry found for PID {0}")]
NoAuxvEntryFound(Pid),
#[error("Invalid auxv format (should not hit EOF before AT_NULL)")]
Expand Down
15 changes: 6 additions & 9 deletions src/linux/auxv/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,19 @@
use {
super::{AuxvError, AuxvPair, AuxvType},
byteorder::{NativeEndian, ReadBytesExt},
std::{
fs::File,
io::{BufReader, Read},
},
std::io::Read,
};

/// An iterator across auxv pairs from procfs.
pub struct ProcfsAuxvIter {
pub struct ProcfsAuxvIter<R: Read> {
pair_size: usize,
buf: Vec<u8>,
input: BufReader<File>,
input: R,
keep_going: bool,
}

impl ProcfsAuxvIter {
pub fn new(input: BufReader<File>) -> Self {
impl<R: Read> ProcfsAuxvIter<R> {
pub fn new(input: R) -> Self {
let pair_size = 2 * std::mem::size_of::<AuxvType>();
let buf: Vec<u8> = Vec::with_capacity(pair_size);

Expand All @@ -36,7 +33,7 @@ impl ProcfsAuxvIter {
}
}

impl Iterator for ProcfsAuxvIter {
impl<R: Read> Iterator for ProcfsAuxvIter<R> {
type Item = Result<AuxvPair, AuxvError>;
fn next(&mut self) -> Option<Self::Item> {
if !self.keep_going {
Expand Down
Loading
Loading