Support watchpoints
This commit is contained in:
parent
fd7298d24e
commit
af9a4ae8ee
|
@ -18,8 +18,9 @@ use egui_toast::{Toast, ToastKind, ToastOptions};
|
||||||
|
|
||||||
use crate::{audio::Audio, graphics::TextureSink};
|
use crate::{audio::Audio, graphics::TextureSink};
|
||||||
use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE};
|
use shrooms_vb_core::{Sim, StopReason, EXPECTED_FRAME_SIZE};
|
||||||
pub use shrooms_vb_core::{VBKey, VBRegister};
|
pub use shrooms_vb_core::{VBKey, VBRegister, VBWatchpointType};
|
||||||
|
|
||||||
|
mod address_set;
|
||||||
mod shrooms_vb_core;
|
mod shrooms_vb_core;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
|
@ -421,6 +422,9 @@ impl Emulator {
|
||||||
if let Some(reason) = sim.stop_reason() {
|
if let Some(reason) = sim.stop_reason() {
|
||||||
let stop_reason = match reason {
|
let stop_reason = match reason {
|
||||||
StopReason::Stepped => DebugStopReason::Trace,
|
StopReason::Stepped => DebugStopReason::Trace,
|
||||||
|
StopReason::Watchpoint(watch, address) => {
|
||||||
|
DebugStopReason::Watchpoint(watch, address)
|
||||||
|
}
|
||||||
StopReason::Breakpoint => DebugStopReason::Breakpoint,
|
StopReason::Breakpoint => DebugStopReason::Breakpoint,
|
||||||
};
|
};
|
||||||
self.debug_stop(sim_id, stop_reason);
|
self.debug_stop(sim_id, stop_reason);
|
||||||
|
@ -547,6 +551,18 @@ impl Emulator {
|
||||||
};
|
};
|
||||||
sim.remove_breakpoint(address);
|
sim.remove_breakpoint(address);
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::AddWatchpoint(sim_id, address, length, watch) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.add_watchpoint(address, length, watch);
|
||||||
|
}
|
||||||
|
EmulatorCommand::RemoveWatchpoint(sim_id, address, length, watch) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.remove_watchpoint(address, length, watch);
|
||||||
|
}
|
||||||
EmulatorCommand::SetAudioEnabled(p1, p2) => {
|
EmulatorCommand::SetAudioEnabled(p1, p2) => {
|
||||||
self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release);
|
self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release);
|
||||||
self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release);
|
self.audio_on[SimId::Player2.to_index()].store(p2, Ordering::Release);
|
||||||
|
@ -613,6 +629,8 @@ pub enum EmulatorCommand {
|
||||||
ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
||||||
AddBreakpoint(SimId, u32),
|
AddBreakpoint(SimId, u32),
|
||||||
RemoveBreakpoint(SimId, u32),
|
RemoveBreakpoint(SimId, u32),
|
||||||
|
AddWatchpoint(SimId, u32, usize, VBWatchpointType),
|
||||||
|
RemoveWatchpoint(SimId, u32, usize, VBWatchpointType),
|
||||||
SetAudioEnabled(bool, bool),
|
SetAudioEnabled(bool, bool),
|
||||||
Link,
|
Link,
|
||||||
Unlink,
|
Unlink,
|
||||||
|
@ -645,6 +663,8 @@ pub enum DebugStopReason {
|
||||||
Trace,
|
Trace,
|
||||||
// We hit a breakpoint
|
// We hit a breakpoint
|
||||||
Breakpoint,
|
Breakpoint,
|
||||||
|
// We hit a watchpoint
|
||||||
|
Watchpoint(VBWatchpointType, u32),
|
||||||
// The debugger told us to pause
|
// The debugger told us to pause
|
||||||
Trapped,
|
Trapped,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
use std::{
|
||||||
|
collections::{BTreeMap, HashSet},
|
||||||
|
ops::Bound,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct AddressSet {
|
||||||
|
ranges: HashSet<(u32, usize)>,
|
||||||
|
bounds: BTreeMap<u32, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddressSet {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, address: u32, length: usize) {
|
||||||
|
if length == 0 || !self.ranges.insert((address, length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let end = (address as usize)
|
||||||
|
.checked_add(length)
|
||||||
|
.and_then(|e| u32::try_from(e).ok());
|
||||||
|
|
||||||
|
if let Some(end) = end {
|
||||||
|
let val_before = self.bounds.range(..=end).next_back().map_or(0, |(_, &v)| v);
|
||||||
|
self.bounds.insert(end, val_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
let val_before = self
|
||||||
|
.bounds
|
||||||
|
.range(..address)
|
||||||
|
.next_back()
|
||||||
|
.map_or(0, |(_, &v)| v);
|
||||||
|
if let Some(&val_at) = self.bounds.get(&address) {
|
||||||
|
if val_before == val_at + 1 {
|
||||||
|
self.bounds.remove(&address);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.bounds.insert(address, val_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_bound = Bound::Included(address);
|
||||||
|
let end_bound = match end {
|
||||||
|
Some(e) => Bound::Excluded(e),
|
||||||
|
None => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, val) in self.bounds.range_mut((start_bound, end_bound)) {
|
||||||
|
*val += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, address: u32, length: usize) {
|
||||||
|
if !self.ranges.remove(&(address, length)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let end = (address as usize)
|
||||||
|
.checked_add(length)
|
||||||
|
.and_then(|e| u32::try_from(e).ok());
|
||||||
|
|
||||||
|
if let Some(end) = end {
|
||||||
|
let val_before = self.bounds.range(..end).next_back().map_or(0, |(_, &v)| v);
|
||||||
|
if let Some(&val_at) = self.bounds.get(&end) {
|
||||||
|
if val_at + 1 == val_before {
|
||||||
|
self.bounds.remove(&end);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.bounds.insert(end, val_before);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let val_before = self
|
||||||
|
.bounds
|
||||||
|
.range(..address)
|
||||||
|
.next_back()
|
||||||
|
.map_or(0, |(_, &v)| v);
|
||||||
|
if let Some(&val_at) = self.bounds.get(&address) {
|
||||||
|
if val_before + 1 == val_at {
|
||||||
|
self.bounds.remove(&address);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.bounds.insert(address, val_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_bound = Bound::Included(address);
|
||||||
|
let end_bound = match end {
|
||||||
|
Some(e) => Bound::Excluded(e),
|
||||||
|
None => Bound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, val) in self.bounds.range_mut((start_bound, end_bound)) {
|
||||||
|
*val -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.ranges.clear();
|
||||||
|
self.bounds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.bounds.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, address: u32) -> bool {
|
||||||
|
self.bounds
|
||||||
|
.range(..=address)
|
||||||
|
.next_back()
|
||||||
|
.is_some_and(|(_, &val)| val > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::AddressSet;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_include_addresses_when_empty() {
|
||||||
|
let set = AddressSet::new();
|
||||||
|
assert!(set.is_empty());
|
||||||
|
assert!(!set.contains(0x13374200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_include_addresses_when_full() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x00000000, 0x100000000);
|
||||||
|
assert!(set.contains(0x13374200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_ignore_empty_address_ranges() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 0);
|
||||||
|
assert!(set.is_empty());
|
||||||
|
assert!(!set.contains(0x13374200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_add_addresses_idempotently() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 1);
|
||||||
|
set.add(0x13374200, 1);
|
||||||
|
set.remove(0x13374200, 1);
|
||||||
|
assert!(set.is_empty());
|
||||||
|
assert!(!set.contains(0x13374200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_remove_addresses_idempotently() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 1);
|
||||||
|
set.remove(0x13374200, 1);
|
||||||
|
set.remove(0x13374200, 1);
|
||||||
|
assert!(set.is_empty());
|
||||||
|
assert!(!set.contains(0x13374200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_report_address_in_range() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 4);
|
||||||
|
assert!(!set.contains(0x133741ff));
|
||||||
|
for address in 0x13374200..0x13374204 {
|
||||||
|
assert!(set.contains(address));
|
||||||
|
}
|
||||||
|
assert!(!set.contains(0x13374204));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_overlapping_addresses() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 4);
|
||||||
|
set.add(0x13374201, 1);
|
||||||
|
set.add(0x13374202, 2);
|
||||||
|
|
||||||
|
assert!(!set.contains(0x133741ff));
|
||||||
|
for address in 0x13374200..0x13374204 {
|
||||||
|
assert!(set.contains(address));
|
||||||
|
}
|
||||||
|
assert!(!set.contains(0x13374204));
|
||||||
|
|
||||||
|
set.remove(0x13374200, 4);
|
||||||
|
assert!(!set.contains(0x13374200));
|
||||||
|
for address in 0x13374201..0x13374204 {
|
||||||
|
assert!(set.contains(address));
|
||||||
|
}
|
||||||
|
assert!(!set.contains(0x13374204));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_allow_removing_overlapped_address_ranges() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 8);
|
||||||
|
set.add(0x13374204, 8);
|
||||||
|
set.remove(0x13374204, 8);
|
||||||
|
|
||||||
|
for address in 0x13374200..0x13374208 {
|
||||||
|
assert!(set.contains(address));
|
||||||
|
}
|
||||||
|
for address in 0x13374208..0x1337420c {
|
||||||
|
assert!(!set.contains(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_merge_adjacent_ranges() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 4);
|
||||||
|
set.add(0x13374204, 4);
|
||||||
|
set.add(0x13374208, 4);
|
||||||
|
set.add(0x1337420c, 4);
|
||||||
|
|
||||||
|
assert!(!set.contains(0x133741ff));
|
||||||
|
for address in 0x13374200..0x13374210 {
|
||||||
|
assert!(set.contains(address));
|
||||||
|
}
|
||||||
|
assert!(!set.contains(0x13374210));
|
||||||
|
|
||||||
|
set.remove(0x13374200, 4);
|
||||||
|
set.remove(0x13374204, 4);
|
||||||
|
set.remove(0x13374208, 4);
|
||||||
|
set.remove(0x1337420c, 4);
|
||||||
|
|
||||||
|
assert!(set.is_empty());
|
||||||
|
for address in 0x133741ff..=0x13374210 {
|
||||||
|
assert!(!set.contains(address));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ use bitflags::bitflags;
|
||||||
use num_derive::{FromPrimitive, ToPrimitive};
|
use num_derive::{FromPrimitive, ToPrimitive};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::address_set::AddressSet;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct VB {
|
struct VB {
|
||||||
_data: [u8; 0],
|
_data: [u8; 0],
|
||||||
|
@ -62,9 +64,31 @@ pub enum VBRegister {
|
||||||
PC,
|
PC,
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum VBWatchpointType {
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
Access,
|
||||||
|
}
|
||||||
|
|
||||||
type OnExecute =
|
type OnExecute =
|
||||||
extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
|
extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
|
||||||
|
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
||||||
|
type OnRead = extern "C" fn(
|
||||||
|
sim: *mut VB,
|
||||||
|
address: u32,
|
||||||
|
type_: VBDataType,
|
||||||
|
value: *mut i32,
|
||||||
|
cycles: *mut u32,
|
||||||
|
) -> c_int;
|
||||||
|
type OnWrite = extern "C" fn(
|
||||||
|
sim: *mut VB,
|
||||||
|
address: u32,
|
||||||
|
type_: VBDataType,
|
||||||
|
value: *mut i32,
|
||||||
|
cycles: *mut u32,
|
||||||
|
cancel: *mut c_int,
|
||||||
|
) -> c_int;
|
||||||
|
|
||||||
#[link(name = "vb")]
|
#[link(name = "vb")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -112,15 +136,17 @@ extern "C" {
|
||||||
#[link_name = "vbSetCartROM"]
|
#[link_name = "vbSetCartROM"]
|
||||||
fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
|
fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
|
||||||
#[link_name = "vbSetExecuteCallback"]
|
#[link_name = "vbSetExecuteCallback"]
|
||||||
fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>);
|
fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>) -> Option<OnExecute>;
|
||||||
#[link_name = "vbSetFrameCallback"]
|
#[link_name = "vbSetFrameCallback"]
|
||||||
fn vb_set_frame_callback(sim: *mut VB, callback: Option<OnFrame>);
|
fn vb_set_frame_callback(sim: *mut VB, callback: Option<OnFrame>) -> Option<OnFrame>;
|
||||||
#[link_name = "vbSetKeys"]
|
#[link_name = "vbSetKeys"]
|
||||||
fn vb_set_keys(sim: *mut VB, keys: u16) -> u16;
|
fn vb_set_keys(sim: *mut VB, keys: u16) -> u16;
|
||||||
#[link_name = "vbSetOption"]
|
#[link_name = "vbSetOption"]
|
||||||
fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int);
|
fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int);
|
||||||
#[link_name = "vbSetPeer"]
|
#[link_name = "vbSetPeer"]
|
||||||
fn vb_set_peer(sim: *mut VB, peer: *mut VB);
|
fn vb_set_peer(sim: *mut VB, peer: *mut VB);
|
||||||
|
#[link_name = "vbSetReadCallback"]
|
||||||
|
fn vb_set_read_callback(sim: *mut VB, callback: Option<OnRead>) -> Option<OnRead>;
|
||||||
#[link_name = "vbSetSamples"]
|
#[link_name = "vbSetSamples"]
|
||||||
fn vb_set_samples(
|
fn vb_set_samples(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
|
@ -130,6 +156,8 @@ extern "C" {
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
#[link_name = "vbSetUserData"]
|
#[link_name = "vbSetUserData"]
|
||||||
fn vb_set_user_data(sim: *mut VB, tag: *mut c_void);
|
fn vb_set_user_data(sim: *mut VB, tag: *mut c_void);
|
||||||
|
#[link_name = "vbSetWriteCallback"]
|
||||||
|
fn vb_set_write_callback(sim: *mut VB, callback: Option<OnWrite>) -> Option<OnWrite>;
|
||||||
#[link_name = "vbSizeOf"]
|
#[link_name = "vbSizeOf"]
|
||||||
fn vb_size_of() -> usize;
|
fn vb_size_of() -> usize;
|
||||||
}
|
}
|
||||||
|
@ -147,18 +175,73 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length:
|
||||||
// There is no way for the userdata to be null or otherwise invalid.
|
// There is no way for the userdata to be null or otherwise invalid.
|
||||||
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
||||||
|
|
||||||
let mut stopped = 0;
|
let mut stopped = data.stop_reason.is_some();
|
||||||
if data.step_from.is_some_and(|s| s != address) {
|
if data.step_from.is_some_and(|s| s != address) {
|
||||||
data.step_from = None;
|
data.step_from = None;
|
||||||
data.stop_reason = Some(StopReason::Stepped);
|
data.stop_reason = Some(StopReason::Stepped);
|
||||||
stopped = 1;
|
stopped = true;
|
||||||
}
|
}
|
||||||
if data.breakpoints.binary_search(&address).is_ok() {
|
if data.breakpoints.binary_search(&address).is_ok() {
|
||||||
data.stop_reason = Some(StopReason::Breakpoint);
|
data.stop_reason = Some(StopReason::Breakpoint);
|
||||||
stopped = 1;
|
stopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
stopped
|
if stopped {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_read(
|
||||||
|
sim: *mut VB,
|
||||||
|
address: u32,
|
||||||
|
_type: VBDataType,
|
||||||
|
_value: *mut i32,
|
||||||
|
_cycles: *mut u32,
|
||||||
|
) -> c_int {
|
||||||
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
|
// There is no way for the userdata to be null or otherwise invalid.
|
||||||
|
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
||||||
|
|
||||||
|
if data.read_watchpoints.contains(address) {
|
||||||
|
let watch = if data.write_watchpoints.contains(address) {
|
||||||
|
VBWatchpointType::Access
|
||||||
|
} else {
|
||||||
|
VBWatchpointType::Read
|
||||||
|
};
|
||||||
|
data.stop_reason = Some(StopReason::Watchpoint(watch, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't stop here, the debugger expects us to break after the memory access.
|
||||||
|
// We'll stop in on_execute instead.
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_write(
|
||||||
|
sim: *mut VB,
|
||||||
|
address: u32,
|
||||||
|
_type: VBDataType,
|
||||||
|
_value: *mut i32,
|
||||||
|
_cycles: *mut u32,
|
||||||
|
_cancel: *mut c_int,
|
||||||
|
) -> c_int {
|
||||||
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
|
// There is no way for the userdata to be null or otherwise invalid.
|
||||||
|
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
|
||||||
|
|
||||||
|
if data.write_watchpoints.contains(address) {
|
||||||
|
let watch = if data.read_watchpoints.contains(address) {
|
||||||
|
VBWatchpointType::Access
|
||||||
|
} else {
|
||||||
|
VBWatchpointType::Write
|
||||||
|
};
|
||||||
|
data.stop_reason = Some(StopReason::Watchpoint(watch, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't stop here, the debugger expects us to break after the memory access.
|
||||||
|
// We'll stop in on_execute instead.
|
||||||
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
||||||
|
@ -170,6 +253,23 @@ struct VBState {
|
||||||
stop_reason: Option<StopReason>,
|
stop_reason: Option<StopReason>,
|
||||||
step_from: Option<u32>,
|
step_from: Option<u32>,
|
||||||
breakpoints: Vec<u32>,
|
breakpoints: Vec<u32>,
|
||||||
|
read_watchpoints: AddressSet,
|
||||||
|
write_watchpoints: AddressSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VBState {
|
||||||
|
fn needs_execute_callback(&self) -> bool {
|
||||||
|
self.step_from.is_some()
|
||||||
|
|| !self.breakpoints.is_empty()
|
||||||
|
|| !self.read_watchpoints.is_empty()
|
||||||
|
|| !self.write_watchpoints.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum StopReason {
|
||||||
|
Breakpoint,
|
||||||
|
Watchpoint(VBWatchpointType, u32),
|
||||||
|
Stepped,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
@ -177,11 +277,6 @@ pub struct Sim {
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum StopReason {
|
|
||||||
Breakpoint,
|
|
||||||
Stepped,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sim {
|
impl Sim {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
// init the VB instance itself
|
// init the VB instance itself
|
||||||
|
@ -201,6 +296,8 @@ impl Sim {
|
||||||
stop_reason: None,
|
stop_reason: None,
|
||||||
step_from: None,
|
step_from: None,
|
||||||
breakpoints: vec![],
|
breakpoints: vec![],
|
||||||
|
read_watchpoints: AddressSet::new(),
|
||||||
|
write_watchpoints: AddressSet::new(),
|
||||||
};
|
};
|
||||||
unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
|
unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
|
||||||
unsafe { vb_set_frame_callback(sim, Some(on_frame)) };
|
unsafe { vb_set_frame_callback(sim, Some(on_frame)) };
|
||||||
|
@ -374,7 +471,71 @@ impl Sim {
|
||||||
let data = self.get_state();
|
let data = self.get_state();
|
||||||
if let Ok(index) = data.breakpoints.binary_search(&address) {
|
if let Ok(index) = data.breakpoints.binary_search(&address) {
|
||||||
data.breakpoints.remove(index);
|
data.breakpoints.remove(index);
|
||||||
if data.step_from.is_none() && data.breakpoints.is_empty() {
|
if !data.needs_execute_callback() {
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_watchpoint(&mut self, address: u32, length: usize, watch: VBWatchpointType) {
|
||||||
|
match watch {
|
||||||
|
VBWatchpointType::Read => self.add_read_watchpoint(address, length),
|
||||||
|
VBWatchpointType::Write => self.add_write_watchpoint(address, length),
|
||||||
|
VBWatchpointType::Access => {
|
||||||
|
self.add_read_watchpoint(address, length);
|
||||||
|
self.add_write_watchpoint(address, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_read_watchpoint(&mut self, address: u32, length: usize) {
|
||||||
|
let state = self.get_state();
|
||||||
|
state.read_watchpoints.add(address, length);
|
||||||
|
if !state.read_watchpoints.is_empty() {
|
||||||
|
unsafe { vb_set_read_callback(self.sim, Some(on_read)) };
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_write_watchpoint(&mut self, address: u32, length: usize) {
|
||||||
|
let state = self.get_state();
|
||||||
|
state.write_watchpoints.add(address, length);
|
||||||
|
if !state.write_watchpoints.is_empty() {
|
||||||
|
unsafe { vb_set_write_callback(self.sim, Some(on_write)) };
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, Some(on_execute)) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_watchpoint(&mut self, address: u32, length: usize, watch: VBWatchpointType) {
|
||||||
|
match watch {
|
||||||
|
VBWatchpointType::Read => self.remove_read_watchpoint(address, length),
|
||||||
|
VBWatchpointType::Write => self.remove_write_watchpoint(address, length),
|
||||||
|
VBWatchpointType::Access => {
|
||||||
|
self.remove_read_watchpoint(address, length);
|
||||||
|
self.remove_write_watchpoint(address, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_read_watchpoint(&mut self, address: u32, length: usize) {
|
||||||
|
let state = self.get_state();
|
||||||
|
state.read_watchpoints.remove(address, length);
|
||||||
|
let needs_execute = state.needs_execute_callback();
|
||||||
|
if state.read_watchpoints.is_empty() {
|
||||||
|
unsafe { vb_set_read_callback(self.sim, None) };
|
||||||
|
if !needs_execute {
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_write_watchpoint(&mut self, address: u32, length: usize) {
|
||||||
|
let state = self.get_state();
|
||||||
|
state.write_watchpoints.remove(address, length);
|
||||||
|
let needs_execute = state.needs_execute_callback();
|
||||||
|
if state.write_watchpoints.is_empty() {
|
||||||
|
unsafe { vb_set_write_callback(self.sim, None) };
|
||||||
|
if !needs_execute {
|
||||||
unsafe { vb_set_execute_callback(self.sim, None) };
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -393,13 +554,17 @@ impl Sim {
|
||||||
let data = self.get_state();
|
let data = self.get_state();
|
||||||
data.step_from = None;
|
data.step_from = None;
|
||||||
data.breakpoints.clear();
|
data.breakpoints.clear();
|
||||||
|
data.read_watchpoints.clear();
|
||||||
|
data.write_watchpoints.clear();
|
||||||
|
unsafe { vb_set_read_callback(self.sim, None) };
|
||||||
|
unsafe { vb_set_write_callback(self.sim, None) };
|
||||||
unsafe { vb_set_execute_callback(self.sim, None) };
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_reason(&mut self) -> Option<StopReason> {
|
pub fn stop_reason(&mut self) -> Option<StopReason> {
|
||||||
let data = self.get_state();
|
let data = self.get_state();
|
||||||
let reason = data.stop_reason.take();
|
let reason = data.stop_reason.take();
|
||||||
if data.step_from.is_none() && data.breakpoints.is_empty() {
|
if !data.needs_execute_callback() {
|
||||||
unsafe { vb_set_execute_callback(self.sim, None) };
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
}
|
}
|
||||||
reason
|
reason
|
||||||
|
|
113
src/gdbserver.rs
113
src/gdbserver.rs
|
@ -13,7 +13,9 @@ use tokio::{
|
||||||
sync::{mpsc, oneshot},
|
sync::{mpsc, oneshot},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::emulator::{DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId};
|
use crate::emulator::{
|
||||||
|
DebugEvent, DebugStopReason, EmulatorClient, EmulatorCommand, SimId, VBWatchpointType,
|
||||||
|
};
|
||||||
|
|
||||||
mod registers;
|
mod registers;
|
||||||
mod request;
|
mod request;
|
||||||
|
@ -187,7 +189,7 @@ impl GdbConnection {
|
||||||
}
|
}
|
||||||
self.stop_reason = Some(reason);
|
self.stop_reason = Some(reason);
|
||||||
self.response()
|
self.response()
|
||||||
.write_str(debug_stop_reason_string(self.stop_reason))
|
.write_str(&debug_stop_reason_string(self.stop_reason))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(res)
|
Some(res)
|
||||||
|
@ -251,7 +253,7 @@ impl GdbConnection {
|
||||||
bail!("debug process was killed");
|
bail!("debug process was killed");
|
||||||
} else if req.match_str("?") {
|
} else if req.match_str("?") {
|
||||||
self.response()
|
self.response()
|
||||||
.write_str(debug_stop_reason_string(self.stop_reason))
|
.write_str(&debug_stop_reason_string(self.stop_reason))
|
||||||
} else if req.match_some_str(["c", "vCont;c:"]).is_some() {
|
} else if req.match_some_str(["c", "vCont;c:"]).is_some() {
|
||||||
self.client
|
self.client
|
||||||
.send_command(EmulatorCommand::DebugContinue(self.sim_id));
|
.send_command(EmulatorCommand::DebugContinue(self.sim_id));
|
||||||
|
@ -329,18 +331,68 @@ impl GdbConnection {
|
||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
}
|
}
|
||||||
} else if req.match_str("Z0,") {
|
} else if req.match_str("Z") {
|
||||||
if let Some(address) = req.match_hex() {
|
let mut parse_request = || {
|
||||||
self.client
|
let type_ = req.match_hex::<u8>()?;
|
||||||
.send_command(EmulatorCommand::AddBreakpoint(self.sim_id, address));
|
if !req.match_str(",") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let address = req.match_hex()?;
|
||||||
|
if type_ == 0 || type_ == 1 {
|
||||||
|
return Some(EmulatorCommand::AddBreakpoint(self.sim_id, address));
|
||||||
|
}
|
||||||
|
if !req.match_str(",") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let length = req.match_hex()?;
|
||||||
|
let watch = match type_ {
|
||||||
|
2 => VBWatchpointType::Write,
|
||||||
|
3 => VBWatchpointType::Read,
|
||||||
|
4 => VBWatchpointType::Access,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(EmulatorCommand::AddWatchpoint(
|
||||||
|
self.sim_id,
|
||||||
|
address,
|
||||||
|
length,
|
||||||
|
watch,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
if let Some(command) = parse_request() {
|
||||||
|
self.client.send_command(command);
|
||||||
self.response().write_str("OK")
|
self.response().write_str("OK")
|
||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
}
|
}
|
||||||
} else if req.match_str("z0,") {
|
} else if req.match_str("z") {
|
||||||
if let Some(address) = req.match_hex() {
|
let mut parse_request = || {
|
||||||
self.client
|
let type_ = req.match_hex::<u8>()?;
|
||||||
.send_command(EmulatorCommand::RemoveBreakpoint(self.sim_id, address));
|
if !req.match_str(",") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let address = req.match_hex()?;
|
||||||
|
if type_ == 0 || type_ == 1 {
|
||||||
|
return Some(EmulatorCommand::RemoveBreakpoint(self.sim_id, address));
|
||||||
|
}
|
||||||
|
if !req.match_str(",") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let length = req.match_hex()?;
|
||||||
|
let watch = match type_ {
|
||||||
|
2 => VBWatchpointType::Write,
|
||||||
|
3 => VBWatchpointType::Read,
|
||||||
|
4 => VBWatchpointType::Access,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some(EmulatorCommand::RemoveWatchpoint(
|
||||||
|
self.sim_id,
|
||||||
|
address,
|
||||||
|
length,
|
||||||
|
watch,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
if let Some(command) = parse_request() {
|
||||||
|
self.client.send_command(command);
|
||||||
self.response().write_str("OK")
|
self.response().write_str("OK")
|
||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
|
@ -367,11 +419,38 @@ impl Drop for GdbConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
|
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
||||||
match reason {
|
let mut result = String::new();
|
||||||
Some(DebugStopReason::Trace) => "T05;thread:p1.t1;threads:p1.t1;reason:trace;",
|
result += if reason.is_some() { "T05;" } else { "T00;" };
|
||||||
Some(DebugStopReason::Breakpoint) => "T05;thread:p1.t1;threads:p1.t1;reason:breakpoint;",
|
if let Some(DebugStopReason::Breakpoint) = reason {
|
||||||
Some(DebugStopReason::Trapped) => "T05;swbreak;thread:p1.t1;threads:p1.t1;reason:trap;",
|
result += "swbreak;";
|
||||||
None => "T00;thread:p1.t1;threads:p1.t1;",
|
|
||||||
}
|
}
|
||||||
|
if let Some(DebugStopReason::Watchpoint(watch, address)) = reason {
|
||||||
|
result += match watch {
|
||||||
|
VBWatchpointType::Write => "watch:",
|
||||||
|
VBWatchpointType::Read => "rwatch:",
|
||||||
|
VBWatchpointType::Access => "awatch:",
|
||||||
|
};
|
||||||
|
result += &format!("{address:08x};");
|
||||||
|
}
|
||||||
|
|
||||||
|
result += "thread:p1.t1;threads:p1.t1;";
|
||||||
|
|
||||||
|
if let Some(reason) = reason {
|
||||||
|
result += "reason:";
|
||||||
|
result += match reason {
|
||||||
|
DebugStopReason::Trace => "trace;",
|
||||||
|
DebugStopReason::Breakpoint => "breakpoint;",
|
||||||
|
DebugStopReason::Watchpoint(_, _) => "watchpoint;",
|
||||||
|
DebugStopReason::Trapped => "trap;",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(DebugStopReason::Watchpoint(_, address)) = reason {
|
||||||
|
result += "description:";
|
||||||
|
result += &hex::encode(address.to_string());
|
||||||
|
result += ";";
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue