use std::{collections::HashMap, sync::Arc};
use pelite::pe64::{Pe, PeView, image::IMAGE_SCN_MEM_EXECUTE};
use crate::backend::MemoryOps;
use crate::dbg_backend::{
DebugBackend, DebugCapability, HW_BREAKPOINT_SLOTS, HwBreakpointAccess, WatchpointAccess,
validate_hw_breakpoint,
};
use crate::error::{Error, Result};
use crate::expr::Expr;
use crate::guest::{ModuleInfo, ProcessInfo, read_pe_image};
use crate::memory::AddressSpace;
use crate::target::Target;
use crate::types::{Dtb, VirtAddr};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HardwareBreakpoint {
pub access: HwBreakpointAccess,
pub len: u8,
pub slot: u8,
}
#[derive(Debug, Clone)]
pub struct Breakpoint {
pub id: u32,
pub address: VirtAddr,
pub enabled: bool,
pub symbol: Option<String>,
pub scope: BreakpointScope,
pub condition: Option<String>,
pub condition_expr: Option<Arc<Expr>>,
pub temporary: bool,
pub hardware: Option<HardwareBreakpoint>,
backend: BreakpointBackend,
}
impl Breakpoint {
pub fn watchpoint(&self) -> Option<(WatchpointAccess, u8)> {
let hardware = self.hardware?;
let access = match hardware.access {
HwBreakpointAccess::Write => WatchpointAccess::Write,
HwBreakpointAccess::ReadWrite => WatchpointAccess::ReadWrite,
HwBreakpointAccess::Execute => return None,
};
Some((access, hardware.len))
}
pub fn watch_access_name(&self) -> Option<&'static str> {
self.watchpoint().map(|(access, _)| access.name())
}
pub fn watch_length(&self) -> Option<u8> {
self.watchpoint().map(|(_, length)| length)
}
pub fn evaluate_condition(&self, target: &Target) -> Result<bool> {
match &self.condition_expr {
Some(expr) => Ok(expr.resolve(target)?.0 != 0),
None => Ok(true),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BreakpointScope {
Kernel,
Process { pid: u64, dtb: Dtb, name: String },
}
impl BreakpointScope {
fn matches_cr3(&self, cr3: u64) -> bool {
const CR3_PAGE_MASK: u64 = 0x000F_FFFF_FFFF_F000;
match self {
Self::Kernel => true,
Self::Process { dtb, .. } => (cr3 & CR3_PAGE_MASK) == (*dtb & CR3_PAGE_MASK),
}
}
pub fn label(&self) -> String {
match self {
Self::Kernel => "global".to_string(),
Self::Process { pid, name, .. } => format!("{name} ({pid})"),
}
}
}
#[derive(Debug, Clone)]
enum BreakpointBackend {
Kernel { original_byte: u8 },
GuestMemoryPatch { original_byte: u8 },
Hardware,
}
impl BreakpointBackend {
fn original_byte(&self) -> u8 {
match self {
Self::Kernel { original_byte } | Self::GuestMemoryPatch { original_byte } => {
*original_byte
}
Self::Hardware => 0,
}
}
}
#[derive(Default)]
pub struct BreakpointManager {
breakpoints: HashMap<u32, Breakpoint>,
next_id: u32,
}
impl BreakpointManager {
pub fn new() -> Self {
Self {
breakpoints: HashMap::new(),
next_id: 0,
}
}
#[cfg(test)]
pub(crate) fn insert_for_test(
&mut self,
id: u32,
address: VirtAddr,
enabled: bool,
hardware: Option<HardwareBreakpoint>,
) {
let backend = match hardware {
Some(_) => BreakpointBackend::Hardware,
None => BreakpointBackend::Kernel {
original_byte: 0x90,
},
};
self.breakpoints.insert(
id,
Breakpoint {
id,
address,
enabled,
symbol: None,
scope: BreakpointScope::Kernel,
condition: None,
condition_expr: None,
temporary: false,
hardware,
backend,
},
);
}
pub fn add(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
address: VirtAddr,
symbol: Option<String>,
condition: Option<String>,
) -> Result<u32> {
self.add_code(client, debugger, address, symbol, condition, false)
}
pub fn add_temporary_code(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
address: VirtAddr,
) -> Result<u32> {
self.add_code(client, debugger, address, None, None, true)
}
fn add_code(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
address: VirtAddr,
symbol: Option<String>,
condition: Option<String>,
temporary: bool,
) -> Result<u32> {
let condition_expr = Self::compile_condition(condition.as_deref())?;
let scope = Self::scope_for_current_context(debugger);
let caps = client.capabilities();
if matches!(scope, BreakpointScope::Process { .. })
&& !caps
.iter()
.any(|c| c.capability == DebugCapability::UserModeBreakpoints && c.supported)
{
return Err(Error::NotSupported);
}
if matches!(scope, BreakpointScope::Kernel)
&& !caps
.iter()
.any(|c| c.capability == DebugCapability::KernelBreakpoints && c.supported)
{
return Err(Error::NotSupported);
}
Self::validate_breakpoint_target(debugger, address)?;
let backend = Self::install_breakpoint(client, debugger, address, &scope)?;
let id = self.next_id;
self.next_id += 1;
let bp = Breakpoint {
id,
address,
enabled: true,
symbol,
scope,
condition,
condition_expr,
temporary,
hardware: None,
backend,
};
self.breakpoints.insert(id, bp);
Ok(id)
}
pub fn add_hardware(
&mut self,
client: &mut dyn DebugBackend,
address: VirtAddr,
access: HwBreakpointAccess,
len: u8,
symbol: Option<String>,
condition: Option<String>,
) -> Result<u32> {
let condition_expr = Self::compile_condition(condition.as_deref())?;
if !client.supports_watchpoints() {
return Err(Error::NotSupported);
}
validate_hw_breakpoint(access, len, address.0)?;
let slot = self.free_hardware_slot()?;
client.set_hardware_breakpoint(slot, address.0, access, len)?;
let id = self.next_id;
self.next_id += 1;
let bp = Breakpoint {
id,
address,
enabled: true,
symbol,
scope: BreakpointScope::Kernel,
condition,
condition_expr,
temporary: false,
hardware: Some(HardwareBreakpoint { access, len, slot }),
backend: BreakpointBackend::Hardware,
};
self.breakpoints.insert(id, bp);
Ok(id)
}
fn compile_condition(condition: Option<&str>) -> Result<Option<Arc<Expr>>> {
condition
.map(Expr::parse)
.transpose()
.map(|expr| expr.map(Arc::new))
}
fn free_hardware_slot(&self) -> Result<u8> {
(0..HW_BREAKPOINT_SLOTS)
.find(|slot| {
!self
.breakpoints
.values()
.any(|bp| bp.hardware.is_some_and(|hw| hw.slot == *slot))
})
.ok_or_else(|| {
Error::Rsp(format!(
"all {HW_BREAKPOINT_SLOTS} hardware breakpoint slots are in use"
))
})
}
pub fn remove(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
id: u32,
) -> Result<()> {
self.remove_if_uninstalled(id, |bp| Self::uninstall_breakpoint(client, debugger, bp))
}
fn remove_if_uninstalled(
&mut self,
id: u32,
uninstall: impl FnOnce(&Breakpoint) -> Result<()>,
) -> Result<()> {
let bp = self
.breakpoints
.get(&id)
.cloned()
.ok_or(Error::BPNotFound(id))?;
if bp.enabled {
uninstall(&bp)?;
}
self.breakpoints.remove(&id);
if self.breakpoints.is_empty() {
self.next_id = 0;
}
Ok(())
}
pub fn discard(&mut self, id: u32) -> Result<Breakpoint> {
let bp = self.breakpoints.remove(&id).ok_or(Error::BPNotFound(id))?;
if self.breakpoints.is_empty() {
self.next_id = 0;
}
Ok(bp)
}
pub fn enable(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
id: u32,
) -> Result<()> {
let bp = self.breakpoints.get_mut(&id).ok_or(Error::BPNotFound(id))?;
if bp.enabled {
return Ok(());
}
Self::install_existing_breakpoint(client, debugger, bp)?;
bp.enabled = true;
Ok(())
}
pub fn disable(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
id: u32,
) -> Result<()> {
let bp = self.breakpoints.get_mut(&id).ok_or(Error::BPNotFound(id))?;
if !bp.enabled {
return Ok(());
}
Self::uninstall_breakpoint(client, debugger, bp)?;
bp.enabled = false;
Ok(())
}
pub fn disable_guest_memory_patch_in_address_space(
&mut self,
client: &mut dyn DebugBackend,
debugger: &Target,
id: u32,
dtb: Dtb,
) -> Result<()> {
let bp = self.breakpoints.get_mut(&id).ok_or(Error::BPNotFound(id))?;
if !bp.enabled {
return Ok(());
}
match bp.backend {
BreakpointBackend::GuestMemoryPatch { original_byte } => {
let memory = AddressSpace::new(&debugger.phys, dtb);
memory.write_bytes(bp.address, &[original_byte])?;
client.note_breakpoint_uninstalled(bp.address.0);
bp.enabled = false;
Ok(())
}
BreakpointBackend::Kernel { .. } => Err(Error::Rsp(
"cannot address-space-disable a kernel breakpoint".into(),
)),
BreakpointBackend::Hardware => Err(Error::Rsp(
"cannot address-space-disable a hardware breakpoint".into(),
)),
}
}
pub fn list(&self) -> Vec<&Breakpoint> {
let mut bps: Vec<_> = self.breakpoints.values().collect();
bps.sort_by_key(|bp| bp.id);
bps
}
pub fn has_enabled_breakpoints(&self) -> bool {
self.breakpoints.values().any(|bp| bp.enabled)
}
pub fn has_enabled_hardware_breakpoints(&self) -> bool {
self.breakpoints
.values()
.any(|bp| bp.enabled && bp.hardware.is_some())
}
pub fn hardware_breakpoint_for_slot(&self, slot: u8) -> Option<Breakpoint> {
self.breakpoints
.values()
.find(|bp| bp.enabled && bp.hardware.is_some_and(|hw| hw.slot == slot))
.cloned()
}
pub fn clear_hardware_slots(&self, client: &mut dyn DebugBackend) {
for bp in self.breakpoints.values() {
if let Some(hw) = bp.hardware {
let _ = client.clear_hardware_breakpoint(hw.slot);
}
}
}
pub fn refresh_enabled(&self, client: &mut dyn DebugBackend, debugger: &Target) -> Result<()> {
let mut enabled: Vec<_> = self
.breakpoints
.values()
.filter(|bp| bp.enabled && bp.hardware.is_none())
.collect();
enabled.sort_by_key(|bp| bp.id);
for bp in enabled {
let _ = Self::uninstall_breakpoint(client, debugger, bp);
Self::install_existing_breakpoint(client, debugger, bp)?;
}
Ok(())
}
pub fn check_breakpoint_hit(&self, rip: u64, cr3: u64) -> BreakpointHitResult {
for bp in self.breakpoints.values() {
if bp.hardware.is_none()
&& bp.address.0 == rip
&& bp.enabled
&& bp.scope.matches_cr3(cr3)
{
return BreakpointHitResult::Hit(bp.clone());
}
}
BreakpointHitResult::NotBreakpoint
}
pub fn enabled_breakpoint_id_for_current_context(
&self,
debugger: &Target,
address: VirtAddr,
) -> Option<u32> {
let scope = Self::scope_for_current_context(debugger);
self.enabled_software_breakpoint_id(&scope, address)
}
fn enabled_software_breakpoint_id(
&self,
scope: &BreakpointScope,
address: VirtAddr,
) -> Option<u32> {
self.breakpoints
.values()
.find(|bp| {
bp.enabled && bp.hardware.is_none() && bp.address == address && &bp.scope == scope
})
.map(|bp| bp.id)
}
pub fn mask_breakpoint_bytes(&self, start: VirtAddr, buf: &mut [u8], cr3: u64) {
let end = start.0.wrapping_add(buf.len() as u64);
for bp in self.breakpoints.values() {
if !bp.enabled || bp.hardware.is_some() || !bp.scope.matches_cr3(cr3) {
continue;
}
if bp.address.0 < start.0 || bp.address.0 >= end {
continue;
}
buf[(bp.address.0 - start.0) as usize] = bp.backend.original_byte();
}
}
pub fn breakpoint_id_at_address(&self, rip: u64) -> Option<u32> {
self.breakpoints
.values()
.find(|bp| bp.enabled && bp.hardware.is_none() && bp.address.0 == rip)
.map(|bp| bp.id)
}
fn scope_for_current_context(debugger: &Target) -> BreakpointScope {
match &debugger.current_process_info {
Some(ProcessInfo { pid, name, dtb, .. }) => BreakpointScope::Process {
pid: *pid,
dtb: *dtb,
name: name.clone(),
},
None => BreakpointScope::Kernel,
}
}
fn install_breakpoint(
client: &mut dyn DebugBackend,
debugger: &Target,
address: VirtAddr,
scope: &BreakpointScope,
) -> Result<BreakpointBackend> {
match scope {
BreakpointScope::Kernel => {
let memory = AddressSpace::new(&debugger.phys, debugger.current_dtb());
let mut original = [0u8; 1];
memory.read_bytes(address, &mut original)?;
client.set_breakpoint(address.0)?;
Ok(BreakpointBackend::Kernel {
original_byte: original[0],
})
}
BreakpointScope::Process { dtb, .. } => {
let memory = AddressSpace::new(&debugger.phys, *dtb);
let mut original = [0u8; 1];
memory.read_bytes(address, &mut original)?;
memory.write_bytes(address, &[0xcc])?;
client.note_breakpoint_installed(address.0);
Ok(BreakpointBackend::GuestMemoryPatch {
original_byte: original[0],
})
}
}
}
fn install_existing_breakpoint(
client: &mut dyn DebugBackend,
debugger: &Target,
bp: &Breakpoint,
) -> Result<()> {
match (&bp.scope, &bp.backend) {
(BreakpointScope::Kernel, BreakpointBackend::Kernel { .. }) => {
client.set_breakpoint(bp.address.0)
}
(BreakpointScope::Process { dtb, .. }, BreakpointBackend::GuestMemoryPatch { .. }) => {
let memory = AddressSpace::new(&debugger.phys, *dtb);
memory.write_bytes(bp.address, &[0xcc])?;
client.note_breakpoint_installed(bp.address.0);
Ok(())
}
(_, BreakpointBackend::Hardware) => match bp.hardware {
Some(hw) => {
client.set_hardware_breakpoint(hw.slot, bp.address.0, hw.access, hw.len)
}
None => Err(Error::Rsp("hardware breakpoint missing parameters".into())),
},
_ => Err(Error::Rsp("breakpoint backend/scope mismatch".into())),
}
}
fn uninstall_breakpoint(
client: &mut dyn DebugBackend,
debugger: &Target,
bp: &Breakpoint,
) -> Result<()> {
match (&bp.scope, &bp.backend) {
(BreakpointScope::Kernel, BreakpointBackend::Kernel { .. }) => {
client.remove_breakpoint(bp.address.0)
}
(
BreakpointScope::Process { dtb, .. },
BreakpointBackend::GuestMemoryPatch { original_byte },
) => {
let memory = AddressSpace::new(&debugger.phys, *dtb);
memory.write_bytes(bp.address, &[*original_byte])?;
client.note_breakpoint_uninstalled(bp.address.0);
Ok(())
}
(_, BreakpointBackend::Hardware) => match bp.hardware {
Some(hw) => client.clear_hardware_breakpoint(hw.slot),
None => Err(Error::Rsp("hardware breakpoint missing parameters".into())),
},
_ => Err(Error::Rsp("breakpoint backend/scope mismatch".into())),
}
}
fn validate_breakpoint_target(debugger: &Target, address: VirtAddr) -> Result<()> {
let module = Self::find_kernel_module_containing_address(debugger, address);
let memory = AddressSpace::new(&debugger.phys, debugger.current_dtb());
let translation = memory
.virt_to_phys(address)?
.ok_or(Error::BadVirtualAddress(address))?;
if translation.nx {
let context = module
.as_ref()
.map(|module| module.short_name.as_str())
.unwrap_or("unknown");
return Err(Error::Rsp(format!(
"refusing breakpoint at {:#x}: target page is non-executable ({})",
address.0, context
)));
}
if let Some(module) = module {
let image = read_pe_image(module.base_address, &memory)?;
let view = PeView::from_bytes(image.as_slice())?;
let rva = address.0.saturating_sub(module.base_address.0) as u32;
let in_executable_section = view.section_headers().iter().any(|section| {
let size = section.VirtualSize.max(section.SizeOfRawData);
size != 0
&& section.Characteristics & IMAGE_SCN_MEM_EXECUTE != 0
&& rva >= section.VirtualAddress
&& rva < section.VirtualAddress.saturating_add(size)
});
if !in_executable_section {
return Err(Error::Rsp(format!(
"refusing breakpoint at {:#x}: address falls in non-executable section of {}",
address.0, module.short_name
)));
}
}
Ok(())
}
fn find_kernel_module_containing_address(
debugger: &Target,
address: VirtAddr,
) -> Option<ModuleInfo> {
debugger
.kernel_modules()
.ok()?
.into_iter()
.find(|module| module.contains_address(address))
}
}
#[derive(Debug)]
pub enum BreakpointHitResult {
Hit(Breakpoint),
NotBreakpoint,
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::{
Breakpoint, BreakpointBackend, BreakpointHitResult, BreakpointManager, BreakpointScope,
HardwareBreakpoint,
};
use crate::dbg_backend::{DebugBackend, HwBreakpointAccess, StopEvent, WatchpointAccess};
use crate::error::{Error, Result};
use crate::expr::{Expr, ExprBinaryOp};
use crate::gdb::RegisterMap;
use crate::types::VirtAddr;
#[test]
fn failed_uninstall_keeps_breakpoint_managed_for_retry() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(
7,
VirtAddr(0x1000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Execute,
len: 1,
slot: 0,
}),
);
let result = manager.remove_if_uninstalled(7, |_| {
Err(Error::Kd("injected hardware clear failure".into()))
});
assert!(result.is_err());
assert_eq!(manager.list().len(), 1);
assert_eq!(manager.list()[0].id, 7);
assert!(manager.has_enabled_hardware_breakpoints());
}
#[test]
fn exposes_data_watches_without_transport_metadata() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(
7,
VirtAddr(0x2000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::ReadWrite,
len: 8,
slot: 2,
}),
);
let breakpoint = manager.list().into_iter().find(|bp| bp.id == 7).unwrap();
assert_eq!(
breakpoint.watchpoint(),
Some((WatchpointAccess::ReadWrite, 8))
);
}
#[test]
fn detects_breakpoint_hit_at_exact_rip() {
let mut manager = BreakpointManager::new();
manager.breakpoints.insert(
0,
Breakpoint {
id: 0,
address: VirtAddr(0x1000),
enabled: true,
symbol: None,
scope: BreakpointScope::Kernel,
condition: None,
condition_expr: None,
temporary: false,
hardware: None,
backend: BreakpointBackend::Kernel {
original_byte: 0x90,
},
},
);
match manager.check_breakpoint_hit(0x1000, 0) {
BreakpointHitResult::Hit(bp) => assert_eq!(bp.id, 0),
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn process_breakpoint_hit_requires_matching_cr3() {
let mut manager = BreakpointManager::new();
manager.breakpoints.insert(
0,
Breakpoint {
id: 0,
address: VirtAddr(0x7ff7_1234_1000),
enabled: true,
symbol: None,
scope: BreakpointScope::Process {
pid: 42,
dtb: 0x1234_5000,
name: "user.exe".to_string(),
},
condition: None,
condition_expr: None,
temporary: false,
hardware: None,
backend: BreakpointBackend::GuestMemoryPatch {
original_byte: 0x90,
},
},
);
assert!(matches!(
manager.check_breakpoint_hit(0x7ff7_1234_1000, 0x1234_5000),
BreakpointHitResult::Hit(_)
));
assert!(matches!(
manager.check_breakpoint_hit(0x7ff7_1234_1000, 0x1234_5fff),
BreakpointHitResult::Hit(_)
));
assert!(matches!(
manager.check_breakpoint_hit(0x7ff7_1234_1000, 0x9999_9000),
BreakpointHitResult::NotBreakpoint
));
assert!(matches!(
manager.check_breakpoint_hit(0x7ff7_1234_1000, 0x1234_4000),
BreakpointHitResult::NotBreakpoint
));
}
#[test]
fn hardware_breakpoint_is_ignored_by_int3_hit_predicates() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(
0,
VirtAddr(0x2000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Write,
len: 4,
slot: 1,
}),
);
assert!(matches!(
manager.check_breakpoint_hit(0x2000, 0),
BreakpointHitResult::NotBreakpoint
));
assert_eq!(manager.breakpoint_id_at_address(0x2000), None);
}
#[test]
fn has_enabled_hardware_breakpoints_tracks_enabled_hw_bps() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(0, VirtAddr(0x1000), true, None);
assert!(!manager.has_enabled_hardware_breakpoints());
manager.insert_for_test(
1,
VirtAddr(0x2000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Write,
len: 4,
slot: 1,
}),
);
assert!(manager.has_enabled_hardware_breakpoints());
manager.breakpoints.get_mut(&1).unwrap().enabled = false;
assert!(!manager.has_enabled_hardware_breakpoints());
}
#[test]
fn hardware_breakpoint_for_slot_resolves_enabled_slot_only() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(
7,
VirtAddr(0x3000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::ReadWrite,
len: 8,
slot: 1,
}),
);
let found = manager
.hardware_breakpoint_for_slot(1)
.expect("slot 1 hw bp");
assert_eq!(found.id, 7);
assert_eq!(found.hardware.expect("hw params").slot, 1);
assert!(manager.hardware_breakpoint_for_slot(0).is_none());
manager.insert_for_test(
8,
VirtAddr(0x4000),
false,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Write,
len: 2,
slot: 0,
}),
);
assert!(manager.hardware_breakpoint_for_slot(0).is_none());
}
#[test]
fn software_and_hardware_breakpoint_coexist_at_same_address() {
let mut manager = BreakpointManager::new();
let addr = 0x5000;
manager.insert_for_test(0, VirtAddr(addr), true, None);
manager.insert_for_test(
1,
VirtAddr(addr),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Write,
len: 4,
slot: 1,
}),
);
match manager.check_breakpoint_hit(addr, 0) {
BreakpointHitResult::Hit(bp) => {
assert_eq!(bp.id, 0);
assert!(bp.hardware.is_none());
}
other => panic!("expected software hit, got {:?}", other),
}
assert_eq!(manager.breakpoint_id_at_address(addr), Some(0));
assert_eq!(
manager.enabled_software_breakpoint_id(&BreakpointScope::Kernel, VirtAddr(addr)),
Some(0)
);
manager.breakpoints.remove(&0);
assert_eq!(
manager.enabled_software_breakpoint_id(&BreakpointScope::Kernel, VirtAddr(addr)),
None
);
}
#[test]
fn condition_compiler_accepts_the_full_expression_grammar() {
let source = "$rax == 1 && ($rcx & 0xff) != 0";
let compiled = BreakpointManager::compile_condition(Some(source))
.unwrap()
.expect("compiled condition");
assert!(matches!(
compiled.as_ref(),
Expr::Binary(_, ExprBinaryOp::LogicalAnd, _)
));
assert!(
BreakpointManager::compile_condition(None)
.unwrap()
.is_none()
);
assert!(BreakpointManager::compile_condition(Some("$rax == ")).is_err());
}
struct SlotRecorder {
register_map: RegisterMap,
cleared: Vec<u8>,
}
impl SlotRecorder {
fn new() -> Self {
Self {
register_map: RegisterMap::default(),
cleared: Vec::new(),
}
}
}
impl DebugBackend for SlotRecorder {
fn register_map(&self) -> &RegisterMap {
&self.register_map
}
fn read_registers(&mut self) -> Result<Vec<u8>> {
Err(Error::NotSupported)
}
fn write_registers(&mut self, _data: &[u8]) -> Result<()> {
Err(Error::NotSupported)
}
fn set_breakpoint(&mut self, _addr: u64) -> Result<()> {
Err(Error::NotSupported)
}
fn remove_breakpoint(&mut self, _addr: u64) -> Result<()> {
Err(Error::NotSupported)
}
fn clear_hardware_breakpoint(&mut self, slot: u8) -> Result<()> {
self.cleared.push(slot);
Ok(())
}
fn continue_execution(&mut self) -> Result<()> {
Err(Error::NotSupported)
}
fn step(&mut self) -> Result<()> {
Err(Error::NotSupported)
}
fn interrupt(&mut self) -> Result<StopEvent> {
Err(Error::NotSupported)
}
fn wait_for_stop(&mut self) -> Result<StopEvent> {
Err(Error::NotSupported)
}
fn try_wait_for_stop(&mut self, _timeout: Duration) -> Result<Option<StopEvent>> {
Ok(None)
}
fn thread_list(&mut self) -> Result<Vec<String>> {
Err(Error::NotSupported)
}
fn set_current_thread(&mut self, _thread_id: &str) -> Result<()> {
Err(Error::NotSupported)
}
fn stopped_thread_id(&mut self) -> Result<String> {
Err(Error::NotSupported)
}
fn is_running(&self) -> bool {
false
}
}
#[test]
fn clear_hardware_slots_releases_every_hw_slot_and_skips_software() {
let mut manager = BreakpointManager::new();
manager.insert_for_test(
0,
VirtAddr(0x1000),
true,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Write,
len: 4,
slot: 2,
}),
);
manager.insert_for_test(
1,
VirtAddr(0x2000),
false,
Some(HardwareBreakpoint {
access: HwBreakpointAccess::Execute,
len: 1,
slot: 0,
}),
);
manager.insert_for_test(2, VirtAddr(0x3000), true, None);
let mut backend = SlotRecorder::new();
manager.clear_hardware_slots(&mut backend);
let mut cleared = backend.cleared.clone();
cleared.sort_unstable();
assert_eq!(cleared, vec![0, 2]);
}
}