Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a2aaee92 | ||
|
|
d59da1009a | ||
|
|
9d37d22a22 | ||
|
|
e1a8006af7 | ||
|
|
9f7895a457 | ||
|
|
5ce39fb70e | ||
|
|
0fff4d427f | ||
|
|
17d3811124 |
@@ -5,7 +5,7 @@ A Virtual Boy emulator built around the shrooms-vb core. Written in Rust, using
|
|||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
Install the following dependencies:
|
Install the following dependencies:
|
||||||
- `cargo`
|
- `cargo` (via [rustup](https://rustup.rs/), the version from your package manager is too old)
|
||||||
|
|
||||||
Run
|
Run
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
+4
-2
@@ -23,13 +23,15 @@ ENV PATH="/osxcross/bin:$PATH" \
|
|||||||
CXX_x86_64-apple-darwin="o64-clang++" \
|
CXX_x86_64-apple-darwin="o64-clang++" \
|
||||||
CC_aarch64-apple-darwin="oa64-clang" \
|
CC_aarch64-apple-darwin="oa64-clang" \
|
||||||
CXX_aarch64-apple-darwin="o6a4-clang++" \
|
CXX_aarch64-apple-darwin="o6a4-clang++" \
|
||||||
|
SHROOMS_CFLAGS_x86_64-unknown-linux-gnu="-flto" \
|
||||||
|
CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" \
|
||||||
|
SHROOMS_CFLAGS_x86_64-pc-windows-msvc="-flto" \
|
||||||
|
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64 -Clinker-plugin-lto -Clink-arg=-fuse-ld=lld" \
|
||||||
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link-19" \
|
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER="lld-link-19" \
|
||||||
CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="o64-clang" \
|
CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER="o64-clang" \
|
||||||
CARGO_TARGET_X86_64_APPLE_DARWIN_AR="llvm-ar-19" \
|
CARGO_TARGET_X86_64_APPLE_DARWIN_AR="llvm-ar-19" \
|
||||||
CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="oa64-clang" \
|
CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER="oa64-clang" \
|
||||||
CARGO_TARGET_AARCH64_APPLE_DARWIN_AR="llvm-ar-19" \
|
CARGO_TARGET_AARCH64_APPLE_DARWIN_AR="llvm-ar-19" \
|
||||||
CROSS_COMPILE="setting-this-to-silence-a-warning-" \
|
CROSS_COMPILE="setting-this-to-silence-a-warning-" \
|
||||||
SHROOMS_CFLAGS="-flto" \
|
|
||||||
RC_PATH="llvm-rc-19" \
|
RC_PATH="llvm-rc-19" \
|
||||||
RUSTFLAGS="-Lnative=/xwin/crt/lib/x86_64 -Lnative=/xwin/sdk/lib/um/x86_64 -Lnative=/xwin/sdk/lib/ucrt/x86_64" \
|
|
||||||
MACOSX_DEPLOYMENT_TARGET="14.5"
|
MACOSX_DEPLOYMENT_TARGET="14.5"
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.define("VB_LITTLE_ENDIAN", None)
|
.define("VB_LITTLE_ENDIAN", None)
|
||||||
.define("VB_SIGNED_PROPAGATE", None)
|
.define("VB_SIGNED_PROPAGATE", None)
|
||||||
.define("VB_DIV_GENERIC", None)
|
.define("VB_DIV_GENERIC", None)
|
||||||
|
.define("VB_DIRECT_EXECUTE", "on_execute")
|
||||||
|
.define("VB_DIRECT_FRAME", "on_frame")
|
||||||
|
.define("VB_DIRECT_READ", "on_read")
|
||||||
|
.define("VB_DIRECT_WRITE", "on_write")
|
||||||
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
.file(Path::new("shrooms-vb-core/core/vb.c"))
|
||||||
.compile("vb");
|
.compile("vb");
|
||||||
|
|
||||||
|
|||||||
+22
-3
@@ -305,9 +305,13 @@ impl Emulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start_debugging(&mut self, sim_id: SimId, sender: DebugSender) {
|
fn start_debugging(&mut self, sim_id: SimId, sender: DebugSender) {
|
||||||
|
if self.sim_state[sim_id.to_index()].load(Ordering::Acquire) != SimState::Ready {
|
||||||
|
// Can't debug unless a game is connected
|
||||||
|
return;
|
||||||
|
}
|
||||||
let debug = DebugInfo {
|
let debug = DebugInfo {
|
||||||
sender,
|
sender,
|
||||||
stop_reason: Some(DebugStopReason::Trapped),
|
stop_reason: Some(DebugStopReason::Paused),
|
||||||
};
|
};
|
||||||
self.debuggers.insert(sim_id, debug);
|
self.debuggers.insert(sim_id, debug);
|
||||||
self.state
|
self.state
|
||||||
@@ -330,7 +334,7 @@ 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);
|
self.debug_stop(sim_id, DebugStopReason::Paused);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) {
|
fn debug_stop(&mut self, sim_id: SimId, reason: DebugStopReason) {
|
||||||
@@ -533,6 +537,12 @@ impl Emulator {
|
|||||||
let value = sim.read_register(register);
|
let value = sim.read_register(register);
|
||||||
let _ = done.send(value);
|
let _ = done.send(value);
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::WriteRegister(sim_id, register, value) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.write_register(register, value);
|
||||||
|
}
|
||||||
EmulatorCommand::ReadMemory(sim_id, start, length, mut buffer, done) => {
|
EmulatorCommand::ReadMemory(sim_id, start, length, mut buffer, done) => {
|
||||||
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
return;
|
return;
|
||||||
@@ -540,6 +550,13 @@ impl Emulator {
|
|||||||
sim.read_memory(start, length, &mut buffer);
|
sim.read_memory(start, length, &mut buffer);
|
||||||
let _ = done.send(buffer);
|
let _ = done.send(buffer);
|
||||||
}
|
}
|
||||||
|
EmulatorCommand::WriteMemory(sim_id, start, buffer, done) => {
|
||||||
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
sim.write_memory(start, &buffer);
|
||||||
|
let _ = done.send(buffer);
|
||||||
|
}
|
||||||
EmulatorCommand::AddBreakpoint(sim_id, address) => {
|
EmulatorCommand::AddBreakpoint(sim_id, address) => {
|
||||||
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
|
||||||
return;
|
return;
|
||||||
@@ -627,7 +644,9 @@ pub enum EmulatorCommand {
|
|||||||
DebugContinue(SimId),
|
DebugContinue(SimId),
|
||||||
DebugStep(SimId),
|
DebugStep(SimId),
|
||||||
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
ReadRegister(SimId, VBRegister, oneshot::Sender<u32>),
|
||||||
|
WriteRegister(SimId, VBRegister, u32),
|
||||||
ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
ReadMemory(SimId, u32, usize, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
||||||
|
WriteMemory(SimId, u32, Vec<u8>, oneshot::Sender<Vec<u8>>),
|
||||||
AddBreakpoint(SimId, u32),
|
AddBreakpoint(SimId, u32),
|
||||||
RemoveBreakpoint(SimId, u32),
|
RemoveBreakpoint(SimId, u32),
|
||||||
AddWatchpoint(SimId, u32, usize, VBWatchpointType),
|
AddWatchpoint(SimId, u32, usize, VBWatchpointType),
|
||||||
@@ -667,7 +686,7 @@ pub enum DebugStopReason {
|
|||||||
// We hit a watchpoint
|
// We hit a watchpoint
|
||||||
Watchpoint(VBWatchpointType, u32),
|
Watchpoint(VBWatchpointType, u32),
|
||||||
// The debugger told us to pause
|
// The debugger told us to pause
|
||||||
Trapped,
|
Paused,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DebugInfo {
|
struct DebugInfo {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
ops::Bound,
|
ops::Bound,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct AddressSet {
|
pub struct AddressSet {
|
||||||
ranges: HashSet<(u32, usize)>,
|
ranges: BTreeSet<(u32, usize)>,
|
||||||
bounds: BTreeMap<u32, usize>,
|
bounds: BTreeMap<u32, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +109,22 @@ impl AddressSet {
|
|||||||
.next_back()
|
.next_back()
|
||||||
.is_some_and(|(_, &val)| val > 0)
|
.is_some_and(|(_, &val)| val > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_of_range_containing(&self, address: u32) -> Option<u32> {
|
||||||
|
if !self.contains(address) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.ranges
|
||||||
|
.range(..=(address, usize::MAX))
|
||||||
|
.rev()
|
||||||
|
.find_map(|&(start, length)| {
|
||||||
|
let contains = start <= address
|
||||||
|
&& (start as usize)
|
||||||
|
.checked_add(length)
|
||||||
|
.is_none_or(|end| end > address as usize);
|
||||||
|
contains.then_some(start)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -120,6 +136,7 @@ mod tests {
|
|||||||
let set = AddressSet::new();
|
let set = AddressSet::new();
|
||||||
assert!(set.is_empty());
|
assert!(set.is_empty());
|
||||||
assert!(!set.contains(0x13374200));
|
assert!(!set.contains(0x13374200));
|
||||||
|
assert_eq!(set.start_of_range_containing(0x13374200), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -127,6 +144,7 @@ mod tests {
|
|||||||
let mut set = AddressSet::new();
|
let mut set = AddressSet::new();
|
||||||
set.add(0x00000000, 0x100000000);
|
set.add(0x00000000, 0x100000000);
|
||||||
assert!(set.contains(0x13374200));
|
assert!(set.contains(0x13374200));
|
||||||
|
assert_eq!(set.start_of_range_containing(0x13374200), Some(0x00000000));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -228,4 +246,25 @@ mod tests {
|
|||||||
assert!(!set.contains(address));
|
assert!(!set.contains(address));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_find_start_of_range() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x13374200, 4);
|
||||||
|
assert_eq!(set.start_of_range_containing(0x133741ff), None);
|
||||||
|
for address in 0x13374200..0x13374204 {
|
||||||
|
assert_eq!(set.start_of_range_containing(address), Some(0x13374200));
|
||||||
|
}
|
||||||
|
assert_eq!(set.start_of_range_containing(0x13374204), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_ignore_ranges_not_containing_address() {
|
||||||
|
let mut set = AddressSet::new();
|
||||||
|
set.add(0x10000000, 1024);
|
||||||
|
set.add(0x30000000, 1024);
|
||||||
|
|
||||||
|
assert!(!set.contains(0x20000000));
|
||||||
|
assert_eq!(set.start_of_range_containing(0x20000000), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,10 @@ extern "C" {
|
|||||||
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 = "vbSetProgramCounter"]
|
||||||
|
fn vb_set_program_counter(sim: *mut VB, value: u32) -> u32;
|
||||||
|
#[link_name = "vbSetProgramRegister"]
|
||||||
|
fn vb_set_program_register(sim: *mut VB, index: c_uint, value: i32) -> i32;
|
||||||
#[link_name = "vbSetReadCallback"]
|
#[link_name = "vbSetReadCallback"]
|
||||||
fn vb_set_read_callback(sim: *mut VB, callback: Option<OnRead>) -> Option<OnRead>;
|
fn vb_set_read_callback(sim: *mut VB, callback: Option<OnRead>) -> Option<OnRead>;
|
||||||
#[link_name = "vbSetSamples"]
|
#[link_name = "vbSetSamples"]
|
||||||
@@ -154,14 +158,19 @@ extern "C" {
|
|||||||
typ_: VBDataType,
|
typ_: VBDataType,
|
||||||
capacity: c_uint,
|
capacity: c_uint,
|
||||||
) -> c_int;
|
) -> c_int;
|
||||||
|
#[link_name = "vbSetSystemRegister"]
|
||||||
|
fn vb_set_system_register(sim: *mut VB, index: c_uint, value: u32) -> u32;
|
||||||
#[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"]
|
#[link_name = "vbSetWriteCallback"]
|
||||||
fn vb_set_write_callback(sim: *mut VB, callback: Option<OnWrite>) -> Option<OnWrite>;
|
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;
|
||||||
|
#[link_name = "vbWrite"]
|
||||||
|
fn vb_write(sim: *mut VB, address: u32, _type: VBDataType, value: i32) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
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.
|
||||||
@@ -170,6 +179,7 @@ extern "C" fn on_frame(sim: *mut VB) -> c_int {
|
|||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length: c_int) -> c_int {
|
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.
|
// 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.
|
||||||
@@ -193,6 +203,7 @@ extern "C" fn on_execute(sim: *mut VB, address: u32, _code: *const u16, _length:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
extern "C" fn on_read(
|
extern "C" fn on_read(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
address: u32,
|
address: u32,
|
||||||
@@ -204,13 +215,13 @@ extern "C" fn on_read(
|
|||||||
// 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() };
|
||||||
|
|
||||||
if data.read_watchpoints.contains(address) {
|
if let Some(start) = data.read_watchpoints.start_of_range_containing(address) {
|
||||||
let watch = if data.write_watchpoints.contains(address) {
|
let watch = if data.write_watchpoints.contains(address) {
|
||||||
VBWatchpointType::Access
|
VBWatchpointType::Access
|
||||||
} else {
|
} else {
|
||||||
VBWatchpointType::Read
|
VBWatchpointType::Read
|
||||||
};
|
};
|
||||||
data.stop_reason = Some(StopReason::Watchpoint(watch, address));
|
data.stop_reason = Some(StopReason::Watchpoint(watch, start));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't stop here, the debugger expects us to break after the memory access.
|
// Don't stop here, the debugger expects us to break after the memory access.
|
||||||
@@ -218,6 +229,7 @@ extern "C" fn on_read(
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
extern "C" fn on_write(
|
extern "C" fn on_write(
|
||||||
sim: *mut VB,
|
sim: *mut VB,
|
||||||
address: u32,
|
address: u32,
|
||||||
@@ -230,13 +242,13 @@ extern "C" fn on_write(
|
|||||||
// 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() };
|
||||||
|
|
||||||
if data.write_watchpoints.contains(address) {
|
if let Some(start) = data.write_watchpoints.start_of_range_containing(address) {
|
||||||
let watch = if data.read_watchpoints.contains(address) {
|
let watch = if data.read_watchpoints.contains(address) {
|
||||||
VBWatchpointType::Access
|
VBWatchpointType::Access
|
||||||
} else {
|
} else {
|
||||||
VBWatchpointType::Write
|
VBWatchpointType::Write
|
||||||
};
|
};
|
||||||
data.stop_reason = Some(StopReason::Watchpoint(watch, address));
|
data.stop_reason = Some(StopReason::Watchpoint(watch, start));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't stop here, the debugger expects us to break after the memory access.
|
// Don't stop here, the debugger expects us to break after the memory access.
|
||||||
@@ -447,6 +459,20 @@ impl Sim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_register(&mut self, register: VBRegister, value: u32) {
|
||||||
|
match register {
|
||||||
|
VBRegister::Program(index) => unsafe {
|
||||||
|
vb_set_program_register(self.sim, index, value as i32);
|
||||||
|
},
|
||||||
|
VBRegister::System(index) => unsafe {
|
||||||
|
vb_set_system_register(self.sim, index, value);
|
||||||
|
},
|
||||||
|
VBRegister::PC => unsafe {
|
||||||
|
vb_set_program_counter(self.sim, value);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_memory(&mut self, start: u32, length: usize, into: &mut Vec<u8>) {
|
pub fn read_memory(&mut self, start: u32, length: usize, into: &mut Vec<u8>) {
|
||||||
let mut address = start;
|
let mut address = start;
|
||||||
for _ in 0..length {
|
for _ in 0..length {
|
||||||
@@ -456,6 +482,14 @@ impl Sim {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_memory(&mut self, start: u32, buffer: &[u8]) {
|
||||||
|
let mut address = start;
|
||||||
|
for byte in buffer {
|
||||||
|
unsafe { vb_write(self.sim, address, VBDataType::U8, *byte as i32) };
|
||||||
|
address = address.wrapping_add(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_breakpoint(&mut self, address: u32) {
|
pub fn add_breakpoint(&mut self, address: u32) {
|
||||||
let data = self.get_state();
|
let data = self.get_state();
|
||||||
if let Err(index) = data.breakpoints.binary_search(&address) {
|
if let Err(index) = data.breakpoints.binary_search(&address) {
|
||||||
|
|||||||
+119
-24
@@ -9,7 +9,7 @@ use std::{
|
|||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncWriteExt as _, BufReader},
|
io::{AsyncWriteExt as _, BufReader},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
select,
|
pin, select,
|
||||||
sync::{mpsc, oneshot},
|
sync::{mpsc, oneshot},
|
||||||
};
|
};
|
||||||
use tracing::{debug, enabled, error, info, Level};
|
use tracing::{debug, enabled, error, info, Level};
|
||||||
@@ -57,8 +57,9 @@ impl GdbServer {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
select! {
|
select! {
|
||||||
_ = run_server(sim_id, client, port, &status) => {}
|
_ = run_server(sim_id, client.clone(), port, &status) => {}
|
||||||
_ = rx => {
|
_ = rx => {
|
||||||
|
client.send_command(EmulatorCommand::StopDebugging(sim_id));
|
||||||
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,19 +74,46 @@ impl GdbServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for GdbServer {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_server(
|
async fn run_server(
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
client: EmulatorClient,
|
client: EmulatorClient,
|
||||||
port: u16,
|
port: u16,
|
||||||
status: &Mutex<GdbServerStatus>,
|
status: &Mutex<GdbServerStatus>,
|
||||||
) {
|
) {
|
||||||
|
let (debug_sink, mut debug_source) = mpsc::unbounded_channel();
|
||||||
|
client.send_command(EmulatorCommand::StartDebugging(sim_id, debug_sink));
|
||||||
|
|
||||||
info!("Connecting to debugger on port {port}...");
|
info!("Connecting to debugger on port {port}...");
|
||||||
let Some(stream) = try_connect(port, status).await else {
|
let connect_future = try_connect(port, status);
|
||||||
|
pin!(connect_future);
|
||||||
|
|
||||||
|
let stream = loop {
|
||||||
|
select! {
|
||||||
|
stream = &mut connect_future => {
|
||||||
|
if let Some(stream) = stream {
|
||||||
|
break stream;
|
||||||
|
} else {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event = debug_source.recv() => {
|
||||||
|
if event.is_none() {
|
||||||
|
// The sim has stopped (or was never started)
|
||||||
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
info!("Connected!");
|
info!("Connected!");
|
||||||
let mut connection = GdbConnection::new(sim_id, client);
|
let mut connection = GdbConnection::new(sim_id, client);
|
||||||
match connection.run(stream).await {
|
match connection.run(stream, debug_source).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
info!("Finished debugging.");
|
info!("Finished debugging.");
|
||||||
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||||
@@ -154,12 +182,13 @@ impl GdbConnection {
|
|||||||
memory_buf: None,
|
memory_buf: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn run(&mut self, stream: TcpStream) -> Result<()> {
|
async fn run(
|
||||||
let (debug_sink, mut debug_source) = mpsc::unbounded_channel();
|
&mut self,
|
||||||
|
stream: TcpStream,
|
||||||
|
mut debug_source: mpsc::UnboundedReceiver<DebugEvent>,
|
||||||
|
) -> Result<()> {
|
||||||
let (rx, mut tx) = stream.into_split();
|
let (rx, mut tx) = stream.into_split();
|
||||||
let mut request_source = RequestSource::new(BufReader::new(rx));
|
let mut request_source = RequestSource::new(BufReader::new(rx));
|
||||||
self.client
|
|
||||||
.send_command(EmulatorCommand::StartDebugging(self.sim_id, debug_sink));
|
|
||||||
loop {
|
loop {
|
||||||
let response = select! {
|
let response = select! {
|
||||||
maybe_event = debug_source.recv() => {
|
maybe_event = debug_source.recv() => {
|
||||||
@@ -289,24 +318,37 @@ impl GdbConnection {
|
|||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
}
|
}
|
||||||
|
} else if req.match_str("P") {
|
||||||
|
let mut write_register = || {
|
||||||
|
let register_index = req.match_hex::<usize>()?;
|
||||||
|
let register = REGISTERS.get(register_index)?.to_vb_register();
|
||||||
|
if !req.match_str("=") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let value = {
|
||||||
|
let mut buffer = [0; 4];
|
||||||
|
if !req.match_hex_bytes(&mut buffer) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
u32::from_le_bytes(buffer)
|
||||||
|
};
|
||||||
|
self.client.send_command(EmulatorCommand::WriteRegister(
|
||||||
|
self.sim_id,
|
||||||
|
register,
|
||||||
|
value,
|
||||||
|
));
|
||||||
|
Some(())
|
||||||
|
};
|
||||||
|
if let Some(()) = write_register() {
|
||||||
|
self.response().write_str("OK")
|
||||||
|
} else {
|
||||||
|
self.response()
|
||||||
|
}
|
||||||
} else if let Some(op) = req.match_some_str(["m", "x"]) {
|
} else if let Some(op) = req.match_some_str(["m", "x"]) {
|
||||||
let mut read_memory = || {
|
let mut read_memory = || {
|
||||||
let start = req.match_hex::<u64>()?;
|
let (start, length) = parse_memory_range(&mut req)?;
|
||||||
if !req.match_str(",") {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
let length = req.match_hex::<usize>()?;
|
|
||||||
|
|
||||||
let mut buf = self.memory_buf.take().unwrap_or_default();
|
let mut buf = self.memory_buf.take().unwrap_or_default();
|
||||||
buf.clear();
|
buf.clear();
|
||||||
|
|
||||||
// The v810 has a 32-bit address space.
|
|
||||||
// Addresses wrap within that space, but we don't need to implement that for 64-bit addresses.
|
|
||||||
// Just refuse to return any info for addresses above 0xffffffff.
|
|
||||||
let Ok(start) = u32::try_from(start) else {
|
|
||||||
return Some(buf);
|
|
||||||
};
|
|
||||||
let length = length.min((u32::MAX - start) as usize + 1);
|
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return Some(buf);
|
return Some(buf);
|
||||||
}
|
}
|
||||||
@@ -340,6 +382,38 @@ impl GdbConnection {
|
|||||||
} else {
|
} else {
|
||||||
self.response()
|
self.response()
|
||||||
}
|
}
|
||||||
|
} else if let Some(op) = req.match_some_str(["M", "X"]) {
|
||||||
|
let mut write_memory = || {
|
||||||
|
let (start, length) = parse_memory_range(&mut req)?;
|
||||||
|
if length == 0 {
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
if !req.match_str(":") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut buf = self.memory_buf.take().unwrap_or_default();
|
||||||
|
buf.resize(length, 0);
|
||||||
|
let successful_read = if op == "M" {
|
||||||
|
req.match_hex_bytes(&mut buf)
|
||||||
|
} else {
|
||||||
|
req.match_bytes(&mut buf)
|
||||||
|
};
|
||||||
|
if !successful_read {
|
||||||
|
self.memory_buf = Some(buf);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (tx, rx) = ::oneshot::channel();
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::WriteMemory(self.sim_id, start, buf, tx));
|
||||||
|
let buf = rx.recv().ok()?;
|
||||||
|
self.memory_buf = Some(buf);
|
||||||
|
Some(())
|
||||||
|
};
|
||||||
|
if write_memory().is_some() {
|
||||||
|
self.response().write_str("OK")
|
||||||
|
} else {
|
||||||
|
self.response()
|
||||||
|
}
|
||||||
} else if req.match_str("Z") {
|
} else if req.match_str("Z") {
|
||||||
let mut parse_request = || {
|
let mut parse_request = || {
|
||||||
let type_ = req.match_hex::<u8>()?;
|
let type_ = req.match_hex::<u8>()?;
|
||||||
@@ -428,9 +502,30 @@ impl Drop for GdbConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse a memory range into a start and a length.
|
||||||
|
fn parse_memory_range(req: &mut Request<'_>) -> Option<(u32, usize)> {
|
||||||
|
let start = req.match_hex::<u64>()?;
|
||||||
|
if !req.match_str(",") {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let length = req.match_hex::<usize>()?;
|
||||||
|
let Ok(start) = u32::try_from(start) else {
|
||||||
|
// The v810 has a 32-bit address space.
|
||||||
|
// Addresses wrap within that space, but we don't need to implement that for 64-bit addresses.
|
||||||
|
// Just refuse to return any info for addresses above 0xffffffff.
|
||||||
|
return Some((0, 0));
|
||||||
|
};
|
||||||
|
let length = length.min((u32::MAX - start) as usize + 1);
|
||||||
|
Some((start, length))
|
||||||
|
}
|
||||||
|
|
||||||
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
||||||
let mut result = String::new();
|
let mut result = String::new();
|
||||||
result += if reason.is_some() { "T05;" } else { "T00;" };
|
result += if reason.is_some_and(|r| r != DebugStopReason::Paused) {
|
||||||
|
"T05;"
|
||||||
|
} else {
|
||||||
|
"T00;"
|
||||||
|
};
|
||||||
if let Some(DebugStopReason::Breakpoint) = reason {
|
if let Some(DebugStopReason::Breakpoint) = reason {
|
||||||
result += "swbreak;";
|
result += "swbreak;";
|
||||||
}
|
}
|
||||||
@@ -451,7 +546,7 @@ fn debug_stop_reason_string(reason: Option<DebugStopReason>) -> String {
|
|||||||
DebugStopReason::Trace => "trace;",
|
DebugStopReason::Trace => "trace;",
|
||||||
DebugStopReason::Breakpoint => "breakpoint;",
|
DebugStopReason::Breakpoint => "breakpoint;",
|
||||||
DebugStopReason::Watchpoint(_, _) => "watchpoint;",
|
DebugStopReason::Watchpoint(_, _) => "watchpoint;",
|
||||||
DebugStopReason::Trapped => "trap;",
|
DebugStopReason::Paused => "trap;",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,29 @@ impl Request<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn match_hex_bytes(&mut self, buffer: &mut [u8]) -> bool {
|
||||||
|
if self.buffer.len() < buffer.len() * 2 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i, item) in buffer.iter_mut().enumerate() {
|
||||||
|
match u8::from_radix_16(&self.buffer[(i * 2)..(i * 2) + 2]) {
|
||||||
|
(byte, 2) => *item = byte,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.buffer = self.buffer.split_at(buffer.len()).1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_bytes(&mut self, buffer: &mut [u8]) -> bool {
|
||||||
|
if self.buffer.len() < buffer.len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
buffer.copy_from_slice(&self.buffer[0..buffer.len()]);
|
||||||
|
self.buffer = self.buffer.split_at(buffer.len()).1;
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RequestSource<R> {
|
pub struct RequestSource<R> {
|
||||||
|
|||||||
+6
-3
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
use std::{path::PathBuf, process, time::SystemTime};
|
use std::{path::PathBuf, process, time::SystemTime};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
use app::Application;
|
use app::Application;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use emulator::EmulatorBuilder;
|
use emulator::EmulatorBuilder;
|
||||||
@@ -95,10 +95,13 @@ fn main() -> Result<()> {
|
|||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let (mut builder, client) = EmulatorBuilder::new();
|
let (mut builder, client) = EmulatorBuilder::new();
|
||||||
if let Some(path) = args.rom {
|
if let Some(path) = &args.rom {
|
||||||
builder = builder.with_rom(&path);
|
builder = builder.with_rom(path);
|
||||||
}
|
}
|
||||||
if args.debug_port.is_some() {
|
if args.debug_port.is_some() {
|
||||||
|
if args.rom.is_none() {
|
||||||
|
bail!("to start debugging, please select a game.");
|
||||||
|
}
|
||||||
builder = builder.start_paused(true);
|
builder = builder.start_paused(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-2
@@ -3,7 +3,7 @@ use winit::event_loop::EventLoopProxy;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::UserEvent,
|
app::UserEvent,
|
||||||
emulator::{EmulatorClient, SimId},
|
emulator::{EmulatorClient, SimId, SimState},
|
||||||
gdbserver::{GdbServer, GdbServerStatus},
|
gdbserver::{GdbServer, GdbServerStatus},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ use super::AppWindow;
|
|||||||
|
|
||||||
pub struct GdbServerWindow {
|
pub struct GdbServerWindow {
|
||||||
sim_id: SimId,
|
sim_id: SimId,
|
||||||
|
client: EmulatorClient,
|
||||||
port_str: String,
|
port_str: String,
|
||||||
connected: bool,
|
connected: bool,
|
||||||
quit_on_disconnect: bool,
|
quit_on_disconnect: bool,
|
||||||
@@ -22,6 +23,7 @@ impl GdbServerWindow {
|
|||||||
pub fn new(sim_id: SimId, client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
pub fn new(sim_id: SimId, client: EmulatorClient, proxy: EventLoopProxy<UserEvent>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
sim_id,
|
sim_id,
|
||||||
|
client: client.clone(),
|
||||||
port_str: (8080 + sim_id.to_index()).to_string(),
|
port_str: (8080 + sim_id.to_index()).to_string(),
|
||||||
connected: false,
|
connected: false,
|
||||||
quit_on_disconnect: false,
|
quit_on_disconnect: false,
|
||||||
@@ -78,7 +80,9 @@ impl AppWindow for GdbServerWindow {
|
|||||||
self.connected = false;
|
self.connected = false;
|
||||||
}
|
}
|
||||||
let start_button = Button::new("Start");
|
let start_button = Button::new("Start");
|
||||||
if ui.add_enabled(port_num.is_some(), start_button).clicked() {
|
let can_start =
|
||||||
|
port_num.is_some() && self.client.sim_state(self.sim_id) == SimState::Ready;
|
||||||
|
if ui.add_enabled(can_start, start_button).clicked() {
|
||||||
let port = port_num.unwrap();
|
let port = port_num.unwrap();
|
||||||
self.server.start(port);
|
self.server.start(port);
|
||||||
self.connected = true;
|
self.connected = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user