use std::io::{self, Seek, Write};
use crate::prelude::ByteOrder;
pub trait WriteExt {
fn write_bytes(&self, address: usize, bytes: &[u8]) -> WriteExtResult<()>;
fn write_u8(
&self,
address: usize,
value: u8,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_u16(
&self,
address: usize,
value: u16,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_u32(
&self,
address: usize,
value: u32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
#[cfg(target_pointer_width = "64")]
fn write_u64(
&self,
address: usize,
value: u64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_i8(
&self,
address: usize,
value: i8,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_i16(
&self,
address: usize,
value: i16,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_i32(
&self,
address: usize,
value: i32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
#[cfg(target_pointer_width = "64")]
fn write_i64(
&self,
address: usize,
value: i64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
fn write_f32(
&self,
address: usize,
value: f32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
#[cfg(target_pointer_width = "64")]
fn write_f64(
&self,
address: usize,
value: f64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()>;
}
#[cfg(target_os = "linux")]
impl WriteExt for crate::process::Process {
fn write_bytes(&self, address: usize, bytes: &[u8]) -> WriteExtResult<()> {
use nix::{sys::ptrace, unistd::Pid};
ptrace::attach(Pid::from_raw(self.pid))?;
defer! {
let _ = ptrace::detach(Pid::from_raw(self.pid), None);
}
let mut file = self.mem_file(true)?;
file.seek(io::SeekFrom::Start(address as u64))?;
file.write_all(bytes)?;
ptrace::detach(Pid::from_raw(self.pid), None)?;
Ok(())
}
fn write_u8(
&self,
address: usize,
value: u8,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_u16(
&self,
address: usize,
value: u16,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_u32(
&self,
address: usize,
value: u32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
#[cfg(target_pointer_width = "64")]
fn write_u64(
&self,
address: usize,
value: u64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_i8(
&self,
address: usize,
value: i8,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_i16(
&self,
address: usize,
value: i16,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_i32(
&self,
address: usize,
value: i32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
#[cfg(target_pointer_width = "64")]
fn write_i64(
&self,
address: usize,
value: i64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
fn write_f32(
&self,
address: usize,
value: f32,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
#[cfg(target_pointer_width = "64")]
fn write_f64(
&self,
address: usize,
value: f64,
byte_order: Option<ByteOrder>,
) -> WriteExtResult<()> {
self.write_bytes(
address,
&(match byte_order {
Some(ByteOrder::BigEndian) => value.to_be_bytes(),
Some(ByteOrder::LittleEndian) => value.to_le_bytes(),
None => value.to_ne_bytes(),
}),
)
}
}
#[derive(Debug, Error)]
pub enum WriteExtError {
#[error("A *nix error occured. {0:?}")]
Nix(#[from] nix::Error),
#[error("An IO error occured. {0:?}")]
IO(#[from] io::Error),
}
pub type WriteExtResult<T> = Result<T, WriteExtError>;
#[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_write_u8(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_u8(address, 20, None)
.expect("Could not write onto u8val.");
let value: u8 = {
let response = reqwest::blocking::get(format!("{}/u8val", dummy_process.base_url))
.expect("Could not request u8val value.");
response
.text()
.expect("Could not decode the charset of u8val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, 20);
}
#[rstest]
fn test_write_u16(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_u16(address, 30, None)
.expect("Could not write onto u16val.");
let value: u16 = {
let response = reqwest::blocking::get(format!("{}/u16val", dummy_process.base_url))
.expect("Could not request u16val value.");
response
.text()
.expect("Could not decode the charset of u16val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, 30);
}
#[rstest]
fn test_write_u32(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_u32(address, 40, None)
.expect("Could not write onto u32val.");
let value: u32 = {
let response = reqwest::blocking::get(format!("{}/u32val", dummy_process.base_url))
.expect("Could not request u32val value.");
response
.text()
.expect("Could not decode the charset of u32val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, 40);
}
#[cfg(target_pointer_width = "64")]
#[rstest]
fn test_write_u64(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_u64(address, 50, None)
.expect("Could not write onto u64val.");
let value: u64 = {
let response = reqwest::blocking::get(format!("{}/u64val", dummy_process.base_url))
.expect("Could not request u64val value.");
response
.text()
.expect("Could not decode the charset of u64val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, 50);
}
#[rstest]
fn test_write_i8(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_i8(address, -10, None)
.expect("Could not write onto i8val.");
let value: i8 = {
let response = reqwest::blocking::get(format!("{}/i8val", dummy_process.base_url))
.expect("Could not request i8val value.");
response
.text()
.expect("Could not decode the charset of i8val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, -10);
}
#[rstest]
fn test_write_i16(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_i16(address, -20, None)
.expect("Could not write onto i16val.");
let value: i16 = {
let response = reqwest::blocking::get(format!("{}/i16val", dummy_process.base_url))
.expect("Could not request i16val value.");
response
.text()
.expect("Could not decode the charset of i16val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, -20);
}
#[rstest]
fn test_write_i32(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_i32(address, -30, None)
.expect("Could not write onto i32val.");
let value: i32 = {
let response = reqwest::blocking::get(format!("{}/i32val", dummy_process.base_url))
.expect("Could not request i32val value.");
response
.text()
.expect("Could not decode the charset of i32val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, -30);
}
#[cfg(target_pointer_width = "64")]
#[rstest]
fn test_write_i64(dummy_process: DummyProcess) {
let process = Process::try_new(dummy_process.pid).expect("Could not init Process.");
let 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.");
process
.write_i64(address, -40, None)
.expect("Could not write onto i64val.");
let value: i64 = {
let response = reqwest::blocking::get(format!("{}/i64val", dummy_process.base_url))
.expect("Could not request i64val value.");
response
.text()
.expect("Could not decode the charset of i64val value.")
.parse()
}
.expect("Could not parse value.");
assert_eq!(value, -40);
}
}