use std::collections::HashMap;
use crate::error::{Error, Result};
use crate::process::Process;
use crate::types::VirtAddr;
const INT3: u8 = 0xCC;
#[derive(Debug)]
pub struct BreakpointSite {
addr: VirtAddr,
saved_byte: u8,
enabled: bool,
condition: Option<String>,
}
impl BreakpointSite {
pub fn enable(&mut self, proc: &Process) -> Result<()> {
if self.enabled {
return Ok(());
}
let word = proc.read_memory_word(self.addr)?;
self.saved_byte = (word & 0xFF) as u8;
let patched = (word & !0xFF) | INT3 as u64;
proc.write_memory_word(self.addr, patched)?;
self.enabled = true;
Ok(())
}
pub fn disable(&mut self, proc: &Process) -> Result<()> {
if !self.enabled {
return Ok(());
}
let word = proc.read_memory_word(self.addr)?;
let restored = (word & !0xFF) | self.saved_byte as u64;
proc.write_memory_word(self.addr, restored)?;
self.enabled = false;
Ok(())
}
pub fn addr(&self) -> VirtAddr {
self.addr
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn saved_byte(&self) -> u8 {
self.saved_byte
}
pub fn condition(&self) -> Option<&str> {
self.condition.as_deref()
}
pub fn set_condition(&mut self, cond: Option<String>) {
self.condition = cond;
}
}
pub struct BreakpointManager {
sites: HashMap<u64, BreakpointSite>,
next_id: u32,
}
impl BreakpointManager {
pub fn new() -> Self {
Self {
sites: HashMap::new(),
next_id: 1,
}
}
pub fn set(&mut self, proc: &Process, addr: VirtAddr) -> Result<u32> {
self.set_with_condition(proc, addr, None)
}
pub fn set_with_condition(
&mut self,
proc: &Process,
addr: VirtAddr,
condition: Option<String>,
) -> Result<u32> {
if self.sites.contains_key(&addr.addr()) {
return Err(Error::Breakpoint(format!(
"breakpoint already set at {}",
addr
)));
}
let mut site = BreakpointSite {
addr,
saved_byte: 0,
enabled: false,
condition,
};
site.enable(proc)?;
let id = self.next_id;
self.next_id += 1;
self.sites.insert(addr.addr(), site);
Ok(id)
}
pub fn remove(&mut self, proc: &Process, addr: VirtAddr) -> Result<()> {
let mut site = self
.sites
.remove(&addr.addr())
.ok_or_else(|| Error::Breakpoint(format!("no breakpoint at {}", addr)))?;
site.disable(proc)?;
Ok(())
}
pub fn get_at(&self, addr: VirtAddr) -> Option<&BreakpointSite> {
self.sites.get(&addr.addr())
}
pub fn get_at_mut(&mut self, addr: VirtAddr) -> Option<&mut BreakpointSite> {
self.sites.get_mut(&addr.addr())
}
pub fn step_over_breakpoint(
&mut self,
proc: &mut Process,
addr: VirtAddr,
) -> Result<()> {
if let Some(site) = self.sites.get_mut(&addr.addr()) {
if site.is_enabled() {
site.disable(proc)?;
proc.step_instruction()?;
proc.wait_on_signal()?;
site.enable(proc)?;
}
}
Ok(())
}
pub fn list(&self) -> impl Iterator<Item = &BreakpointSite> {
self.sites.values()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn int3_byte_patching_logic() {
let original_word: u64 = 0x00000000_EC834855;
let original_byte = (original_word & 0xFF) as u8;
assert_eq!(original_byte, 0x55);
let patched = (original_word & !0xFF) | INT3 as u64;
assert_eq!(patched & 0xFF, 0xCC);
assert_eq!(patched >> 8, original_word >> 8);
let restored = (patched & !0xFF) | original_byte as u64;
assert_eq!(restored, original_word);
}
#[test]
fn breakpoint_site_initial_state() {
let site = BreakpointSite {
addr: VirtAddr(0x401000),
saved_byte: 0,
enabled: false,
condition: None,
};
assert_eq!(site.addr(), VirtAddr(0x401000));
assert!(!site.is_enabled());
assert_eq!(site.saved_byte(), 0);
assert!(site.condition().is_none());
}
#[test]
fn breakpoint_site_with_condition() {
let mut site = BreakpointSite {
addr: VirtAddr(0x401000),
saved_byte: 0,
enabled: false,
condition: Some("x > 5".to_string()),
};
assert_eq!(site.condition(), Some("x > 5"));
site.set_condition(None);
assert!(site.condition().is_none());
}
#[test]
fn breakpoint_manager_id_assignment() {
let mgr = BreakpointManager::new();
assert_eq!(mgr.next_id, 1);
}
}