use self::{error::*, wrapper::*};
use core::{
cell::OnceCell,
ffi::{CStr, c_long, c_void},
fmt::Write,
mem::size_of,
ptr,
};
pub use error::{ReadError, ReadExactError};
mod error;
mod wrapper;
const PTRACE_PEEKDATA_LEN: usize = size_of::<c_long>();
#[derive(Debug)]
pub struct ProcessReader {
pid: libc::pid_t,
style: OnceCell<Style>,
}
impl ProcessReader {
pub fn new(pid: libc::pid_t) -> Self {
Self::assert_valid_pid(pid);
Self {
pid,
style: OnceCell::new(),
}
}
pub fn for_virtual_mem(pid: libc::pid_t) -> Self {
Self::assert_valid_pid(pid);
Self {
pid,
style: OnceCell::from(Style::VirtualMem),
}
}
pub fn for_file(pid: libc::pid_t) -> Result<Self, ReadError> {
Self::assert_valid_pid(pid);
let file = Self::open_mem_file_for_pid(pid)
.map_err(FileStrategyError::Open)
.map_err(|e| ReadError(ReadErrorInner::FileStrategy(e)))?;
Ok(Self {
pid,
style: OnceCell::from(Style::File(file)),
})
}
pub fn for_ptrace(pid: libc::pid_t) -> Self {
Self::assert_valid_pid(pid);
Self {
pid,
style: OnceCell::from(Style::Ptrace),
}
}
pub fn read_exact_at(
&self,
mut address: usize,
mut buf: &mut [u8],
) -> Result<(), ReadExactError> {
if buf.is_empty() {
return Ok(());
}
loop {
let bytes_read = self.read_at(address, buf).map_err(ReadExactError::Read)?;
if bytes_read == 0 {
return Err(ReadExactError::UnexpectedEof);
}
if bytes_read == buf.len() {
return Ok(());
}
address = address
.checked_add(bytes_read)
.expect("requested read will wrap past end of address space");
buf = &mut buf[bytes_read..];
}
}
pub fn read_at(&self, address: usize, buf: &mut [u8]) -> Result<usize, ReadError> {
if buf.is_empty() {
return Ok(0);
}
if let Some(style) = self.style.get() {
return match style {
Style::VirtualMem => {
Self::vmem(self.pid, address, buf).map_err(ReadErrorInner::VirtualMemStrategy)
}
Style::File(file) => Self::file(file, address, buf)
.map(|()| buf.len())
.map_err(ReadErrorInner::FileStrategy),
Style::Ptrace => Self::ptrace(self.pid, address, buf)
.map(|()| buf.len())
.map_err(ReadErrorInner::PtraceStrategy),
}
.map_err(ReadError);
}
const DOUBLE_INIT_MSG: &str = "somehow ProcessReader::style initialized twice";
let vmem_err = match Self::vmem(self.pid, address, buf) {
Ok(len) => {
self.style.set(Style::VirtualMem).expect(DOUBLE_INIT_MSG);
return Ok(len);
}
Err(e) => e,
};
let file_err = match Self::open_mem_file_for_pid(self.pid) {
Ok(file) => match Self::file(&file, address, buf) {
Ok(()) => {
self.style.set(Style::File(file)).expect(DOUBLE_INIT_MSG);
return Ok(buf.len());
}
Err(e) => e,
},
Err(e) => FileStrategyError::Open(e),
};
let ptrace_err = match Self::ptrace(self.pid, address, buf) {
Ok(()) => {
self.style.set(Style::Ptrace).expect(DOUBLE_INIT_MSG);
return Ok(buf.len());
}
Err(e) => e,
};
Err(ReadError(ReadErrorInner::AllStrategies {
vmem_err,
file_err,
ptrace_err,
}))
}
fn assert_valid_pid(pid: libc::pid_t) {
assert!(pid >= 0, "pid must be a non-negative process ID");
}
fn open_mem_file_for_pid(pid: libc::pid_t) -> Result<File, OpenFailed> {
const MAX_PROC_MEM_LEN: usize = 6 + 10 + 4 + 1;
let mut path_c_str = [0u8; MAX_PROC_MEM_LEN];
let path_c_str = {
let mut writer = ByteSliceWriter::new(&mut path_c_str);
write!(writer, "/proc/{pid}/mem\0").unwrap();
CStr::from_bytes_until_nul(&path_c_str).unwrap()
};
File::open(path_c_str)
}
fn vmem(
pid: libc::pid_t,
address: usize,
buf: &mut [u8],
) -> Result<usize, ProcessVmReadvFailed> {
let mut local_iov = [libc::iovec {
iov_base: buf.as_mut_ptr().cast(),
iov_len: buf.len(),
}];
let mut remote_iov = [libc::iovec {
iov_base: address as *mut _,
iov_len: buf.len(),
}];
let rv = unsafe {
libc::process_vm_readv(
pid,
local_iov.as_mut_ptr(),
local_iov.len().try_into().unwrap(),
remote_iov.as_mut_ptr(),
remote_iov.len().try_into().unwrap(),
0,
)
};
if rv == -1 {
return Err(ProcessVmReadvFailed(errno()));
}
let bytes_read = usize::try_from(rv).unwrap();
Ok(bytes_read)
}
fn file(fd: &File, position: usize, buf: &mut [u8]) -> Result<(), FileStrategyError> {
fd.read_exact_at(position, buf)
.map_err(FileStrategyError::Read)
}
fn ptrace(pid: libc::pid_t, address: usize, buf: &mut [u8]) -> Result<(), PtraceError> {
let mut reader = PtraceReader::new(pid, address)?;
reader.read_exact(buf)
}
}
struct PtraceReader {
pid: libc::pid_t,
position: usize,
buffer: [u8; PTRACE_PEEKDATA_LEN],
buffer_pos: usize,
}
impl PtraceReader {
fn new(pid: libc::pid_t, position: usize) -> Result<Self, PtraceError> {
let requested_position = position;
let position = requested_position / PTRACE_PEEKDATA_LEN * PTRACE_PEEKDATA_LEN;
let buffer = Self::ptrace_peekdata(pid, position)?;
let buffer_pos = requested_position - position;
Ok(Self {
pid,
position,
buffer,
buffer_pos,
})
}
fn read_exact(&mut self, mut buf: &mut [u8]) -> Result<(), PtraceError> {
while !buf.is_empty() {
let bytes_read = self.read(buf)?;
assert!(bytes_read > 0);
buf = &mut buf[bytes_read..];
}
Ok(())
}
fn read(&mut self, buf: &mut [u8]) -> Result<usize, PtraceError> {
let buffered_bytes = self.fill_buf()?;
let bytes_to_read = usize::min(buf.len(), buffered_bytes.len());
buf[0..bytes_to_read].copy_from_slice(&buffered_bytes[0..bytes_to_read]);
self.buffer_pos += bytes_to_read;
Ok(bytes_to_read)
}
fn fill_buf(&mut self) -> Result<&[u8], PtraceError> {
if self.buffer_pos == PTRACE_PEEKDATA_LEN {
self.position = self
.position
.checked_add(PTRACE_PEEKDATA_LEN)
.ok_or(PtraceError::AddressOverflow)?;
self.buffer = Self::ptrace_peekdata(self.pid, self.position)?;
self.buffer_pos = 0;
}
Ok(&self.buffer[self.buffer_pos..PTRACE_PEEKDATA_LEN])
}
fn ptrace_peekdata(
pid: libc::pid_t,
position: usize,
) -> Result<[u8; PTRACE_PEEKDATA_LEN], PtraceError> {
set_errno(0);
let rv = unsafe {
let addr: *mut c_void = position as *mut _;
let data: *mut c_void = ptr::null_mut();
libc::ptrace(libc::PTRACE_PEEKDATA, pid, addr, data)
};
let err = errno();
if rv == -1 && err != 0 {
return Err(PtraceError::Syscall {
errno: err,
position,
});
}
Ok(rv.to_ne_bytes())
}
}
#[derive(Debug)]
enum Style {
VirtualMem,
File(File),
Ptrace,
}