diff --git a/Cargo.lock b/Cargo.lock index 0ab9562..8a80866 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,9 @@ name = "bdb" version = "0.0.0" dependencies = [ "color-eyre", + "derive_more", "either", + "libc", "nix", "thiserror", ] @@ -96,6 +98,27 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "either" version = "1.15.0" @@ -317,6 +340,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 2176a63..a5b4426 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,8 @@ nix = { version = "0.30.1", features = ["process", "ptrace"] } thiserror = "2.0.12" color-eyre = "0.6.3" either = "1.15.0" +libc = "0.2.172" +derive_more = { version = "2.0.1", features = ["debug"] } [build-dependencies] color-eyre = "0.6.3" \ No newline at end of file diff --git a/src/debug_target.rs b/src/debug_target.rs index 9f6a756..a875715 100644 --- a/src/debug_target.rs +++ b/src/debug_target.rs @@ -2,6 +2,7 @@ use either::{Either, Left, Right}; use nix::libc::user_regs_struct; use nix::sys::wait::{waitid, Id, WaitPidFlag, WaitStatus}; use nix::unistd::Pid; +use crate::syscall_info::{SyscallInfo, SyscallInfoError, syscall_info}; #[derive(thiserror::Error, Debug)] #[error("Error when calling ptrace: {0}")] @@ -67,6 +68,10 @@ impl DebugTarget { pub fn get_registers(&self) -> Result { Ok(nix::sys::ptrace::getregs(self.state.pid)?) } + + pub fn get_syscall_info(&self) -> Result { + Ok(syscall_info(self.state.pid.as_raw())?) + } } pub struct Running { diff --git a/src/main.rs b/src/main.rs index e21a701..e0b4519 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod child; mod debug_target; +mod syscall_info; use crate::debug_target::{DebugTarget, Stopped}; use color_eyre::eyre::eyre; @@ -92,24 +93,24 @@ fn main() -> color_eyre::Result<()> { let target = DebugTarget::new(child_pid)?; println!("✔️ Child ready!"); - // println!("🔎 rip: {:#x}", target.get_registers()?.rip); - // println!("➡️➡️➡️ Stepping three times"); - // let mut target = target; - // for _i in 0..3 { - // target = target.stepi()?; - // } - // println!("🔎 rip: {:#x}", target.get_registers()?.rip); - // - // println!("⚙️ Continuing execution"); - // let target = target.cont()?; - // let exit_code = target.wait_for_exit()?; - // - // println!("👋 Child exited with code {exit_code}"); - // - // Ok(()) + println!("🔎 rip: {:#x}", target.get_registers()?.rip); - single_step_all(target) + println!("⚙️ Executing until next syscall"); + let target = target.cont_syscall()?.wait_for_syscall()?; + println!("{:?}", target.get_syscall_info()?); + let target = target.cont_syscall()?.wait_for_syscall()?; + println!("{:?}", target.get_syscall_info()?); + println!("🔎 rip: {}", target.get_registers()?.rip); + + println!("⚙️ Continuing execution"); + let exit_code = target.cont()?.wait_for_exit()?; + println!("👋 Child exited with code {exit_code}"); + + Ok(()) + + + // single_step_all(target) // breakpoint_fun(child_pid) } Err(e) => { diff --git a/src/syscall_info.rs b/src/syscall_info.rs new file mode 100644 index 0000000..e0c74fb --- /dev/null +++ b/src/syscall_info.rs @@ -0,0 +1,79 @@ +/*! +Currently nix's (v0.30.1) implementation for PTRACE_GET_SYSCALL_INFO does not seem to work, always returning a 0-filled struct. +It also returns the raw libc struct containing a union, while this parses it in a custom union-free type. +This is an alternative implementation that relies on libc and unsafe. +*/ + +use crate::syscall_info::SyscallInfoError::UnsupportedType; +use std::mem::MaybeUninit; + +#[derive(thiserror::Error, Debug)] +pub enum SyscallInfoError { + #[error("Error code when calling ptrace: {error_code}")] + PTraceError { error_code: libc::c_long }, + + #[error("Allocated buffer for syscall info was too small, this should not happen: allocated {allocated} but expected {expected}")] + TooSmallBufferError { allocated: usize, expected: usize }, + + #[error("No info available, it means program was not stopped on a syscall entry/exit")] + NotInSyscall, + + #[error("Unsupported info type returned: {op}")] + UnsupportedType { op: u8 }, +} + +#[derive(derive_more::Debug)] +#[allow(dead_code)] +pub enum SyscallInfo { + SysCallInfoEntry { + #[debug("{rip:#x}")] + rip: u64, + #[debug("{stack_pointer:#x}")] + stack_pointer: u64, + number: u64, + args: [u64; 6], + }, + + SysCallInfoExit { + #[debug("{rip:#x}")] + rip: u64, + #[debug("{stack_pointer:#x}")] + stack_pointer: u64, + return_val: i64, + is_error: u8, + }, +} + +pub fn syscall_info(pid: libc::pid_t) -> Result { + let mut syscall_info_raw = MaybeUninit::::uninit(); + let output_size = size_of_val(&syscall_info_raw); + let return_code = unsafe { libc::ptrace(libc::PTRACE_GET_SYSCALL_INFO, pid, output_size, syscall_info_raw.as_mut_ptr()) }; + if return_code < 0 { + return Err(SyscallInfoError::PTraceError { error_code: return_code }); + } + if return_code as usize > output_size { // Should not happen, but kernel is telling us that it wants to write more than we were expecting + return Err(SyscallInfoError::TooSmallBufferError { allocated: output_size, expected: return_code as usize }); + } + let syscall_info_raw = unsafe { syscall_info_raw.assume_init() }; + + match syscall_info_raw.op { + libc::PTRACE_SYSCALL_INFO_NONE => Err(SyscallInfoError::NotInSyscall), + libc::PTRACE_SYSCALL_INFO_ENTRY => Ok(unsafe { + SyscallInfo::SysCallInfoEntry { + rip: syscall_info_raw.instruction_pointer, + stack_pointer: syscall_info_raw.stack_pointer, + number: syscall_info_raw.u.entry.nr, + args: syscall_info_raw.u.entry.args, + } + }), + libc::PTRACE_SYSCALL_INFO_EXIT => Ok(unsafe { + SyscallInfo::SysCallInfoExit { + rip: syscall_info_raw.instruction_pointer, + stack_pointer: syscall_info_raw.stack_pointer, + return_val: syscall_info_raw.u.exit.sval, + is_error: syscall_info_raw.u.exit.is_error, + } + }), + op => Err(UnsupportedType { op }), + } +}