Upgrading from version 0.4 to 0.5 involves three main breaking changes related to how breakpoints, register access, and thread resumption are handled. While the core logic remains similar, the organizational structure of the traits has changed.
1. Consolidating Breakpoint IDETs
Breakpoint operations have moved from the top-level Target trait into a consolidated Breakpoints IDET.
- Old way: Implement
sw_breakpoint and hw_watchpoint directly on Target. - New way: Implement
breakpoints() on Target which returns BreakpointsOps. Then, implement the Breakpoints trait on your target type to provide sw_breakpoint() and hw_watchpoint() implementations. - Note:
SwBreakpoint methods (like add_sw_breakpoint) now include a new kind parameter (e.g., arch::arm::ArmBreakpointKind).
2. Moving Single-Register Access to a Separate Trait
Single register access is no longer part of the core SingleThreadOps but is moved to the SingleRegisterAccess IDET.
- Old way: Implement
read_register and write_register directly on SingleThreadOps. - New way: Implement
single_register_access() on SingleThreadOps to return SingleRegisterAccessOps. Then, implement the SingleRegisterAccess trait. - Note: The
read_register and write_register methods now include a tid parameter (which can be ignored on single-threaded systems).
3. Refactoring MultiThreadOps::resume API
The single resume method that used an Actions iterator has been replaced by a lifecycle-based approach to improve error handling and performance. Targets are now responsible for maintaining internal state (e.g., a HashMap<Tid, ResumeAction>) to map thread IDs to their respective actions.
The new lifecycle flow is:
set_resume_action: Called prior to resume to notify the target how a specific Tid should be resumed.set_resume_action_range_step (Optional): Implement MultiThreadRangeStepping if the target supports optimized range-stepping.resume: Called to trigger execution. The target uses its internal state to determine how to resume each thread.clear_resume_actions: Called after resume returns a ThreadStopReason to reset the per-tid actions.
// ==== 0.4.x ==== //
impl Target for Emu {
fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
Some(self)
}
fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
Some(self)
}
}
impl target::ext::breakpoints::SwBreakpoint for Emu {
fn add_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
fn remove_sw_breakpoint(&mut self, addr: u32) -> TargetResult<bool, Self> { ... }
}
impl target::ext::breakpoints::HwWatchpoint for Emu {
fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
}
// ==== 0.5.0 ==== //
impl Target for Emu {
// (New Method) //
fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
Some(self)
}
}
impl target::ext::breakpoints::Breakpoints for Emu {
fn sw_breakpoint(&mut self) -> Option<target::ext::breakpoints::SwBreakpointOps<Self>> {
Some(self)
}
fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
Some(self)
}
}
// (Almost Unchanged) //
impl target::ext::breakpoints::SwBreakpoint for Emu {
// /-- New `kind` parameter
// \/
fn add_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
fn remove_sw_breakpoint(&mut self, addr: u32, _kind: arch::arm::ArmBreakpointKind) -> TargetResult<bool, Self> { ... }
}
// (Unchanged) //
impl target::ext::breakpoints::HwWatchpoint for Emu {
fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult<bool, Self> { ... }
}
// ==== 0.4.x (Register Access) ====
impl SingleThreadOps for Emu {
fn read_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
fn write_register(&mut self, reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
}
// ==== 0.5.0 (Register Access) ====
impl SingleThreadOps for Emu {
// (New Method) //
fn single_register_access(&mut self) -> Option<target::ext::base::SingleRegisterAccessOps<(), Self>> {
Some(self)
}
}
impl target::ext::base::SingleRegisterAccess<()> for Emu {
// /-- New `tid` parameter (ignored on single-threaded systems)
// \/
fn read_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8]) -> TargetResult<(), Self> { ... }
fn write_register(&mut self, _tid: (), reg_id: arch::arm::reg::id::ArmCoreRegId, val: &[u8]) -> TargetResult<(), Self> { ... }
}