Support watchpoints
This commit is contained in:
+179
-14
@@ -5,6 +5,8 @@ use bitflags::bitflags;
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::address_set::AddressSet;
|
||||
|
||||
#[repr(C)]
|
||||
struct VB {
|
||||
_data: [u8; 0],
|
||||
@@ -62,9 +64,31 @@ pub enum VBRegister {
|
||||
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 =
|
||||
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")]
|
||||
extern "C" {
|
||||
@@ -112,15 +136,17 @@ extern "C" {
|
||||
#[link_name = "vbSetCartROM"]
|
||||
fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int;
|
||||
#[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"]
|
||||
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"]
|
||||
fn vb_set_keys(sim: *mut VB, keys: u16) -> u16;
|
||||
#[link_name = "vbSetOption"]
|
||||
fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int);
|
||||
#[link_name = "vbSetPeer"]
|
||||
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"]
|
||||
fn vb_set_samples(
|
||||
sim: *mut VB,
|
||||
@@ -130,6 +156,8 @@ extern "C" {
|
||||
) -> c_int;
|
||||
#[link_name = "vbSetUserData"]
|
||||
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"]
|
||||
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.
|
||||
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) {
|
||||
data.step_from = None;
|
||||
data.stop_reason = Some(StopReason::Stepped);
|
||||
stopped = 1;
|
||||
stopped = true;
|
||||
}
|
||||
if data.breakpoints.binary_search(&address).is_ok() {
|
||||
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;
|
||||
@@ -170,6 +253,23 @@ struct VBState {
|
||||
stop_reason: Option<StopReason>,
|
||||
step_from: Option<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)]
|
||||
@@ -177,11 +277,6 @@ pub struct Sim {
|
||||
sim: *mut VB,
|
||||
}
|
||||
|
||||
pub enum StopReason {
|
||||
Breakpoint,
|
||||
Stepped,
|
||||
}
|
||||
|
||||
impl Sim {
|
||||
pub fn new() -> Self {
|
||||
// init the VB instance itself
|
||||
@@ -201,6 +296,8 @@ impl Sim {
|
||||
stop_reason: None,
|
||||
step_from: None,
|
||||
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_frame_callback(sim, Some(on_frame)) };
|
||||
@@ -374,7 +471,71 @@ impl Sim {
|
||||
let data = self.get_state();
|
||||
if let Ok(index) = data.breakpoints.binary_search(&address) {
|
||||
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) };
|
||||
}
|
||||
}
|
||||
@@ -393,13 +554,17 @@ impl Sim {
|
||||
let data = self.get_state();
|
||||
data.step_from = None;
|
||||
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) };
|
||||
}
|
||||
|
||||
pub fn stop_reason(&mut self) -> Option<StopReason> {
|
||||
let data = self.get_state();
|
||||
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) };
|
||||
}
|
||||
reason
|
||||
|
||||
Reference in New Issue
Block a user