Custom error class and better error handling in main using thiserror and anyhow

This commit is contained in:
Elnath 2025-04-19 17:46:33 +02:00
parent c59e81571c
commit 37ace245b3
4 changed files with 111 additions and 39 deletions

63
Cargo.lock generated
View File

@ -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"

View File

@ -6,3 +6,5 @@ publish = false
[dependencies]
nix = { version = "0.29.0", features = ["process", "ptrace"] }
thiserror = "2.0.12"
anyhow = "1.0.98"

View File

@ -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<S: DebugState> {
state: S,
}
@ -20,12 +28,14 @@ impl DebugState for Stopped {
}
impl DebugTarget<Stopped> {
pub fn new(pid: Pid) -> Result<Self, Errno> {
waitid(Id::Pid(pid), WaitPidFlag::WSTOPPED).and(Ok(DebugTarget { state: Stopped { pid } }))
pub fn new(pid: Pid) -> Result<Self, DebugError> {
waitid(Id::Pid(pid), WaitPidFlag::WSTOPPED)?;
Ok(DebugTarget { state: Stopped { pid } })
}
pub fn cont(self) -> Result<DebugTarget<Running>, Errno> {
nix::sys::ptrace::cont(self.state.pid, None).and(Ok(DebugTarget { state: Running { pid: self.state.pid } }))
pub fn cont(self) -> Result<DebugTarget<Running>, 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<Running> {
pub fn wait_for_exit(self) -> Result<i32, Errno> {
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<i32, DebugError> {
match waitid(Id::Pid(self.state.pid), WaitPidFlag::WEXITED)? {
WaitStatus::Exited(_pid, exit_code) => Ok(exit_code),
status => Err(DebugError::UnexpectedWaitStatus(status))
}
}
}

View File

@ -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"))
}
}
}