Skip to content

Commit

Permalink
Merge pull request #127 from marti4d/improve_auxv
Browse files Browse the repository at this point in the history
Setup infrastructure to allow the auxiliary vector information to be passed from the crashed process.
  • Loading branch information
gabrielesvelto authored Aug 6, 2024
2 parents 6bd2eb5 + 282ee6f commit 6f93cb2
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 109 deletions.
22 changes: 10 additions & 12 deletions src/bin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ pub type Result<T> = std::result::Result<T, Error>;
mod linux {
use super::*;
use minidump_writer::{
minidump_writer::STOP_TIMEOUT,
module_reader,
ptrace_dumper::{PtraceDumper, AT_SYSINFO_EHDR},
minidump_writer::STOP_TIMEOUT, module_reader, ptrace_dumper::PtraceDumper,
LINUX_GATE_LIBRARY_NAME,
};
use nix::{
Expand All @@ -30,13 +28,13 @@ mod linux {

fn test_setup() -> Result<()> {
let ppid = getppid();
PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT)?;
PtraceDumper::new(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)?;
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
test!(!dumper.threads.is_empty(), "No threads")?;
test!(
dumper
Expand All @@ -52,7 +50,7 @@ mod linux {

fn test_copy_from_process(stack_var: usize, heap_var: usize) -> Result<()> {
let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?;
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;
let stack_res = PtraceDumper::copy_from_process(ppid, stack_var as *mut libc::c_void, 1)?;

Expand All @@ -74,7 +72,7 @@ mod linux {

fn test_find_mappings(addr1: usize, addr2: usize) -> Result<()> {
let ppid = getppid();
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT)?;
let dumper = PtraceDumper::new(ppid.as_raw(), STOP_TIMEOUT, Default::default())?;
dumper
.find_mapping(addr1)
.ok_or("No mapping for addr1 found")?;
Expand All @@ -91,7 +89,7 @@ mod linux {
let ppid = getppid().as_raw();
let exe_link = format!("/proc/{}/exe", ppid);
let exe_name = std::fs::read_link(exe_link)?.into_os_string();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?;
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT, Default::default())?;
dumper.suspend_threads()?;
let mut found_exe = None;
for (idx, mapping) in dumper.mappings.iter().enumerate() {
Expand All @@ -110,7 +108,7 @@ mod linux {

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)?;
let dumper = PtraceDumper::new(getppid().as_raw(), STOP_TIMEOUT, Default::default())?;
let mut mapping_count = 0;
for map in &dumper.mappings {
if map
Expand All @@ -132,7 +130,7 @@ mod linux {

fn test_linux_gate_mapping_id() -> Result<()> {
let ppid = getppid().as_raw();
let mut dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?;
let mut dumper = PtraceDumper::new(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()) {
Expand All @@ -152,8 +150,8 @@ mod linux {

fn test_mappings_include_linux_gate() -> Result<()> {
let ppid = getppid().as_raw();
let dumper = PtraceDumper::new(ppid, STOP_TIMEOUT)?;
let linux_gate_loc = dumper.auxv[&AT_SYSINFO_EHDR];
let dumper = PtraceDumper::new(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;
for mapping in &dumper.mappings {
Expand Down
2 changes: 1 addition & 1 deletion src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#[cfg(target_os = "android")]
mod android;
pub mod app_memory;
pub(crate) mod auxv_reader;
pub(crate) mod auxv;
pub mod crash_context;
mod dso_debug;
mod dumper_cpu_info;
Expand Down
137 changes: 137 additions & 0 deletions src/linux/auxv/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
pub use reader::ProcfsAuxvIter;
use {
crate::linux::thread_info::Pid,
std::{fs::File, io::BufReader},
thiserror::Error,
};

mod reader;

/// The type used in auxv keys and values.
#[cfg(target_pointer_width = "32")]
pub type AuxvType = u32;
/// The type used in auxv keys and values.
#[cfg(target_pointer_width = "64")]
pub type AuxvType = u64;

#[cfg(target_os = "android")]
mod consts {
use super::AuxvType;
pub const AT_PHDR: AuxvType = 3;
pub const AT_PHNUM: AuxvType = 5;
pub const AT_ENTRY: AuxvType = 9;
pub const AT_SYSINFO_EHDR: AuxvType = 33;
}
#[cfg(not(target_os = "android"))]
mod consts {
use super::AuxvType;
pub const AT_PHDR: AuxvType = libc::AT_PHDR;
pub const AT_PHNUM: AuxvType = libc::AT_PHNUM;
pub const AT_ENTRY: AuxvType = libc::AT_ENTRY;
pub const AT_SYSINFO_EHDR: AuxvType = libc::AT_SYSINFO_EHDR;
}

/// An auxv key-value pair.
#[derive(Debug, PartialEq, Eq)]
pub struct AuxvPair {
pub key: AuxvType,
pub value: AuxvType,
}

/// Auxv info that can be passed from crashing process
///
/// Since `/proc/{pid}/auxv` can sometimes be inaccessible, the calling process should prefer to transfer this
/// information directly using the Linux `getauxval()` call (if possible).
///
/// Any field that is set to `0` will be considered unset. In that case, minidump-writer might try other techniques
/// to obtain it (like reading `/proc/{pid}/auxv`).
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct DirectAuxvDumpInfo {
/// The value of `getauxval(AT_PHNUM)`
pub program_header_count: AuxvType,
/// The value of `getauxval(AT_PHDR)`
pub program_header_address: AuxvType,
/// The value of `getauxval(AT_SYSINFO_EHDR)`
pub linux_gate_address: AuxvType,
/// The value of `getauxval(AT_ENTRY)`
pub entry_address: AuxvType,
}

impl From<DirectAuxvDumpInfo> for AuxvDumpInfo {
fn from(f: DirectAuxvDumpInfo) -> AuxvDumpInfo {
AuxvDumpInfo {
program_header_count: (f.program_header_count > 0).then_some(f.program_header_count),
program_header_address: (f.program_header_address > 0)
.then_some(f.program_header_address),
linux_gate_address: (f.linux_gate_address > 0).then_some(f.linux_gate_address),
entry_address: (f.entry_address > 0).then_some(f.entry_address),
}
}
}

#[derive(Debug, Default)]
pub struct AuxvDumpInfo {
program_header_count: Option<AuxvType>,
program_header_address: Option<AuxvType>,
linux_gate_address: Option<AuxvType>,
entry_address: Option<AuxvType>,
}

impl AuxvDumpInfo {
pub fn try_filling_missing_info(&mut self, pid: Pid) -> 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))?;

for AuxvPair { key, value } in
ProcfsAuxvIter::new(BufReader::new(auxv_file)).filter_map(Result::ok)
{
let dest_field = match key {
consts::AT_PHNUM => &mut self.program_header_count,
consts::AT_PHDR => &mut self.program_header_address,
consts::AT_SYSINFO_EHDR => &mut self.linux_gate_address,
consts::AT_ENTRY => &mut self.entry_address,
_ => continue,
};
if dest_field.is_none() {
*dest_field = Some(value);
}
}

Ok(())
}
pub fn get_program_header_count(&self) -> Option<AuxvType> {
self.program_header_count
}
pub fn get_program_header_address(&self) -> Option<AuxvType> {
self.program_header_address
}
pub fn get_linux_gate_address(&self) -> Option<AuxvType> {
self.linux_gate_address
}
pub fn get_entry_address(&self) -> Option<AuxvType> {
self.entry_address
}
pub fn is_complete(&self) -> bool {
self.program_header_count.is_some()
&& self.program_header_address.is_some()
&& self.linux_gate_address.is_some()
&& self.entry_address.is_some()
}
}

#[derive(Debug, Error)]
pub enum AuxvError {
#[error("Failed to open file {0}")]
OpenError(String, #[source] std::io::Error),
#[error("No auxv entry found for PID {0}")]
NoAuxvEntryFound(Pid),
#[error("Invalid auxv format (should not hit EOF before AT_NULL)")]
InvalidFormat,
#[error("IO Error")]
IOError(#[from] std::io::Error),
}
31 changes: 10 additions & 21 deletions src/linux/auxv_reader.rs → src/linux/auxv/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,15 @@
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be in…substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
use crate::errors::AuxvReaderError;
use byteorder::{NativeEndian, ReadBytesExt};
use std::fs::File;
use std::io::{BufReader, Read};

pub type Result<T> = std::result::Result<T, AuxvReaderError>;

/// The type used in auxv keys and values.
#[cfg(target_pointer_width = "32")]
pub type AuxvType = u32;
/// The type used in auxv keys and values.
#[cfg(target_pointer_width = "64")]
pub type AuxvType = u64;

/// An auxv key-value pair.
#[derive(Debug, PartialEq, Eq)]
pub struct AuxvPair {
pub key: AuxvType,
pub value: AuxvType,
}
use {
super::{AuxvError, AuxvPair, AuxvType},
byteorder::{NativeEndian, ReadBytesExt},
std::{
fs::File,
io::{BufReader, Read},
},
};

/// An iterator across auxv pairs from procfs.
pub struct ProcfsAuxvIter {
Expand All @@ -48,7 +37,7 @@ impl ProcfsAuxvIter {
}

impl Iterator for ProcfsAuxvIter {
type Item = Result<AuxvPair>;
type Item = Result<AuxvPair, AuxvError>;
fn next(&mut self) -> Option<Self::Item> {
if !self.keep_going {
return None;
Expand All @@ -65,7 +54,7 @@ impl Iterator for ProcfsAuxvIter {
Ok(n) => {
if n == 0 {
// should not hit EOF before AT_NULL
return Some(Err(AuxvReaderError::InvalidFormat));
return Some(Err(AuxvError::InvalidFormat));
}

read_bytes += n;
Expand Down
28 changes: 7 additions & 21 deletions src/linux/dso_debug.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::{
linux::{auxv_reader::AuxvType, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper},
linux::{auxv::AuxvDumpInfo, errors::SectionDsoDebugError, ptrace_dumper::PtraceDumper},
mem_writer::{write_string_to_location, Buffer, MemoryArrayWriter, MemoryWriter},
minidump_format::*,
};
use std::collections::HashMap;

type Result<T> = std::result::Result<T, SectionDsoDebugError>;

Expand Down Expand Up @@ -77,26 +76,13 @@ pub struct RDebug {
pub fn write_dso_debug_stream(
buffer: &mut Buffer,
blamed_thread: i32,
auxv: &HashMap<AuxvType, AuxvType>,
auxv: &AuxvDumpInfo,
) -> Result<MDRawDirectory> {
let at_phnum;
let at_phdr;
#[cfg(any(target_arch = "arm", all(target_os = "android", target_arch = "x86")))]
{
at_phdr = 3;
at_phnum = 5;
}
#[cfg(not(any(target_arch = "arm", all(target_os = "android", target_arch = "x86"))))]
{
at_phdr = libc::AT_PHDR;
at_phnum = libc::AT_PHNUM;
}
let phnum_max = *auxv
.get(&at_phnum)
.ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))?
as usize;
let phdr = *auxv
.get(&at_phdr)
let phnum_max =
auxv.get_program_header_count()
.ok_or(SectionDsoDebugError::CouldNotFind("AT_PHNUM in auxv"))? as usize;
let phdr = auxv
.get_program_header_address()
.ok_or(SectionDsoDebugError::CouldNotFind("AT_PHDR in auxv"))? as usize;

let ph = PtraceDumper::copy_from_process(
Expand Down
13 changes: 3 additions & 10 deletions src/linux/errors.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::auxv::AuxvError;
use crate::dir_section::FileWriterError;
use crate::maps_reader::MappingInfo;
use crate::mem_writer::MemoryWriterError;
Expand All @@ -9,10 +10,10 @@ use thiserror::Error;

#[derive(Debug, Error)]
pub enum InitError {
#[error("failed to read auxv")]
ReadAuxvFailed(AuxvError),
#[error("IO error for file {0}")]
IOError(String, #[source] std::io::Error),
#[error("No auxv entry found for PID {0}")]
NoAuxvEntryFound(Pid),
#[error("crash thread does not reference principal mapping")]
PrincipalMappingNotReferenced,
#[error("Failed Android specific late init")]
Expand Down Expand Up @@ -47,14 +48,6 @@ pub enum MapsReaderError {
SymlinkError(std::path::PathBuf, std::path::PathBuf),
}

#[derive(Debug, Error)]
pub enum AuxvReaderError {
#[error("Invalid auxv format (should not hit EOF before AT_NULL)")]
InvalidFormat,
#[error("IO Error")]
IOError(#[from] std::io::Error),
}

#[derive(Debug, Error)]
pub enum CpuInfoError {
#[error("IO error for file /proc/cpuinfo")]
Expand Down
2 changes: 1 addition & 1 deletion src/linux/maps_reader.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::auxv_reader::AuxvType;
use crate::auxv::AuxvType;
use crate::errors::MapsReaderError;
use byteorder::{NativeEndian, ReadBytesExt};
use goblin::elf;
Expand Down
Loading

0 comments on commit 6f93cb2

Please sign in to comment.