Custom error class and better error handling in main using thiserror and anyhow
This commit is contained in:
parent
c59e81571c
commit
37ace245b3
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
nix = { version = "0.29.0", features = ["process", "ptrace"] }
|
||||
thiserror = "2.0.12"
|
||||
anyhow = "1.0.98"
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/main.rs
55
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue