use std::cell::Ref;
use std::fmt::Debug;
use std::sync::atomic::{AtomicUsize, Ordering};
use crate::error::CouldNotCreateBreakpoint;
use crate::process::TargetController;
use crate::run::Executing;
pub const TRAP_OPCODE: u8 = 0xCC;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
OneShot,
Persistent,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Breakpoint {
address: usize,
saved_byte: u8,
mode: Mode,
id: BreakpointId,
}
static BREAKPOINT_ID_COUNTER: AtomicUsize = AtomicUsize::new(1);
pub(crate) fn new_breakpoint_id() -> BreakpointId {
BreakpointId {
id: BREAKPOINT_ID_COUNTER.fetch_add(1, Ordering::Relaxed),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[allow(clippy::module_name_repetitions)] pub struct BreakpointId {
pub(crate) id: usize,
}
pub fn breakpoint<E>(
ctrl: &mut TargetController<E>,
address: usize,
mode: Mode,
) -> Result<Ref<Breakpoint>, CouldNotCreateBreakpoint>
where
E: Executing,
{
let saved_byte = ctrl.read(address, 1)?;
ctrl.write(address, &[TRAP_OPCODE])?;
let brk = Breakpoint {
address,
saved_byte: saved_byte[0],
mode,
id: new_breakpoint_id(),
};
ctrl.register_breakpoint(brk)
}
impl Breakpoint {
#[must_use]
pub const fn address(&self) -> usize {
self.address
}
#[must_use]
pub const fn mode(&self) -> Mode {
self.mode
}
#[must_use]
pub const fn saved_byte(&self) -> u8 {
self.saved_byte
}
#[must_use]
pub const fn id(&self) -> BreakpointId {
self.id
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use anyhow::Error as AnyError;
use crate::process::spawn_process;
use crate::run::Reason;
use super::*;
const MAIN_FUNCTION: usize = 0x0040_113c;
const HELLO_FUNCTION: usize = 0x0040_1126;
#[test]
fn use_one_shot_breakpoint() -> Result<(), AnyError> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/say_hello_no_pie");
let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
let mut ctrl_start = process.wait()?.assume_alive()?;
{
let brk = breakpoint(&mut ctrl_start, MAIN_FUNCTION, Mode::OneShot)?;
let id = brk.id();
assert_eq!(
*brk,
Breakpoint {
address: MAIN_FUNCTION,
saved_byte: 0x55,
mode: Mode::OneShot,
id
}
);
}
let process = ctrl_start.resume()?;
let ctrl_main = process.wait()?.assume_alive()?;
let regs = ctrl_main.get_registers()?;
assert_eq!(MAIN_FUNCTION + 1, regs.rip as usize);
let process = ctrl_main.resume()?;
let state = process.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
#[test]
fn resume_and_do_things() -> Result<(), AnyError> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/say_hello_no_pie");
let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
let mut ctrl_start = process.wait()?.assume_alive()?;
{
let brk_main = breakpoint(&mut ctrl_start, MAIN_FUNCTION, Mode::Persistent)?;
let id_main = brk_main.id();
assert_eq!(
*brk_main,
Breakpoint {
address: MAIN_FUNCTION,
saved_byte: 0x55,
mode: Mode::Persistent,
id: id_main
}
);
}
{
let brk_hello = breakpoint(&mut ctrl_start, HELLO_FUNCTION, Mode::OneShot)?;
let id_hello = brk_hello.id();
assert_eq!(
*brk_hello,
Breakpoint {
address: HELLO_FUNCTION,
saved_byte: 0x55,
mode: Mode::OneShot,
id: id_hello
}
);
}
let process = ctrl_start.resume()?;
let ctrl_main = process.wait()?.assume_alive()?;
let regs = ctrl_main.get_registers()?;
assert_eq!(MAIN_FUNCTION + 1, regs.rip as usize);
let process = ctrl_main.resume()?;
let ctrl_hello = process.wait()?.assume_alive()?;
let regs = ctrl_hello.get_registers()?;
assert_eq!(HELLO_FUNCTION + 1, regs.rip as usize);
let process = ctrl_hello.resume()?;
let state = process.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
#[test]
fn new_breakpoint_is_registered() -> Result<(), AnyError> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/say_hello_no_pie");
let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
let mut ctrl = process.wait()?.assume_alive()?;
let id = breakpoint(&mut ctrl, MAIN_FUNCTION, Mode::OneShot)?.id();
let brks: Vec<Ref<Breakpoint>> = ctrl.process().breakpoints().collect();
assert_eq!(brks.len(), 1);
assert_eq!(brks[0].id(), id);
Ok(())
}
#[test]
fn singlestep_over_breakpoint() -> Result<(), AnyError> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/say_hello_no_pie");
let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
let mut ctrl_start = process.wait()?.assume_alive()?;
breakpoint(&mut ctrl_start, MAIN_FUNCTION, Mode::OneShot)?;
let process = ctrl_start.resume()?;
let mut ctrl = process.wait()?.assume_alive()?;
let first_regs = ctrl.get_registers()?;
ctrl.singlestep()?;
let mut second_regs = ctrl.get_registers()?;
second_regs.rsp += 8;
assert_eq!(first_regs, second_regs);
Ok(())
}
#[test]
fn resume_after_breakpoint_removal() -> Result<(), AnyError> {
let mut path_buf = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path_buf.push("resources/test/say_hello_no_pie");
let process = spawn_process::<_, _, &str>(path_buf, vec![])?;
let mut ctrl_start = process.wait()?.assume_alive()?;
let id = breakpoint(&mut ctrl_start, MAIN_FUNCTION, Mode::Persistent)?.id();
let process = ctrl_start.resume()?;
let mut ctrl = process.wait()?.assume_alive()?;
let regs = ctrl.get_registers()?;
assert_eq!(*ctrl.reason(), Reason::Breakpoint(id));
assert_eq!(regs.rip as usize - 1, MAIN_FUNCTION);
assert!(ctrl.breakpoint().is_some());
assert_eq!(ctrl.process().breakpoints().count(), 1);
{
let brk_opt = ctrl.breakpoint();
assert!(brk_opt.is_some());
let brk = brk_opt.unwrap();
assert_eq!(brk.id(), id);
assert_eq!(brk.saved_byte(), 0x55);
assert_eq!(brk.address(), MAIN_FUNCTION);
}
ctrl.remove_breakpoint(id)?;
let bytes = ctrl.read(MAIN_FUNCTION, 4)?;
assert_eq!(bytes, vec![0x55, 0x48, 0x89, 0xe5]);
assert_eq!(ctrl.process().breakpoints().count(), 0);
let process = ctrl.resume()?;
assert_eq!(process.breakpoints().count(), 0);
let state = process.wait()?;
assert!(state.has_exited(), "{}", state.reason());
Ok(())
}
}