diff --git a/Cargo.lock b/Cargo.lock index 3921868..a390d08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,19 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "bdb" version = "0.0.0" dependencies = [ + "anyhow", "nix", + "thiserror", ] [[package]] @@ -44,3 +52,58 @@ dependencies = [ "cfg_aliases", "libc", ] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" diff --git a/Cargo.toml b/Cargo.toml index 56711d1..c7abdff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ publish = false [dependencies] nix = { version = "0.29.0", features = ["process", "ptrace"] } +thiserror = "2.0.12" +anyhow = "1.0.98" \ No newline at end of file diff --git a/src/debug_target.rs b/src/debug_target.rs index 45e701f..e0347a3 100644 --- a/src/debug_target.rs +++ b/src/debug_target.rs @@ -1,7 +1,15 @@ -use nix::errno::Errno; use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus}; use nix::unistd::Pid; +#[derive(thiserror::Error, Debug)] +pub enum DebugError { + #[error("Error when calling ptrace: {0}")] + PTraceError(#[from] nix::errno::Errno), + + #[error("Child stopped with status {0:?}, but was not expecting to catch this one")] + UnexpectedWaitStatus(nix::sys::wait::WaitStatus), +} + pub struct DebugTarget { state: S, } @@ -20,12 +28,14 @@ impl DebugState for Stopped { } impl DebugTarget { - pub fn new(pid: Pid) -> Result { - waitid(Id::Pid(pid), WaitPidFlag::WSTOPPED).and(Ok(DebugTarget { state: Stopped { pid } })) + pub fn new(pid: Pid) -> Result { + waitid(Id::Pid(pid), WaitPidFlag::WSTOPPED)?; + Ok(DebugTarget { state: Stopped { pid } }) } - pub fn cont(self) -> Result, Errno> { - nix::sys::ptrace::cont(self.state.pid, None).and(Ok(DebugTarget { state: Running { pid: self.state.pid } })) + pub fn cont(self) -> Result, DebugError> { + nix::sys::ptrace::cont(self.state.pid, None)?; + Ok(DebugTarget { state: Running { pid: self.state.pid } }) } } @@ -39,10 +49,10 @@ impl DebugState for Running { impl DebugTarget { - pub fn wait_for_exit(self) -> Result { - waitid(Id::Pid(self.state.pid), WaitPidFlag::WEXITED).and_then(|status| match status { - WaitStatus::Exited(_, exit_code) => Ok(exit_code), - _ => Err(Errno::EINVAL), // TODO: custom error type? - }) + pub fn wait_for_exit(self) -> Result { + match waitid(Id::Pid(self.state.pid), WaitPidFlag::WEXITED)? { + WaitStatus::Exited(_pid, exit_code) => Ok(exit_code), + status => Err(DebugError::UnexpectedWaitStatus(status)) + } } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 92c0ca2..379b760 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,16 @@ mod child; mod debug_target; use crate::debug_target::DebugTarget; +use anyhow::{anyhow, bail}; use nix::libc::user_regs_struct; use nix::sys::ptrace::*; use nix::sys::signal::Signal::*; use nix::sys::wait::{waitid, waitpid, Id, WaitPidFlag, WaitStatus}; use nix::unistd::{fork, ForkResult, Pid}; use std::ffi::{c_long, c_void, CString}; -use std::process::ExitCode; #[allow(dead_code)] -fn single_step_all(child_pid: Pid) -> ExitCode { +fn single_step_all(child_pid: Pid) -> Result<(), anyhow::Error> { let mut instruction_number = 0; loop { let wait_status = waitpid(child_pid, None); @@ -20,24 +20,23 @@ fn single_step_all(child_pid: Pid) -> ExitCode { instruction_number += 1; let regs = getregs(child_pid).unwrap(); println!("🔎 [==> {}] rip= 0x{:016X}, rax = 0x{rax:X} ({rax})", instruction_number, regs.rip, rax = regs.rax); - step(child_pid, None).unwrap(); + step(child_pid, None)?; } Ok(WaitStatus::Exited(_, exit_code)) => { println!("👋 Child exited with code {exit_code}"); - return ExitCode::SUCCESS; + return Ok(()); } other => { - println!("⚠️ Other (unexpected) wait status: {other:?}"); - return ExitCode::FAILURE; + bail!("⚠️ Other (unexpected) wait status: {other:?}"); } } } } #[allow(dead_code)] -fn breakpoint_fun(child_pid: Pid) -> ExitCode { +fn breakpoint_fun(child_pid: Pid) -> Result<(), anyhow::Error> { println!("⏳️ Waiting for child to be ready"); - waitid(Id::Pid(child_pid), WaitPidFlag::WSTOPPED).unwrap(); + waitid(Id::Pid(child_pid), WaitPidFlag::WSTOPPED)?; let address: u64 = 0x0000000000401019; println!("🚧 Setting breakpoint at location 0x{address:x}"); @@ -53,67 +52,65 @@ fn breakpoint_fun(child_pid: Pid) -> ExitCode { println!("\t🖍️ Breakpoint set"); println!("⚙️ Continuing execution waiting for breakpoint"); - cont(child_pid, None).unwrap(); - match waitpid(child_pid, None).unwrap() { + cont(child_pid, None)?; + match waitpid(child_pid, None)? { WaitStatus::Stopped(_, SIGTRAP) => { - let registers = getregs(child_pid).unwrap(); + let registers = getregs(child_pid)?; let breakpoint_addr = registers.rip - 1; println!("🛑 Stopped at breakpoint ({:#018x})!", breakpoint_addr); println!("\t🔎 Registers content: {:?}", registers); println!("\t🖍️ Restoring instructions to original"); write(child_pid, address as *mut c_void, c_long::from_le_bytes(orig_bytes)).expect("breakpoint restore memory"); println!("\t↪️ Rolling back instruction pointer"); - setregs(child_pid, user_regs_struct { rip: breakpoint_addr, ..registers }).unwrap(); + setregs(child_pid, user_regs_struct { rip: breakpoint_addr, ..registers })?; println!("\t⚙️ One more instruction"); - step(child_pid, None).unwrap(); - waitid(Id::Pid(child_pid), WaitPidFlag::WSTOPPED).unwrap(); + step(child_pid, None)?; + waitid(Id::Pid(child_pid), WaitPidFlag::WSTOPPED)?; println!("\t⚙️ Continuing execution"); - cont(child_pid, None).unwrap(); + cont(child_pid, None)?; } other => { - println!("⚠️ Other (unexpected) wait status: {other:?}"); - return ExitCode::FAILURE; + bail!("⚠️ Other (unexpected) wait status: {other:?}"); } } match waitpid(child_pid, None) { Ok(WaitStatus::Exited(_, exit_code)) => { println!("👋 Child exited with code {exit_code}"); - ExitCode::SUCCESS + Ok(()) } other => { - println!("⚠️ Other (unexpected) wait status: {other:?}"); - ExitCode::FAILURE + bail!("⚠️ Other (unexpected) wait status: {other:?}"); } } } -fn main() -> ExitCode { - let child_exec_path = CString::new(env!("ASM_PROG_PATH")).unwrap(); +fn main() -> Result<(), anyhow::Error> { + let child_exec_path = CString::new(env!("ASM_PROG_PATH"))?; match unsafe { fork() } { Ok(ForkResult::Child) => child::starti(child_exec_path), Ok(ForkResult::Parent { child: child_pid }) => { println!("✔️ Created child {child_pid}"); - let target = DebugTarget::new(child_pid).unwrap(); + let target = DebugTarget::new(child_pid)?; println!("✔️ Child ready!"); println!("⚙️ Continuing execution"); - let target = target.cont().unwrap(); - let exit_code = target.wait_for_exit().unwrap(); + let target = target.cont()?; + let exit_code = target.wait_for_exit()?; println!("👋 Child exited with code {exit_code}"); + Ok(()) - // return single_step_all(child_pid); - // return breakpoint_fun(child_pid); - ExitCode::SUCCESS + // single_step_all(child_pid) + // breakpoint_fun(child_pid) } Err(e) => { println!("❌ Fork failed: {e}"); - ExitCode::FAILURE + Err(anyhow!("Fork failed")) } } }