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