Implement execute breakpoints
This commit is contained in:
parent
856ee00999
commit
d6eb8ec7ef
|
@ -18,7 +18,7 @@ use bytemuck::NoUninit;
|
||||||
use egui_toast::{Toast, ToastKind, ToastOptions};
|
use egui_toast::{Toast, ToastKind, ToastOptions};
|
||||||
|
|
||||||
use crate::{audio::Audio, graphics::TextureSink};
|
use crate::{audio::Audio, graphics::TextureSink};
|
||||||
use shrooms_vb_core::{Sim, 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};
|
||||||
|
|
||||||
mod shrooms_vb_core;
|
mod shrooms_vb_core;
|
||||||
|
@ -302,6 +302,9 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_debugging(&mut self, sim_id: SimId) {
|
fn stop_debugging(&mut self, sim_id: SimId) {
|
||||||
|
if let Some(sim) = self.sims.get_mut(sim_id.to_index()) {
|
||||||
|
sim.clear_breakpoints();
|
||||||
|
}
|
||||||
self.debuggers.remove(&sim_id);
|
self.debuggers.remove(&sim_id);
|
||||||
if self.debuggers.is_empty() {
|
if self.debuggers.is_empty() {
|
||||||
let _ = self.state.compare_exchange(
|
let _ = self.state.compare_exchange(
|
||||||
|
@ -314,17 +317,17 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_interrupt(&mut self, sim_id: SimId) {
|
fn debug_interrupt(&mut self, sim_id: SimId) {
|
||||||
|
self.debug_stop(sim_id, DebugStopReason::Trapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) {
|
||||||
let Some(debugger) = self.debuggers.get_mut(&sim_id) else {
|
let Some(debugger) = self.debuggers.get_mut(&sim_id) else {
|
||||||
self.stop_debugging(sim_id);
|
self.stop_debugging(sim_id);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if !matches!(debugger.stop_reason, Some(DebugStopReason::Trapped)) {
|
if debugger.stop_reason != Some(reason) {
|
||||||
debugger.stop_reason = Some(DebugStopReason::Trapped);
|
debugger.stop_reason = Some(reason);
|
||||||
if debugger
|
if debugger.sender.send(DebugEvent::Stopped(reason)).is_err() {
|
||||||
.sender
|
|
||||||
.send(DebugEvent::Stopped(DebugStopReason::Trapped))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
self.stop_debugging(sim_id);
|
self.stop_debugging(sim_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,6 +392,21 @@ impl Emulator {
|
||||||
self.sims[SimId::Player2.to_index()].emulate();
|
self.sims[SimId::Player2.to_index()].emulate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug state
|
||||||
|
if state == EmulatorState::Debugging {
|
||||||
|
for sim_id in SimId::values() {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(reason) = sim.stop_reason() {
|
||||||
|
let stop_reason = match reason {
|
||||||
|
StopReason::Breakpoint => DebugStopReason::Breakpoint,
|
||||||
|
};
|
||||||
|
self.debug_stop(sim_id, stop_reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
for sim_id in SimId::values() {
|
for sim_id in SimId::values() {
|
||||||
let Some(renderer) = self.renderers.get_mut(&sim_id) else {
|
let Some(renderer) = self.renderers.get_mut(&sim_id) else {
|
||||||
|
@ -493,6 +511,18 @@ impl Emulator {
|
||||||
sim.read_memory(addresses, &mut buffer);
|
sim.read_memory(addresses, &mut buffer);
|
||||||
let _ = done.send(buffer);
|
let _ = done.send(buffer);
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::AddBreakpoint(sim_id, address) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.add_breakpoint(address);
|
||||||
|
}
|
||||||
|
EmulatorCommand::RemoveBreakpoint(sim_id, address) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.remove_breakpoint(address);
|
||||||
|
}
|
||||||
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);
|
||||||
|
@ -556,6 +586,8 @@ pub enum EmulatorCommand {
|
||||||
DebugContinue(SimId),
|
DebugContinue(SimId),
|
||||||
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
||||||
ReadMemory(SimId, Range<u32>, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
ReadMemory(SimId, Range<u32>, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
||||||
|
AddBreakpoint(SimId, u32),
|
||||||
|
RemoveBreakpoint(SimId, u32),
|
||||||
SetAudioEnabled(bool, bool),
|
SetAudioEnabled(bool, bool),
|
||||||
Link,
|
Link,
|
||||||
Unlink,
|
Unlink,
|
||||||
|
@ -584,6 +616,8 @@ type DebugSender = tokio::sync::mpsc::UnboundedSender<DebugEvent>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum DebugStopReason {
|
pub enum DebugStopReason {
|
||||||
|
// We hit a breakpoint
|
||||||
|
Breakpoint,
|
||||||
// The debugger told us to pause
|
// The debugger told us to pause
|
||||||
Trapped,
|
Trapped,
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,8 @@ pub enum VBRegister {
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
type OnFrame = extern "C" fn(sim: *mut VB) -> c_int;
|
||||||
|
type OnExecute =
|
||||||
|
extern "C" fn(sim: *mut VB, address: u32, code: *const u16, length: c_int) -> c_int;
|
||||||
|
|
||||||
#[link(name = "vb")]
|
#[link(name = "vb")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -109,8 +111,10 @@ extern "C" {
|
||||||
fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int;
|
fn vb_set_cart_ram(sim: *mut VB, sram: *mut c_void, size: u32) -> c_int;
|
||||||
#[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"]
|
||||||
|
fn vb_set_execute_callback(sim: *mut VB, callback: Option<OnExecute>);
|
||||||
#[link_name = "vbSetFrameCallback"]
|
#[link_name = "vbSetFrameCallback"]
|
||||||
fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame);
|
fn vb_set_frame_callback(sim: *mut VB, callback: 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"]
|
||||||
|
@ -130,7 +134,7 @@ extern "C" {
|
||||||
fn vb_size_of() -> usize;
|
fn vb_size_of() -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn on_frame(sim: *mut VB) -> i32 {
|
extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
||||||
// SAFETY: the *mut VB owns its userdata.
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
// 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() };
|
||||||
|
@ -138,12 +142,27 @@ extern "C" fn on_frame(sim: *mut VB) -> i32 {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: 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.breakpoints.binary_search(&address).is_err() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.stop_reason = Some(StopReason::Breakpoint);
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
const AUDIO_CAPACITY_SAMPLES: usize = 834 * 4;
|
||||||
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
|
const AUDIO_CAPACITY_FLOATS: usize = AUDIO_CAPACITY_SAMPLES * 2;
|
||||||
pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
|
pub const EXPECTED_FRAME_SIZE: usize = 834 * 2;
|
||||||
|
|
||||||
struct VBState {
|
struct VBState {
|
||||||
frame_seen: bool,
|
frame_seen: bool,
|
||||||
|
stop_reason: Option<StopReason>,
|
||||||
|
breakpoints: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
|
@ -151,8 +170,9 @@ pub struct Sim {
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SAFETY: the memory pointed to by sim is valid
|
pub enum StopReason {
|
||||||
unsafe impl Send for Sim {}
|
Breakpoint,
|
||||||
|
}
|
||||||
|
|
||||||
impl Sim {
|
impl Sim {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -166,9 +186,13 @@ impl Sim {
|
||||||
unsafe { vb_reset(sim) };
|
unsafe { vb_reset(sim) };
|
||||||
|
|
||||||
// set up userdata
|
// set up userdata
|
||||||
let state = VBState { frame_seen: false };
|
let state = VBState {
|
||||||
|
frame_seen: false,
|
||||||
|
stop_reason: None,
|
||||||
|
breakpoints: vec![],
|
||||||
|
};
|
||||||
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, on_frame) };
|
unsafe { vb_set_frame_callback(sim, Some(on_frame)) };
|
||||||
|
|
||||||
// set up audio buffer
|
// set up audio buffer
|
||||||
let audio_buffer = vec![0.0f32; AUDIO_CAPACITY_FLOATS];
|
let audio_buffer = vec![0.0f32; AUDIO_CAPACITY_FLOATS];
|
||||||
|
@ -257,9 +281,7 @@ impl Sim {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
|
pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool {
|
||||||
// SAFETY: the *mut VB owns its userdata.
|
let data = self.get_state();
|
||||||
// There is no way for the userdata to be null or otherwise invalid.
|
|
||||||
let data: &mut VBState = unsafe { &mut *vb_get_user_data(self.sim).cast() };
|
|
||||||
if !data.frame_seen {
|
if !data.frame_seen {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -323,6 +345,44 @@ impl Sim {
|
||||||
into.push(byte as u8);
|
into.push(byte as u8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_breakpoint(&mut self, address: u32) {
|
||||||
|
let data = self.get_state();
|
||||||
|
if let Err(index) = data.breakpoints.binary_search(&address) {
|
||||||
|
data.breakpoints.insert(index, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
vb_set_execute_callback(self.sim, Some(on_execute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_breakpoint(&mut self, address: u32) {
|
||||||
|
let data = self.get_state();
|
||||||
|
if let Ok(index) = data.breakpoints.binary_search(&address) {
|
||||||
|
data.breakpoints.remove(index);
|
||||||
|
if data.breakpoints.is_empty() {
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_breakpoints(&mut self) {
|
||||||
|
let data = self.get_state();
|
||||||
|
data.breakpoints.clear();
|
||||||
|
unsafe { vb_set_execute_callback(self.sim, None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop_reason(&mut self) -> Option<StopReason> {
|
||||||
|
let data = self.get_state();
|
||||||
|
data.stop_reason.take()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_state(&mut self) -> &mut VBState {
|
||||||
|
// SAFETY: the *mut VB owns its userdata.
|
||||||
|
// There is no way for the userdata to be null or otherwise invalid.
|
||||||
|
unsafe { &mut *vb_get_user_data(self.sim).cast() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Sim {
|
impl Drop for Sim {
|
||||||
|
|
|
@ -307,6 +307,22 @@ impl GdbConnection {
|
||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
}
|
}
|
||||||
|
} else if req.match_str("Z0,") {
|
||||||
|
if let Some(address) = req.match_hex() {
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::AddBreakpoint(self.sim_id, address));
|
||||||
|
self.response().write_str("OK")
|
||||||
|
} else {
|
||||||
|
self.response()
|
||||||
|
}
|
||||||
|
} else if req.match_str("z0,") {
|
||||||
|
if let Some(address) = req.match_hex() {
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::RemoveBreakpoint(self.sim_id, address));
|
||||||
|
self.response().write_str("OK")
|
||||||
|
} else {
|
||||||
|
self.response()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// unrecognized command
|
// unrecognized command
|
||||||
self.response()
|
self.response()
|
||||||
|
@ -331,7 +347,8 @@ impl Drop for GdbConnection {
|
||||||
|
|
||||||
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
|
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> &'static str {
|
||||||
match reason {
|
match reason {
|
||||||
Some(DebugStopReason::Trapped) => "T05;thread:p1.t1;threads:p1.t1;reason:trap;",
|
Some(DebugStopReason::Breakpoint) => "T05;thread:p1.t1;threads;p1.t1;reason:breakpoint",
|
||||||
|
Some(DebugStopReason::Trapped) => "T05;swbreak;thread:p1.t1;threads:p1.t1;reason:trap;",
|
||||||
None => "T00;thread:p1.t1;threads:p1.t1;",
|
None => "T00;thread:p1.t1;threads:p1.t1;",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue