diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 90c0356..d096f32 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,5 +14,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: taiki-e/install-action@v2 + env: + CARGO_TERM_COLOR: always + with: + tool: nextest - name: test - run: cargo test + uses: actions-rs/cargo@v1 + with: + command: nextest + args: run --test-threads 1 --retries 10 diff --git a/Cargo.lock b/Cargo.lock index ef8493c..2f2d6e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1055,6 +1055,7 @@ dependencies = [ "nix", "reqwest", "rstest", + "scopeguard", "thiserror 2.0.18", ] @@ -1076,6 +1077,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "3.5.1" diff --git a/Cargo.toml b/Cargo.toml index 71fa36e..98e0ef8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] nix = { version = "0.31.1", features = ["ptrace"] } +scopeguard = "1.2.0" thiserror = "2.0.18" [dev-dependencies] diff --git a/dummy_process/src/main.rs b/dummy_process/src/main.rs index 83a63c6..e939e91 100644 --- a/dummy_process/src/main.rs +++ b/dummy_process/src/main.rs @@ -10,6 +10,8 @@ static I8VAL: atomic::AtomicI8 = atomic::AtomicI8::new(-1); static I16VAL: atomic::AtomicI16 = atomic::AtomicI16::new(-2); static I32VAL: atomic::AtomicI32 = atomic::AtomicI32::new(-3); static I64VAL: atomic::AtomicI64 = atomic::AtomicI64::new(-4); +static F32VAL: f32 = 1.5; +static F64VAL: f64 = 2.5; #[tokio::main] async fn main() { @@ -135,6 +137,26 @@ async fn main() { "OK" }); + let f32val_endpoint = warp::path!("f32val").map(|| format!("{}", F32VAL)); + let f32val_address_endpoint = warp::path!("f32val" / "address").map(|| { + let hex_val = format!("{:?}", ptr::addr_of!(F32VAL)); + let cleaned = hex_val.trim_start_matches("0x"); + format!( + "{}", + usize::from_str_radix(cleaned, 16).expect("Failed to parse address hex value.") + ) + }); + + let f64val_endpoint = warp::path!("f64val").map(|| format!("{}", F64VAL)); + let f64val_address_endpoint = warp::path!("f64val" / "address").map(|| { + let hex_val = format!("{:?}", ptr::addr_of!(F64VAL)); + let cleaned = hex_val.trim_start_matches("0x"); + format!( + "{}", + usize::from_str_radix(cleaned, 16).expect("Failed to parse address hex value.") + ) + }); + let routes = warp::get().and( pid_endpoint .or(u8val_endpoint) @@ -160,7 +182,11 @@ async fn main() { .or(i32val_increment_endpoint) .or(i64val_endpoint) .or(i64val_address_endpoint) - .or(i64val_increment_endpoint), + .or(i64val_increment_endpoint) + .or(f32val_endpoint) + .or(f32val_address_endpoint) + .or(f64val_endpoint) + .or(f64val_address_endpoint), ); let port = rand::random_range(1000..u16::MAX); diff --git a/src/byte_order.rs b/src/byte_order.rs new file mode 100644 index 0000000..4ad2615 --- /dev/null +++ b/src/byte_order.rs @@ -0,0 +1,11 @@ +// Copyright (c) 2026 Eray Erdin +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/// The byte order of the data. Default value is the native endianness of the system. +pub enum ByteOrder { + BigEndian, + LittleEndian, +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs index 210446c..76def6c 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -5,4 +5,5 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. mod mapping; +pub mod read; pub mod search; diff --git a/src/ext/read.rs b/src/ext/read.rs new file mode 100644 index 0000000..6898164 --- /dev/null +++ b/src/ext/read.rs @@ -0,0 +1,547 @@ +// Copyright (c) 2026 Eray Erdin +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +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; + fn read_u16(&self, address: usize, byte_order: Option) -> ReadExtResult; + fn read_u32(&self, address: usize, byte_order: Option) -> ReadExtResult; + #[cfg(target_pointer_width = "64")] + fn read_u64(&self, address: usize, byte_order: Option) -> ReadExtResult; + fn read_i8(&self, address: usize, byte_order: Option) -> ReadExtResult; + fn read_i16(&self, address: usize, byte_order: Option) -> ReadExtResult; + fn read_i32(&self, address: usize, byte_order: Option) -> ReadExtResult; + #[cfg(target_pointer_width = "64")] + fn read_i64(&self, address: usize, byte_order: Option) -> ReadExtResult; + fn read_f32(&self, address: usize, byte_order: Option) -> ReadExtResult; + #[cfg(target_pointer_width = "64")] + fn read_f64(&self, address: usize, byte_order: Option) -> ReadExtResult; +} + +#[cfg(target_os = "linux")] +impl ReadExt for crate::prelude::Process { + fn read_u8(&self, address: usize) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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) -> ReadExtResult { + ptrace::attach(Pid::from_raw(self.pid))?; + + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + + let mut file = self.mem_file()?; + 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 for ReadExtError { + fn from(value: nix::Error) -> Self { + Self::Nix(value) + } +} + +pub type ReadExtResult = Result; + +#[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::() + .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::() + .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); + } +} diff --git a/src/ext/search.rs b/src/ext/search.rs index b4a9fba..55a94fb 100644 --- a/src/ext/search.rs +++ b/src/ext/search.rs @@ -4,22 +4,16 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::byte_order::ByteOrder; use crate::ext::mapping::MappingExt; use core::num; use nix::{sys::ptrace, unistd::Pid}; use std::io; +use std::io::Read; use std::io::Seek; -use std::{fs, io::Read, path}; use crate::ext::mapping::MappingExtError; -#[derive(Default)] -pub enum ByteOrder { - BigEndian, - #[default] - LittleEndian, -} - /// An extension on to search for specific values in an app. pub trait SearchExt { /// Search for a byte pattern and return memory addresses. @@ -29,7 +23,7 @@ pub trait SearchExt { fn search_u32(&self, value: u32, byte_order: Option) -> SearchExtResult>; #[cfg(target_pointer_width = "64")] fn search_u64(&self, value: u64, byte_order: Option) -> SearchExtResult>; - fn search_i8(&self, value: i8) -> SearchExtResult>; + fn search_i8(&self, value: i8, byte_order: Option) -> SearchExtResult>; fn search_i16(&self, value: i16, byte_order: Option) -> SearchExtResult>; fn search_i32(&self, value: i32, byte_order: Option) -> SearchExtResult>; fn search_i64(&self, value: i64, byte_order: Option) -> SearchExtResult>; @@ -44,9 +38,12 @@ impl SearchExt for crate::prelude::Process { ptrace::attach(Pid::from_raw(self.pid))?; + defer! { + let _ = ptrace::detach(Pid::from_raw(self.pid), None); + } + let mappings = self.mappings()?; - let mempath = path::PathBuf::from(format!("/proc/{}/mem", self.pid)); - let mut file = fs::File::open(mempath)?; + let mut file = self.mem_file()?; const BUFFER_SIZE: usize = 8192; let mut buffer = [0u8; BUFFER_SIZE]; @@ -153,67 +150,73 @@ impl SearchExt for crate::prelude::Process { } fn search_u16(&self, value: u16, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), }), ) } fn search_u32(&self, value: u32, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + 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 search_u64(&self, value: u64, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), }), ) } - fn search_i8(&self, value: i8) -> SearchExtResult> { - self.search_bytes(&value.to_le_bytes()) + fn search_i8(&self, value: i8, byte_order: Option) -> SearchExtResult> { + self.search_bytes( + &(match byte_order { + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), + }), + ) } fn search_i16(&self, value: i16, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), }), ) } fn search_i32(&self, value: i32, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + 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 search_i64(&self, value: i64, byte_order: Option) -> SearchExtResult> { - let byte_order = byte_order.unwrap_or_default(); self.search_bytes( &(match byte_order { - ByteOrder::BigEndian => value.to_be_bytes(), - ByteOrder::LittleEndian => value.to_le_bytes(), + Some(ByteOrder::BigEndian) => value.to_be_bytes(), + Some(ByteOrder::LittleEndian) => value.to_le_bytes(), + None => value.to_ne_bytes(), }), ) } @@ -222,41 +225,17 @@ impl SearchExt for crate::prelude::Process { #[derive(Debug, Error)] pub enum SearchExtError { #[error("An IO error occured. {0:?} {0}")] - IO(io::Error), + IO(#[from] io::Error), #[error("Could not convert number. {0} {0:?}")] - TryFromIntError(num::TryFromIntError), + TryFromIntError(#[from] num::TryFromIntError), #[error("A *nix error occured. {0:?} {0}")] - Nix(nix::Error), + Nix(#[from] nix::Error), #[error("An error occured while checking process mapping. {0} {0:?}")] - MappingError(MappingExtError), + MappingError(#[from] MappingExtError), } pub type SearchExtResult = Result; -impl From for SearchExtError { - fn from(value: io::Error) -> Self { - Self::IO(value) - } -} - -impl From for SearchExtError { - fn from(value: nix::Error) -> Self { - Self::Nix(value) - } -} - -impl From for SearchExtError { - fn from(value: MappingExtError) -> Self { - Self::MappingError(value) - } -} - -impl From for SearchExtError { - fn from(value: num::TryFromIntError) -> Self { - Self::TryFromIntError(value) - } -} - #[cfg(target_os = "linux")] #[cfg(test)] mod linux_tests { @@ -427,7 +406,9 @@ mod linux_tests { fn test_search_i8(dummy_process: DummyProcess) { let process = Process::try_new(dummy_process.pid).expect("Could not get dummy process with id."); - let addresses = process.search_i8(-1).expect("Could not search i8 value."); + let addresses = process + .search_i8(-1, None) + .expect("Could not search i8 value."); let target_address: usize = { let response = diff --git a/src/lib.rs b/src/lib.rs index d9c81e1..9e967b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,10 +9,15 @@ extern crate assertables; #[macro_use] extern crate thiserror; +#[macro_use] +extern crate scopeguard; + +mod byte_order; mod ext; mod process; pub mod prelude { - pub use crate::ext::search::{ByteOrder, SearchExt, SearchExtError, SearchExtResult}; + pub use crate::byte_order::ByteOrder; + pub use crate::ext::search::{SearchExt, SearchExtError, SearchExtResult}; pub use crate::process::{Process, ProcessError}; } diff --git a/src/process.rs b/src/process.rs index bbd642c..2979b4a 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,6 +5,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::path; +#[cfg(target_os = "linux")] +use std::{fs, io}; /// Process spawned by OS. #[derive(Debug, PartialEq)] @@ -25,6 +27,16 @@ impl Process { Ok(Self { pid }) } + + #[cfg(target_os = "linux")] + fn mem_path(&self) -> path::PathBuf { + path::PathBuf::from(format!("/proc/{}/mem", self.pid)) + } + + #[cfg(target_os = "linux")] + pub(crate) fn mem_file(&self) -> io::Result { + fs::File::open(self.mem_path()) + } } /// Errors for [Process].