use std::cell::{Ref, RefCell};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::convert::{TryFrom, TryInto};
use std::ffi::CString;
use std::fmt::Result as FmtResult;
use std::fmt::{Display, Formatter};
use std::fs::read_to_string;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::string::String;
pub use nix::libc::user_regs_struct as Registers;
use nix::sys::ptrace::Options as NixPtraceOptions;
use nix::sys::ptrace::{
self, attach, cont, getregs, setoptions, setregs, step, traceme, AddressType,
};
use nix::sys::signal::Signal as NixSignal;
use nix::sys::wait::waitpid;
use nix::unistd::Pid as NixPid;
use nix::unistd::{execv, fork, getpid, ForkResult};
use nix::Error as NixError;
use crate::breakpoint::{Breakpoint, BreakpointId, Mode, TRAP_OPCODE};
use crate::error::{
CouldNotAttachToPid, CouldNotCreateBreakpoint, CouldNotExecute, CouldNotRead,
CouldNotReadRegisters, CouldNotRemoveBreakpoint, CouldNotResume, CouldNotSetOptions,
CouldNotWait, CouldNotWaitForProcess, CouldNotWrite, CouldNotWriteRegisters, ReadWriteError,
ThreadNotFound,
};
use crate::run::{
Executing, PtraceEventStrategy, PtraceOption, PtraceOptionMap, Reason, RunningState,
};
use crate::thread::{determine_state, CurrentState, Thread, ThreadHandle};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Pid(i32);
impl From<NixPid> for Pid {
fn from(value: NixPid) -> Self {
Self(value.as_raw())
}
}
impl From<Pid> for NixPid {
fn from(val: Pid) -> Self {
Self::from_raw(val.0)
}
}
impl Display for Pid {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!(f, "{}", self.0)
}
}
impl Pid {
#[must_use]
pub const fn from_raw(value: i32) -> Self {
Self(value)
}
#[must_use]
pub const fn as_raw(&self) -> i32 {
self.0
}
}
#[allow(clippy::module_name_repetitions)] pub struct TargetProcess {
pid: Pid,
executable: String,
arguments: Vec<String>,
breakpoints: RefCell<BTreeMap<BreakpointId, Breakpoint>>,
threads: RefCell<Vec<Rc<ThreadHandle>>>,
_unsend: PhantomData<*mut ()>,
}
pub struct BreakpointIter<'a> {
map: Ref<'a, BTreeMap<BreakpointId, Breakpoint>>,
keys: VecDeque<BreakpointId>,
}
impl<'a> Iterator for BreakpointIter<'a> {
type Item = Ref<'a, Breakpoint>;
fn next(&mut self) -> Option<Self::Item> {
let key = self.keys.pop_front()?;
let clone = Ref::clone(&self.map);
Some(Ref::map(clone, |breakpoints| {
breakpoints.get(&key).unwrap()
}))
}
}
impl<'a> From<Ref<'a, BTreeMap<BreakpointId, Breakpoint>>> for BreakpointIter<'a> {
fn from(value: Ref<'a, BTreeMap<BreakpointId, Breakpoint>>) -> Self {
let keys = value.keys().copied().collect();
BreakpointIter { map: value, keys }
}
}
fn string_to_cstring<S>(s: S) -> Result<CString, CouldNotExecute>
where
S: AsRef<str>,
{
CString::new(s.as_ref().as_bytes()).map_err(|e| CouldNotExecute::NullInString {
string: s.as_ref().to_owned(),
source: e,
})
}
fn arguments_to_c_strings<I, S>(args: I) -> Result<Vec<CString>, CouldNotExecute>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
args.into_iter().map(string_to_cstring).collect()
}
fn execute_program<I, Sexec, Sargs>(exec: Sexec, args: I) -> Result<Pid, CouldNotExecute>
where
I: IntoIterator<Item = Sargs>,
Sargs: AsRef<str>,
Sexec: AsRef<str>,
{
match unsafe { fork() } {
Err(errno) => Err(CouldNotExecute::CouldNotFork {
source: errno.into(),
}),
Ok(ForkResult::Child) => {
match traceme() {
Ok(()) => {
let args_vec = arguments_to_c_strings(args)?;
let _ = execv(
string_to_cstring(exec.as_ref())?.as_ref(),
args_vec.as_slice(),
);
let exec_s: &str = exec.as_ref();
Err(CouldNotExecute::ExecutableNotFound(PathBuf::from(exec_s)))
}
Err(errno) => Err(CouldNotExecute::CouldNotTrace {
pid: getpid().into(),
source: errno.into(),
}),
}
}
Ok(ForkResult::Parent { child, .. }) => Ok(child.into()),
}
}
#[allow(clippy::module_name_repetitions)] pub fn spawn_process<I, Sexec, Sargs>(
exec: Sexec,
args: I,
) -> Result<TargetProcess, CouldNotExecute>
where
Sexec: AsRef<Path>,
Sargs: AsRef<str>,
I: IntoIterator<Item = Sargs>,
{
fn path_to_string(path: PathBuf) -> Result<String, CouldNotExecute> {
let cloned = path.clone();
let s = cloned
.to_str()
.ok_or(CouldNotExecute::InvalidUTF8Path(path))?;
Ok(s.to_owned())
}
let exec_path = exec.as_ref();
let exec_absolute = exec_path
.canonicalize()
.map_err(|_| CouldNotExecute::ExecutableNotFound(exec_path.to_owned()))
.and_then(path_to_string)?;
let mut args_vec: Vec<String> = args.into_iter().map(|s| String::from(s.as_ref())).collect();
args_vec.insert(0, path_to_string(exec_path.to_owned())?);
let pid = execute_program(exec_absolute.as_str(), &args_vec)?;
let main_thread = ThreadHandle::new(pid);
Ok(TargetProcess {
pid,
executable: exec_absolute,
arguments: args_vec,
breakpoints: RefCell::default(),
threads: RefCell::from(vec![Rc::from(main_thread)]),
_unsend: PhantomData,
})
}
impl TryFrom<Pid> for TargetProcess {
type Error = CouldNotAttachToPid;
fn try_from(pid: Pid) -> Result<Self, Self::Error> {
let proc_path = PathBuf::from(&format!("/proc/{pid}"));
if proc_path.exists() {
let cmdline = proc_path.join("cmdline");
let exe = proc_path.join("exe");
let exec_path = exe
.read_link()
.map_err(|_| CouldNotAttachToPid::ExecutableNotFound(exe))?
.to_string_lossy()
.into_owned();
let cmd = read_to_string(cmdline)
.map_err(|err| CouldNotAttachToPid::CouldNotReadCmdLineFile { pid, source: err })?;
let arguments = cmd
.split('\0')
.filter_map(|s| (!s.is_empty()).then(|| s.to_string()))
.collect();
let nix_pid = pid.into();
attach(nix_pid).map_err(|source| Self::Error::CouldNotAttach {
pid,
source: source.into(),
})?;
waitpid(nix_pid, None).map_err(|source| Self::Error::CouldNotStop {
pid,
source: source.into(),
})?;
cont(nix_pid, NixSignal::SIGCONT).map_err(|source| Self::Error::CouldNotRestart {
pid,
source: source.into(),
})?;
let main_thread = ThreadHandle::new(pid);
Ok(Self {
pid,
executable: exec_path,
arguments,
breakpoints: RefCell::default(),
threads: RefCell::from(vec![Rc::from(main_thread)]),
_unsend: PhantomData,
})
} else {
Err(Self::Error::ProcessNotFound { pid })
}
}
}
pub(crate) fn set_controller_breakpoint_id<E>(ctrl: &mut TargetController<E>)
where
E: Executing,
{
if let &Reason::Breakpoint(brk_id) = ctrl.reason() {
ctrl.breakpoint_id = Some(brk_id);
}
}
struct ThreadState {
reason: Reason,
brk: Option<BreakpointId>,
tid: Pid,
}
impl ThreadState {
pub fn to_running_state_mut<'process>(
&mut self,
process: &'process TargetProcess,
) -> RunningState<Thread<'process>> {
if matches!(self.reason, Reason::Exited { .. } | Reason::Signaled { .. }) {
RunningState::Exited {
tid: self.tid,
reason: self.reason,
}
} else {
let threads = process.threads.borrow();
let handle = threads.iter().find(|thr| thr.tid == self.tid).unwrap();
let thread = Thread::encapsulate(handle.clone(), process);
let ctrl = TargetController::new(thread, self.reason, self.brk);
RunningState::Alive(ctrl)
}
}
}
#[allow(clippy::module_name_repetitions)]
pub struct StoppedProcess {
process: TargetProcess,
threads: HashMap<Pid, ThreadState>,
}
impl AsRef<TargetProcess> for StoppedProcess {
fn as_ref(&self) -> &TargetProcess {
&self.process
}
}
impl StoppedProcess {
pub fn threads(&mut self) -> HashMap<Pid, RunningState<Thread>> {
let process = &self.process;
self.threads
.iter_mut()
.map(|(k, v)| (*k, v.to_running_state_mut(process)))
.collect()
}
#[allow(clippy::missing_const_for_fn)]
pub fn into_inner(self) -> TargetProcess {
self.process
}
}
impl Executing for TargetProcess {
type StoppedRepresentation = StoppedProcess;
type WaitError = CouldNotWaitForProcess;
fn process(&self) -> &TargetProcess {
self
}
fn pid(&self) -> Pid {
self.pid
}
fn wait(mut self) -> Result<RunningState<Self>, CouldNotWaitForProcess> {
let mut one_thread_alive = false;
let states = self
.threads()
.into_iter()
.map(determine_state)
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|state| match state {
CurrentState::Running(thr) => thr.wait(),
CurrentState::Stopped(ctrl) => Ok(RunningState::Alive(ctrl)),
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|state| match state {
RunningState::Alive(ctrl) => {
let tid = ctrl.context.tid();
let state = ThreadState {
reason: *ctrl.reason(),
brk: ctrl.breakpoint_id,
tid,
};
one_thread_alive = true;
(tid, state)
}
RunningState::Exited { tid, reason } => {
let state = ThreadState {
reason,
brk: None,
tid,
};
(tid, state)
}
})
.collect::<HashMap<_, _>>();
let stopped_process = StoppedProcess {
process: self,
threads: states,
};
let pgid = stopped_process.process.pid();
let (main_reason, main_brk) = {
let main_thread = &stopped_process.threads[&pgid];
(main_thread.reason, main_thread.brk)
};
let ctrl = TargetController::<Self>::new(stopped_process, main_reason, main_brk);
if one_thread_alive {
Ok(RunningState::Alive(ctrl))
} else {
Ok(RunningState::Exited {
tid: ctrl.process().pid(),
reason: ctrl.reason,
})
}
}
fn resume(ctrl: TargetController<Self>) -> Result<Self, CouldNotResume> {
let mut process = ctrl.context.process;
for mut thread in ctrl.context.threads.into_values() {
let tid = thread.tid;
let state = thread.to_running_state_mut(&process);
if let RunningState::Alive(ctrl) = state {
ctrl.resume()?;
} else {
drop(state);
process.remove_thread(tid).unwrap();
}
}
Ok(process)
}
fn set_ptrace_option(
ctrl: &mut TargetController<Self>,
option: PtraceOption,
strategy: PtraceEventStrategy,
) -> Result<PtraceEventStrategy, CouldNotSetOptions> {
let (process, threads) = (&ctrl.context.process, &mut ctrl.context.threads);
let mut res = PtraceEventStrategy::Unset;
for thread in threads.values_mut() {
let tid = thread.tid;
let state = thread.to_running_state_mut(process);
if let RunningState::Alive(mut ctrl) = state {
let old = Thread::set_ptrace_option(&mut ctrl, option, strategy)?;
if tid == process.pid() {
res = old;
}
}
}
Ok(res)
}
}
impl TargetProcess {
#[must_use]
pub const fn pid(&self) -> Pid {
self.pid
}
#[must_use]
pub fn executable(&self) -> &str {
&self.executable
}
#[must_use]
pub fn arguments(&self) -> Vec<&str> {
self.arguments.iter().map(String::as_str).collect()
}
pub fn breakpoints(&self) -> BreakpointIter {
BreakpointIter::from(self.breakpoints.borrow())
}
#[must_use]
pub fn get_breakpoint(&self, id: BreakpointId) -> Option<Ref<Breakpoint>> {
Ref::filter_map(self.breakpoints.borrow(), |breakpoints| {
breakpoints.get(&id)
})
.ok()
}
#[must_use]
pub fn threads(&mut self) -> Vec<Thread> {
let handles = self.threads.borrow();
let mut threads = Vec::with_capacity(handles.len());
for idx in 0..handles.len() {
let handle = handles[idx].clone();
let thread = Thread::encapsulate(handle, self);
threads.push(thread);
}
threads
}
pub(crate) fn add_thread(&self, handle: ThreadHandle) {
let mut threads = self.threads.borrow_mut();
threads.push(Rc::from(handle));
}
pub(crate) fn remove_thread(&mut self, tid: Pid) -> Result<(), ThreadNotFound> {
let mut threads = self.threads.borrow_mut();
let pos = threads
.iter()
.position(|thr| thr.tid == tid)
.ok_or_else(|| ThreadNotFound {
pid: self.pid(),
tid,
})?;
threads.remove(pos);
Ok(())
}
}
pub struct TargetController<E>
where
E: Executing,
{
pub(crate) context: E::StoppedRepresentation,
reason: Reason,
breakpoint_id: Option<BreakpointId>,
_unsend: PhantomData<*mut ()>,
}
impl<E> TargetController<E>
where
E: Executing,
{
pub(crate) const fn new(
context: E::StoppedRepresentation,
reason: Reason,
breakpoint_id: Option<BreakpointId>,
) -> Self {
Self {
context,
reason,
breakpoint_id,
_unsend: PhantomData,
}
}
#[must_use]
pub fn process(&self) -> &TargetProcess {
self.context.as_ref().process()
}
#[must_use]
pub fn context(&self) -> &E {
self.context.as_ref()
}
#[must_use]
pub const fn reason(&self) -> &Reason {
&self.reason
}
#[must_use]
pub fn breakpoint(&self) -> Option<Ref<Breakpoint>> {
self.breakpoint_id
.and_then(|id| self.process().get_breakpoint(id))
}
pub fn remove_breakpoint(&mut self, id: BreakpointId) -> Result<(), CouldNotRemoveBreakpoint> {
let res = self.process().get_breakpoint(id).map_or_else(
|| {
Err(CouldNotRemoveBreakpoint::BreakpointNotFound {
id,
pid: self.process().pid(),
})
},
|brk| {
let address = brk.address();
let saved_byte = brk.saved_byte();
Ok((address, saved_byte))
},
);
res.and_then(|(address, saved_byte)| {
self.write(address, &[saved_byte])?;
let mut breakpoints = self.process().breakpoints.borrow_mut();
breakpoints.remove(&id);
Ok(())
})
}
pub(crate) fn pass_over_breakpoint(&mut self) -> Result<(), CouldNotResume> {
if let Some(id) = self.breakpoint_id {
let data = self.process().get_breakpoint(id).map(|brk| {
let address = brk.address();
let saved_byte = brk.saved_byte();
let mode = brk.mode();
(address, saved_byte, mode)
});
let mut regs = self.get_registers()?;
if let Some((address, saved_byte, mode)) = data {
self.write(address, &[saved_byte])?;
regs.rip = address as u64;
self.set_registers(regs)?;
if mode == Mode::Persistent {
self.breakpoint_id = None;
self.singlestep()?;
self.write(address, &[TRAP_OPCODE])?;
} else {
self.remove_breakpoint(id)?;
}
} else {
regs.rip -= 1;
self.set_registers(regs)?;
}
}
Ok(())
}
pub fn resume(self) -> Result<E, CouldNotResume> {
Executing::resume(self)
}
pub fn singlestep(&mut self) -> Result<Reason, CouldNotResume> {
self.pass_over_breakpoint()?;
self.reason = self.blind_singlestep()?;
set_controller_breakpoint_id(self);
Ok(self.reason)
}
pub(crate) fn blind_singlestep(&mut self) -> Result<Reason, CouldNotResume> {
let pid = self.process().pid();
let status = step(pid.into(), None)
.and_then(|_| waitpid(Some(pid.into()), None))
.map_err(|source| {
CouldNotResume::CouldNotStop(CouldNotWait::ProcessNotFound {
pid,
source: source.into(),
})
})?;
Ok(Reason::from_wait_status(self, status))
}
pub fn get_registers(&self) -> Result<Registers, CouldNotReadRegisters> {
let pid = self.process().pid();
match getregs(pid.into()) {
Ok(regs) => Ok(regs),
Err(source) => Err(CouldNotReadRegisters {
pid,
source: source.into(),
}),
}
}
pub fn set_registers(&mut self, regs: Registers) -> Result<(), CouldNotWriteRegisters> {
let pid = self.process().pid();
match setregs(pid.into(), regs) {
Ok(()) => Ok(()),
Err(source) => Err(CouldNotWriteRegisters {
pid,
source: source.into(),
}),
}
}
pub fn read(&mut self, mut addr: usize, mut length: usize) -> Result<Vec<u8>, ReadWriteError> {
let pid = self.process().pid();
let mut res = Vec::with_capacity(length);
while length > 0 {
let addr_ptr = addr as AddressType;
let word = ptrace::read(pid.into(), addr_ptr)
.map(i64::to_ne_bytes)
.map_err(|err| match err {
NixError::EFAULT | NixError::EIO => ReadWriteError::from(CouldNotRead {
pid,
address: addr,
source: err.into(),
}),
NixError::ESRCH => ReadWriteError::ProcessKilled {
pid,
source: err.into(),
},
errno => unreachable!("ptrace should not return {} when reading", errno),
})?;
if length >= 8 {
res.extend(word);
addr += 8;
length -= 8;
} else {
res.extend(&word[0..length]);
length = 0;
}
}
Ok(res)
}
pub fn write(&mut self, mut addr: usize, data: &[u8]) -> Result<(), ReadWriteError> {
let pointer_size = 8;
let pid = self.process().pid();
for chunk in data.chunks(pointer_size) {
let addr_ptr = addr as AddressType;
let mut vec: Vec<u8>;
let slice = if chunk.len() == 8 {
chunk
} else {
let word = ptrace::read(pid.into(), addr_ptr)
.map(i64::to_ne_bytes)
.map_err(|err| match err {
NixError::EFAULT | NixError::EIO => ReadWriteError::from(CouldNotRead {
pid,
address: addr,
source: err.into(),
}),
NixError::ESRCH => ReadWriteError::ProcessKilled {
pid,
source: err.into(),
},
errno => unreachable!("ptrace should not return {} when reading", errno),
})?;
vec = word.to_vec();
vec.splice(0..chunk.len(), chunk.iter().copied());
vec.as_slice()
};
let chunk_array: [u8; 8] = slice.try_into().unwrap();
let chunk_ptr = usize::from_ne_bytes(chunk_array) as AddressType;
unsafe {
ptrace::write(pid.into(), addr_ptr, chunk_ptr).map_err(|err| match err {
NixError::EFAULT | NixError::EIO => ReadWriteError::from(CouldNotWrite {
pid,
address: addr,
source: err.into(),
}),
NixError::ESRCH => ReadWriteError::ProcessKilled {
pid,
source: err.into(),
},
errno => unreachable!("ptrace should not return {} when writing", errno),
})?;
};
addr += pointer_size;
}
Ok(())
}
pub(crate) fn register_breakpoint(
&mut self,
brk: Breakpoint,
) -> Result<Ref<Breakpoint>, CouldNotCreateBreakpoint> {
let pid = self.process().pid();
let mut breakpoints = self.process().breakpoints.borrow_mut();
if breakpoints.iter().any(|(_, b)| *b == brk) {
Err(CouldNotCreateBreakpoint::BreakpointAlreadyExists {
pid,
address: brk.address(),
})
} else {
let id = brk.id();
breakpoints.insert(id, brk);
drop(breakpoints);
Ok(
Ref::filter_map(self.process().breakpoints.borrow(), |breakpoints| {
breakpoints.get(&id)
})
.unwrap(),
)
}
}
pub(crate) fn update_reason(&mut self, reason: Reason) {
self.reason = reason;
}
pub(crate) fn write_ptrace_options(
&mut self,
options: NixPtraceOptions,
) -> Result<(), CouldNotSetOptions> {
let pid = self.context.as_ref().pid();
let nix_pid = pid.into();
setoptions(nix_pid, options).map_err(|source| CouldNotSetOptions {
pid,
options: PtraceOptionMap::all_set_options(options),
source: source.into(),
})
}
}
impl TargetController<TargetProcess> {
#[allow(clippy::missing_const_for_fn)]
pub fn per_thread(self) -> StoppedProcess {
self.context
}
}
#[macro_export]
macro_rules! update_registers {
(
$ctrl:expr => {
$($reg:ident: $exp:expr),*
}
) => {{
#[allow(unused_mut)]
let mut ctrl = $ctrl;
ctrl.get_registers()
.map_err( |e| $crate::error::CouldNotReadWriteRegister::from(e) )
.map( |mut regs| { $( regs.$reg = $exp; )* regs })
.and_then( |regs| {
ctrl.set_registers(regs)
.map_err( |e| $crate::error::CouldNotReadWriteRegister::from(e))
})
}}
}
#[macro_export]
macro_rules! with_registers {
(
$ctrl:expr,
$($reg:ident: $exp:expr),*;
$func:expr
) => {{
const fn type_hint<F, T, E, ExeCtx>(func: F) -> F
where
F: Fn(&mut $crate::process::TargetController<ExeCtx>,
$crate::process::Registers) -> Result<T, E>,
E: std::error::Error + From<$crate::error::CouldNotReadWriteRegister>,
ExeCtx: $crate::run::Executing,
{
func
}
fn to_err_type<F, T, E, ExeCtx>(_func: &F, e: $crate::error::CouldNotReadWriteRegister) -> E
where
F: Fn(&mut $crate::process::TargetController<ExeCtx>,
$crate::process::Registers) -> Result<T, E>,
E: std::error::Error + From<$crate::error::CouldNotReadWriteRegister>,
ExeCtx: $crate::run::Executing,
{
E::from(e)
}
#[allow(unused_mut)]
let mut ctrl = $ctrl;
let func = type_hint($func);
ctrl.get_registers()
.map( |mut regs| {
let save = regs.clone();
$( regs.$reg = $exp; )*
(save, regs)
})
.map_err( |e| $crate::error::CouldNotReadWriteRegister::from(e) )
.map_err( |e| to_err_type(&func, e) )
.and_then( |(save, regs)| {
ctrl.set_registers(regs)?;
let res = func(&mut ctrl, regs)?;
ctrl.set_registers(save)?;
Ok((res))
})
}}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::process::{Command, Stdio};
use anyhow::Error as AnyError;
use pretty_assertions::assert_eq;
use crate::error::CouldNotReadWriteRegister;
use crate::run::Signal;
use super::*;
#[test]
fn process_spawned() {
match spawn_process("/bin/ls", ["-l", "/tmp"]) {
Err(err) => panic!("Failed to spawn process with error: {:?}", err),
Ok(target) => {
assert_eq!(target.executable(), "/usr/bin/ls");
assert_eq!(target.arguments(), vec!["/bin/ls", "-l", "/tmp"]);
}
}
}
#[test]
fn controller_created() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-l"])?;
let ctrl = target.wait()?.assume_alive()?;
assert_eq!(ctrl.reason, Reason::Trapped);
Ok(())
}
#[test]
fn controller_died() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-l"])?;
let ctrl = target.wait()?.assume_alive()?;
assert_eq!(ctrl.reason, Reason::Trapped);
let target = ctrl.resume()?;
let state = target.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
#[test]
#[ignore]
#[allow(clippy::cast_possible_wrap)]
fn process_from_pid() -> Result<(), AnyError> {
match Command::new("cat").stdin(Stdio::piped()).spawn() {
Ok(mut child) => {
let pid = Pid(child.id() as i32);
let process = TargetProcess::try_from(pid)?;
let ctrl = process.wait()?.assume_alive()?;
let process = ctrl.resume()?;
drop(child.stdin.take().unwrap());
let state = process.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
Err(err) => panic!("Could not spawn process with error: {}", err),
}
}
#[test]
#[ignore]
#[allow(clippy::cast_possible_wrap)]
fn stop_after_attach() -> Result<(), AnyError> {
match Command::new("cat").stdin(Stdio::piped()).spawn() {
Ok(mut child) => {
let pid = Pid(child.id() as i32);
let target = TargetProcess::try_from(pid)?;
let ctrl = target.wait()?.assume_alive()?;
assert_eq!(
ctrl.reason,
Reason::Stopped {
signal: Signal::SIGSTOP
}
);
let target = ctrl.resume()?;
let stdin = child.stdin.take().unwrap();
drop(stdin);
let state = target.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
Err(err) => panic!("Could not spawn process with error: {}", err),
}
}
#[test]
fn get_and_set_registers() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-a"])?;
let mut ctrl = target.wait()?.assume_alive()?;
let regs = ctrl.get_registers()?;
ctrl.set_registers(regs)?;
let target = ctrl.resume()?;
let state = target.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
#[test]
fn update_registers() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-a"])?;
let mut ctrl = target.wait()?.assume_alive()?;
update_registers![
&mut ctrl => {
rax: 0xbeef,
rbx: 0xdead,
rcx: 0x0cd
}
]?;
let regs = ctrl.get_registers()?;
assert_eq!(0xbeef, regs.rax);
assert_eq!(0xdead, regs.rbx);
assert_eq!(0x0cd, regs.rcx);
Ok(())
}
#[test]
fn with_register() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-a"])?;
let mut ctrl = target.wait()?.assume_alive()?;
let (rax, rcx, rdx, rdi) = with_registers!(&mut ctrl, rax: 1, rcx: 2, rdx: 3;
|_, regs| {
Ok::<_, CouldNotReadWriteRegister>((regs.rax, regs.rcx, regs.rdx, regs.rdi))
})?;
let expected_rdi = ctrl.get_registers()?.rdi;
assert_eq!(1, rax);
assert_eq!(2, rcx);
assert_eq!(3, rdx);
assert_eq!(expected_rdi, rdi);
Ok(())
}
#[test]
fn write_to_memory() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-a"])?;
let mut ctrl = target.wait()?.assume_alive()?;
let datas = [
vec![0xef, 0xbe, 0xad, 0xde, 0xfe, 0xca, 0x0b, 0xb0],
vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09],
];
for data in datas {
let regs = ctrl.get_registers()?;
ctrl.write(regs.rip as usize, &data)?;
let filename = format!("/proc/{}/mem", ctrl.process().pid());
let mut file = File::open(filename).expect("Could not open file");
file.seek(SeekFrom::Start(regs.rip))
.expect("Could not seek RIP");
let mut buf: Vec<u8> = vec![0; data.len()];
file.read_exact(&mut buf).expect("buffer overflow");
assert_eq!(&data, buf.as_slice());
}
Ok(())
}
#[test]
fn read_from_memory() -> Result<(), AnyError> {
let target = spawn_process("/bin/ls", ["-a"])?;
let mut ctrl = target.wait()?.assume_alive()?;
let sizes = [1, 4, 8, 12, 30];
for size in sizes {
let regs = ctrl.get_registers()?;
let data = ctrl.read(regs.rip as usize, size)?;
let filename = format!("/proc/{}/mem", ctrl.process().pid());
let mut file = File::open(filename).expect("Could not open file");
file.seek(SeekFrom::Start(regs.rip))
.expect("Could not seek RIP");
let mut buf: Vec<u8> = vec![0; size];
file.read_exact(&mut buf).expect("buffer overflow");
assert_eq!(data.len(), buf.len());
assert_eq!(&data, buf.as_slice());
}
Ok(())
}
}