use std::{
fs,
io::{self, Read, Seek},
path,
};
use nix::{sys::ptrace, unistd::Pid};
use crate::prelude::ByteOrder;
pub trait ReadExt {
fn read_u8(&self, address: usize) -> ReadExtResult<u8>;
fn read_u16(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u16>;
fn read_u32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u32>;
#[cfg(target_pointer_width = "64")]
fn read_u64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u64>;
fn read_i8(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i8>;
fn read_i16(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i16>;
fn read_i32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i32>;
#[cfg(target_pointer_width = "64")]
fn read_i64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i64>;
fn read_f32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<f32>;
#[cfg(target_pointer_width = "64")]
fn read_f64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<f64>;
}
#[cfg(target_os = "linux")]
impl ReadExt for crate::prelude::Process {
fn read_u8(&self, address: usize) -> ReadExtResult<u8> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 1];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(buffer[0])
}
fn read_u16(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u16> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 2];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => u16::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => u16::from_le_bytes(buffer),
None => u16::from_ne_bytes(buffer),
})
}
fn read_u32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u32> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 4];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => u32::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => u32::from_le_bytes(buffer),
None => u32::from_ne_bytes(buffer),
})
}
#[cfg(target_pointer_width = "64")]
fn read_u64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<u64> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 8];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => u64::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => u64::from_le_bytes(buffer),
None => u64::from_ne_bytes(buffer),
})
}
fn read_i8(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i8> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 1];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => i8::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => i8::from_le_bytes(buffer),
None => i8::from_ne_bytes(buffer),
})
}
fn read_i16(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i16> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 2];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => i16::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => i16::from_le_bytes(buffer),
None => i16::from_ne_bytes(buffer),
})
}
fn read_i32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i32> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 4];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => i32::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => i32::from_le_bytes(buffer),
None => i32::from_ne_bytes(buffer),
})
}
#[cfg(target_pointer_width = "64")]
fn read_i64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<i64> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 8];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => i64::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => i64::from_le_bytes(buffer),
None => i64::from_ne_bytes(buffer),
})
}
fn read_f32(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<f32> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 4];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => f32::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => f32::from_le_bytes(buffer),
None => f32::from_ne_bytes(buffer),
})
}
#[cfg(target_pointer_width = "64")]
fn read_f64(&self, address: usize, byte_order: Option<ByteOrder>) -> ReadExtResult<f64> {
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(false)?;
file.seek(io::SeekFrom::Start(address as u64))?;
let mut buffer = [0u8; 8];
file.read_exact(&mut buffer)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(match byte_order {
Some(ByteOrder::BigEndian) => f64::from_be_bytes(buffer),
Some(ByteOrder::LittleEndian) => f64::from_le_bytes(buffer),
None => f64::from_ne_bytes(buffer),
})
}
}
#[derive(Debug, Error)]
pub enum ReadExtError {
#[error("A *nix error occured. {0} {0:?}")]
Nix(nix::Error),
#[error("An IO error occured. {0} {0:?}")]
IO(#[from] std::io::Error),
}
impl From<nix::Error> for ReadExtError {
fn from(value: nix::Error) -> Self {
Self::Nix(value)
}
}
pub type ReadExtResult<T> = Result<T, ReadExtError>;
#[cfg(target_os = "linux")]
#[cfg(test)]
mod tests {
use std::{env, io::BufRead, process};
use crate::process::Process;
use super::*;
struct DummyProcess {
child: process::Child,
pub pid: i32,
pub base_url: String,
}
impl Drop for DummyProcess {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
#[fixture]
fn dummy_process() -> DummyProcess {
let cwd = env::current_dir().expect("Could not get the current working directory.");
let dummy_process_dir = cwd.join("dummy_process");
let build_status = process::Command::new("cargo")
.arg("build")
.current_dir(&dummy_process_dir)
.status()
.expect("Failed to run dummy_process compilation.");
assert!(build_status.success(), "Failed to build dummy_process");
let bin_path = dummy_process_dir.join("target/debug/dummy_process");
let mut child = process::Command::new(bin_path)
.stdout(process::Stdio::piped())
.spawn()
.expect("Could not spawn dummy_process.");
let port = {
let stdout = child.stdout.as_mut().expect("Failed to get stdout.");
let reader = io::BufReader::new(stdout);
let port_line = reader
.lines()
.next()
.expect("Port line does not exist.")
.expect("Could not read port line of stdout.");
port_line
.trim()
.split(":")
.last()
.expect("Could not read port part from stdout.")
.parse::<u16>()
.expect("Could not parse port from stdout.")
};
let base_url = format!("http://127.0.0.1:{port}");
let pid = {
let body = reqwest::blocking::get(format!("{base_url}/pid"))
.expect("Could not request PID of dummy process.");
body.text()
.expect("Could not decode the charset of PID of dummy process.")
.parse::<i32>()
.expect("Could not parse PID of dummy process")
};
DummyProcess {
child,
pid,
base_url,
}
}
#[rstest]
fn test_read_u8(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/u8val/address", dummy_process.base_url))
.expect("Could not request u8val address.");
response
.text()
.expect("Could not decode the charset of u8val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_u8(target_address)
.expect("Could not read the value of u8 from the address.");
assert_eq!(value, 2);
}
#[rstest]
fn test_read_u16(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/u16val/address", dummy_process.base_url))
.expect("Could not request u16val address.");
response
.text()
.expect("Could not decode the charset of u16val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_u16(target_address, None)
.expect("Could not read the value of u16 from the address.");
assert_eq!(value, 3);
}
#[rstest]
fn test_read_u32(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/u32val/address", dummy_process.base_url))
.expect("Could not request u32val address.");
response
.text()
.expect("Could not decode the charset of u32val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_u32(target_address, None)
.expect("Could not read the value of u32 from the address.");
assert_eq!(value, 4);
}
#[cfg(target_pointer_width = "64")]
#[rstest]
fn test_read_u64(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/u64val/address", dummy_process.base_url))
.expect("Could not request u64val address.");
response
.text()
.expect("Could not decode the charset of u64val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_u64(target_address, None)
.expect("Could not read the value of u64 from the address.");
assert_eq!(value, 5);
}
#[rstest]
fn test_read_i8(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/i8val/address", dummy_process.base_url))
.expect("Could not request i8val address.");
response
.text()
.expect("Could not decode the charset of i8val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_i8(target_address, None)
.expect("Could not read the value of i8 from the address.");
assert_eq!(value, -1);
}
#[rstest]
fn test_read_i16(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/i16val/address", dummy_process.base_url))
.expect("Could not request i16val address.");
response
.text()
.expect("Could not decode the charset of i16val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_i16(target_address, None)
.expect("Could not read the value of i16 from the address.");
assert_eq!(value, -2);
}
#[rstest]
fn test_read_i32(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/i32val/address", dummy_process.base_url))
.expect("Could not request i32val address.");
response
.text()
.expect("Could not decode the charset of i32val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_i32(target_address, None)
.expect("Could not read the value of i32 from the address.");
assert_eq!(value, -3);
}
#[cfg(target_pointer_width = "64")]
#[rstest]
fn test_read_i64(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/i64val/address", dummy_process.base_url))
.expect("Could not request i64val address.");
response
.text()
.expect("Could not decode the charset of i64val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_i64(target_address, None)
.expect("Could not read the value of i64 from the address.");
assert_eq!(value, -4);
}
#[rstest]
fn test_read_f32(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/f32val/address", dummy_process.base_url))
.expect("Could not request f32val address.");
response
.text()
.expect("Could not decode the charset of f32val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_f32(target_address, None)
.expect("Could not read the value of f32 from the address.");
assert_eq!(value, 1.5);
}
#[cfg(target_pointer_width = "64")]
#[rstest]
fn test_read_f64(dummy_process: DummyProcess) {
let process =
Process::try_new(dummy_process.pid).expect("Could not get dummy process with id.");
let target_address: usize = {
let response =
reqwest::blocking::get(format!("{}/f64val/address", dummy_process.base_url))
.expect("Could not request f64val address.");
response
.text()
.expect("Could not decode the charset of f64val address.")
.parse()
}
.expect("Could not parse address usize.");
let value = process
.read_f64(target_address, None)
.expect("Could not read the value of f64 from the address.");
assert_eq!(value, 2.5);
}
}