Continuing after a breakpoint both with single step and cont (but breakpoints still one-use)

This commit is contained in:
Elnath 2025-05-18 17:52:41 +02:00
parent bcb61a02f0
commit 5897c9e862
2 changed files with 24 additions and 16 deletions

View File

@ -30,20 +30,26 @@ impl StoppedTarget {
Ok(self.breakpoints.contains_key(&(rip - 1))) Ok(self.breakpoints.contains_key(&(rip - 1)))
} }
pub fn cont(mut self) -> Result<RunningTarget, DebugError> { fn breakpoint_remove_and_rewind(&mut self) -> Result<(), DebugError> {
let mut registers = self.get_registers()?; let mut registers = self.get_registers()?;
if self.breakpoints.contains_key(&(registers.rip - 1)) { // We are on a breakpoint, we remove it self.remove_breakpoint(registers.rip - 1)?;
self = self.remove_breakpoint(registers.rip - 1)?;
registers.rip -= 1; registers.rip -= 1;
nix::sys::ptrace::setregs(self.pid, registers).map_err(PTraceError)?; nix::sys::ptrace::setregs(self.pid, registers).map_err(PTraceError)?;
Ok(())
} }
pub fn cont(mut self) -> Result<RunningTarget, DebugError> {
if self.on_breakpoint()? {
self.breakpoint_remove_and_rewind()?;
}
nix::sys::ptrace::cont(self.pid, None).map_err(PTraceError)?; nix::sys::ptrace::cont(self.pid, None).map_err(PTraceError)?;
Ok(self.to_running()) Ok(self.to_running())
} }
pub fn stepi(self) -> Result<RunningTarget, PTraceError> { pub fn stepi(mut self) -> Result<RunningTarget, DebugError> {
todo!("Take into account breakpoints also for stepi"); if self.on_breakpoint()? {
self.breakpoint_remove_and_rewind()?;
}
nix::sys::ptrace::step(self.pid, None).map_err(PTraceError)?; nix::sys::ptrace::step(self.pid, None).map_err(PTraceError)?;
Ok(self.to_running()) Ok(self.to_running())
} }
@ -62,7 +68,7 @@ impl StoppedTarget {
} }
#[cfg(target_endian = "little")] // With a bit more work it could be implemented on both architectures, but I only have little-endian computers 🤷 #[cfg(target_endian = "little")] // With a bit more work it could be implemented on both architectures, but I only have little-endian computers 🤷
pub fn add_breakpoint(mut self, address: u64) -> Result<Self, DebugError> { pub fn add_breakpoint(&mut self, address: u64) -> Result<(), DebugError> {
if self.breakpoints.contains_key(&address) { if self.breakpoints.contains_key(&address) {
return Err(DebugError::DuplicateBreakpoint { address }); return Err(DebugError::DuplicateBreakpoint { address });
} }
@ -72,18 +78,18 @@ impl StoppedTarget {
nix::sys::ptrace::write(self.pid, address as *mut c_void, new_content).map_err(PTraceError)?; nix::sys::ptrace::write(self.pid, address as *mut c_void, new_content).map_err(PTraceError)?;
self.breakpoints.insert(address, target_byte); self.breakpoints.insert(address, target_byte);
Ok(self) Ok(())
} }
#[cfg(target_endian = "little")] // Same as add_breakpoint #[cfg(target_endian = "little")] // Same as add_breakpoint
pub fn remove_breakpoint(mut self, address: u64) -> Result<Self, DebugError> { pub fn remove_breakpoint(&mut self, address: u64) -> Result<(), DebugError> {
match self.breakpoints.remove(&address) { match self.breakpoints.remove(&address) {
None => Err(DebugError::NonExistingBreakpoint { address }), None => Err(DebugError::NonExistingBreakpoint { address }),
Some(original_byte) => { Some(original_byte) => {
let content = nix::sys::ptrace::read(self.pid, address as *mut c_void).map_err(PTraceError)?; let content = nix::sys::ptrace::read(self.pid, address as *mut c_void).map_err(PTraceError)?;
let new_content = (content & (!0xff as c_long)) | (original_byte as c_long); let new_content = (content & (!0xff as c_long)) | (original_byte as c_long);
nix::sys::ptrace::write(self.pid, address as *mut c_void, new_content).map_err(PTraceError)?; nix::sys::ptrace::write(self.pid, address as *mut c_void, new_content).map_err(PTraceError)?;
Ok(self) Ok(())
} }
} }
} }

View File

@ -103,18 +103,20 @@ fn main() -> color_eyre::Result<()> {
Ok(ForkResult::Parent { child: child_pid }) => { Ok(ForkResult::Parent { child: child_pid }) => {
println!("✔️ Created child {child_pid}"); println!("✔️ Created child {child_pid}");
let target = StoppedTarget::new(child_pid)?; let mut target = StoppedTarget::new(child_pid)?;
println!("✔️ Child ready!"); println!("✔️ Child ready!");
let target = target.add_breakpoint(0x401019)?; let breakpoint1 = 0x401019;
println!("🛑 Adding breakpoint at {breakpoint1:#x}");
target.add_breakpoint(breakpoint1)?;
let target = target.cont()?.wait_for_something()?; let target = target.cont()?.wait_for_something()?;
match target { match target {
Either::Left(t) => { Either::Left(t) => {
println!("🔎 rip: {:#x}", t.get_registers()?.rip);
if t.on_breakpoint()? { if t.on_breakpoint()? {
println!("🚧 We are on a breakpoint!") println!("🚧 We are on a breakpoint!")
} }
t.cont()?.wait_for_exit()?; println!("🔎 rip: {:#x}", t.get_registers()?.rip);
single_step_all(t)?;
} }
Either::Right(ExitedTarget { exit_code, .. }) => { Either::Right(ExitedTarget { exit_code, .. }) => {
println!("👋 Child exited with code {exit_code}"); println!("👋 Child exited with code {exit_code}");