From c47d9e1f11f066d2ddd58600ffe6245189ae8de7 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 17 Sep 2022 02:19:46 +0300 Subject: [PATCH] Experimental gdbserver Former-commit-id: e78618b03c745bb9820216e6d9f8c1f4cade28d5 Former-commit-id: 5851f5930e07d8132e643bbe6773bdd0bd42fad6 --- Cargo.lock | 112 +++++++++++++-- arm7tdmi/src/cpu.rs | 25 +++- arm7tdmi/src/gdb/breakpoints.rs | 26 +--- arm7tdmi/src/gdb/target.rs | 2 +- arm7tdmi/src/lib.rs | 1 + core/Cargo.toml | 6 +- core/src/bios.rs | 5 + core/src/cartridge/loader.rs | 6 +- core/src/gba.rs | 98 +++++++++++-- core/src/gdb_support.rs | 168 ++++++++++++++++++++++ core/src/gdb_support/event_loop.rs | 65 +++++++++ core/src/gdb_support/gdb_thread.rs | 63 ++++++++ core/src/gdb_support/memory_map.rs | 56 ++++++++ core/src/gdb_support/target.rs | 155 ++++++++++++++++++++ core/src/iodev.rs | 2 +- core/src/lib.rs | 10 ++ core/src/sysbus.rs | 9 +- platform/rustboyadvance-sdl2/src/input.rs | 20 ++- platform/rustboyadvance-sdl2/src/main.rs | 22 +-- 19 files changed, 774 insertions(+), 77 deletions(-) create mode 100644 core/src/gdb_support.rs create mode 100644 core/src/gdb_support/event_loop.rs create mode 100644 core/src/gdb_support/gdb_thread.rs create mode 100644 core/src/gdb_support/memory_map.rs create mode 100644 core/src/gdb_support/target.rs diff --git a/Cargo.lock b/Cargo.lock index 0f85c5c..baf6b77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -438,27 +438,62 @@ dependencies = [ "itertools 0.9.0", ] +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel 0.5.6", + "crossbeam-deque 0.8.2", + "crossbeam-epoch 0.9.10", + "crossbeam-queue", + "crossbeam-utils 0.8.11", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" dependencies = [ - "crossbeam-utils", + "crossbeam-utils 0.7.2", "maybe-uninit", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.11", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "crossbeam-epoch 0.8.2", + "crossbeam-utils 0.7.2", "maybe-uninit", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch 0.9.10", + "crossbeam-utils 0.8.11", +] + [[package]] name = "crossbeam-epoch" version = "0.8.2" @@ -467,13 +502,37 @@ checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ "autocfg", "cfg-if 0.1.10", - "crossbeam-utils", + "crossbeam-utils 0.7.2", "lazy_static", "maybe-uninit", - "memoffset", + "memoffset 0.5.6", "scopeguard", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "crossbeam-utils 0.8.11", + "memoffset 0.6.5", + "once_cell", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.11", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -485,6 +544,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", +] + [[package]] name = "csv" version = "1.1.3" @@ -945,6 +1014,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memory_units" version = "0.4.0" @@ -1116,6 +1194,12 @@ version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + [[package]] name = "oorandom" version = "11.1.2" @@ -1243,7 +1327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" dependencies = [ "autocfg", - "crossbeam-deque", + "crossbeam-deque 0.7.3", "either", "rayon-core", ] @@ -1254,9 +1338,9 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", + "crossbeam-channel 0.4.4", + "crossbeam-deque 0.7.3", + "crossbeam-utils 0.7.2", "lazy_static", "num_cpus", ] @@ -1320,7 +1404,7 @@ dependencies = [ "base64", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", + "crossbeam-utils 0.7.2", ] [[package]] @@ -1342,6 +1426,7 @@ dependencies = [ "chrono", "colored 1.9.3", "criterion", + "crossbeam", "debug_stub_derive", "enum-primitive-derive", "fuzzy-matcher", @@ -1359,6 +1444,7 @@ dependencies = [ "sha2", "smart-default", "time 0.2.22", + "xml-builder", "yaml-rust", "zip", ] @@ -2149,6 +2235,12 @@ dependencies = [ "toml", ] +[[package]] +name = "xml-builder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aff3eebeb8c12d38dd6dcbd07082929a55764d13ad695632c35132ff7cf61ec" + [[package]] name = "yaml-rust" version = "0.4.4" diff --git a/arm7tdmi/src/cpu.rs b/arm7tdmi/src/cpu.rs index da74b1c..a395099 100644 --- a/arm7tdmi/src/cpu.rs +++ b/arm7tdmi/src/cpu.rs @@ -1,3 +1,4 @@ +use log::debug; use serde::{Deserialize, Serialize}; pub use super::exception::Exception; @@ -118,7 +119,7 @@ pub struct Arm7tdmiCore { pub banks: BankedRegisters, /// Hardware breakpoints for use by gdb - pub breakpoints: Vec, + breakpoints: Vec, /// Deprecated in-house debugger state #[cfg(feature = "debugger")] @@ -196,6 +197,28 @@ impl Arm7tdmiCore { self.bus = i; } + pub fn add_breakpoint(&mut self, addr: Addr) { + debug!("adding breakpoint {:08x}", addr); + self.breakpoints.push(addr); + } + + pub fn del_breakpoint(&mut self, addr: Addr) { + if let Some(pos) = self.breakpoints.iter().position(|x| *x == addr) { + debug!("deleting breakpoint {:08x}", addr); + self.breakpoints.remove(pos); + } + } + + pub fn check_breakpoint(&self) -> Option { + let next_pc = self.get_next_pc(); + for bp in &self.breakpoints { + if (*bp & !1) == next_pc { + return Some(*bp); + } + } + None + } + #[cfg(feature = "debugger")] pub fn set_verbose(&mut self, v: bool) { self.dbg.verbose = v; diff --git a/arm7tdmi/src/gdb/breakpoints.rs b/arm7tdmi/src/gdb/breakpoints.rs index 8c23476..4fd896b 100644 --- a/arm7tdmi/src/gdb/breakpoints.rs +++ b/arm7tdmi/src/gdb/breakpoints.rs @@ -1,6 +1,5 @@ use gdbstub::target; use gdbstub::target::TargetResult; -use log::debug; use crate::Arm7tdmiCore; @@ -22,8 +21,7 @@ impl target::ext::breakpoints::SwBreakpoint for Arm7tdmiC addr: u32, _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { - debug!("adding breakpoint {:08x}", addr); - self.breakpoints.push(addr); + self.add_breakpoint(addr); Ok(true) } @@ -32,25 +30,7 @@ impl target::ext::breakpoints::SwBreakpoint for Arm7tdmiC addr: u32, _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { - match self.breakpoints.iter().position(|x| *x == addr) { - None => Ok(false), - Some(pos) => { - debug!("deleting breakpoint {:08x}", addr); - self.breakpoints.remove(pos); - Ok(true) - } - } - } -} - -impl Arm7tdmiCore { - pub fn check_breakpoint(&self) -> Option { - let next_pc = self.get_next_pc(); - for bp in &self.breakpoints { - if (*bp & !1) == next_pc { - return Some(*bp); - } - } - None + self.del_breakpoint(addr); + Ok(true) } } diff --git a/arm7tdmi/src/gdb/target.rs b/arm7tdmi/src/gdb/target.rs index 6fc3b4a..d8305a4 100644 --- a/arm7tdmi/src/gdb/target.rs +++ b/arm7tdmi/src/gdb/target.rs @@ -41,7 +41,7 @@ impl SingleThreadBase for Arm7tdmiCore { &mut self, regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, ) -> TargetResult<(), Self> { - regs.pc = self.get_reg(REG_PC); + regs.pc = self.get_next_pc(); regs.lr = self.get_reg(REG_LR); regs.sp = self.get_reg(REG_SP); regs.r[..].copy_from_slice(&self.gpr[..13]); diff --git a/arm7tdmi/src/lib.rs b/arm7tdmi/src/lib.rs index 4d477d6..daa61bb 100644 --- a/arm7tdmi/src/lib.rs +++ b/arm7tdmi/src/lib.rs @@ -24,6 +24,7 @@ use memory::Addr; pub mod disass; pub mod exception; pub mod gdb; +pub use gdb::{gdbstub, gdbstub_arch}; pub mod psr; mod simple_memory; pub use simple_memory::SimpleMemory; diff --git a/core/Cargo.toml b/core/Cargo.toml index 1ec84dd..e40e388 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -5,8 +5,8 @@ authors = ["Michel Heily "] edition = "2018" [dependencies] -arm7tdmi = { "path" = "../arm7tdmi" } -rustboyadvance-utils = { "path" = "../utils" } +arm7tdmi = { path = '../arm7tdmi' } +rustboyadvance-utils = { path = "../utils" } cfg-if = "1.0.0" serde = { version = "1.0.104", features = ["derive", "rc"] } bincode = "1.2.1" @@ -41,6 +41,8 @@ bit_reverse = "0.1.8" yaml-rust = "0.4" lazy_static = "1.4.0" smart-default = "0.6.0" +crossbeam = "0.8.2" +xml-builder = "0.5.0" [dev-dependencies] criterion = "0.3" diff --git a/core/src/bios.rs b/core/src/bios.rs index 8f67ba2..a3427ef 100644 --- a/core/src/bios.rs +++ b/core/src/bios.rs @@ -34,6 +34,11 @@ impl Bios { fn read_allowed(&self) -> bool { self.arm_core.pc < 0x4000 } + + #[inline] + pub(crate) fn len(&self) -> usize { + self.rom.len() + } } /// Impl of Bus trait for Bios diff --git a/core/src/cartridge/loader.rs b/core/src/cartridge/loader.rs index 99ce52d..2a84fd1 100644 --- a/core/src/cartridge/loader.rs +++ b/core/src/cartridge/loader.rs @@ -11,6 +11,9 @@ use rustboyadvance_utils::elf::{load_elf, GoblinError}; use rustboyadvance_utils::read_bin_file; use zip::ZipArchive; +#[cfg(feature = "elf_support")] +use crate::sysbus::consts::CART_BASE; + pub enum LoadRom { #[cfg(feature = "elf_support")] Elf { @@ -30,8 +33,7 @@ impl From for GBAError { #[cfg(feature = "elf_support")] pub(super) fn try_load_elf(elf_bytes: &[u8]) -> LoadRomResult { - const CART_BASE: usize = 0x0800_0000; - let elf = load_elf(elf_bytes, CART_BASE)?; + let elf = load_elf(elf_bytes, CART_BASE as usize)?; Ok(LoadRom::Elf { data: elf.data, symbols: elf.symbols, diff --git a/core/src/gba.rs b/core/src/gba.rs index 9fe6965..aee2379 100644 --- a/core/src/gba.rs +++ b/core/src/gba.rs @@ -2,9 +2,12 @@ use std::cell::Cell; use std::rc::Rc; +use arm7tdmi::gdbstub::stub::SingleThreadStopReason; use bincode; use serde::{Deserialize, Serialize}; +use crate::gdb_support::{gdb_thread::start_gdb_server_thread, DebuggerState}; + use super::cartridge::Cartridge; use super::dma::DmaController; use super::gpu::*; @@ -21,12 +24,13 @@ use arm7tdmi::{self, Arm7tdmiCore}; use rustboyadvance_utils::Shared; pub struct GameBoyAdvance { - pub cpu: Box>, - pub sysbus: Shared, - pub io_devs: Shared, - pub scheduler: SharedScheduler, + pub(crate) cpu: Box>, + pub(crate) sysbus: Shared, + pub(crate) io_devs: Shared, + pub(crate) scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags, - pub audio_interface: DynAudioInterface, + audio_interface: DynAudioInterface, + pub(crate) debugger: Option, } #[derive(Serialize, Deserialize)] @@ -106,6 +110,7 @@ impl GameBoyAdvance { audio_interface, scheduler, interrupt_flags, + debugger: None, }; gba.sysbus.init(gba.cpu.weak_ptr()); @@ -150,6 +155,7 @@ impl GameBoyAdvance { interrupt_flags: interrupts, audio_interface, scheduler, + debugger: None, }) } @@ -206,10 +212,62 @@ impl GameBoyAdvance { &mut self.sysbus.io.keyinput } + /// Advance the emulation for one frame worth of time pub fn frame(&mut self) { static mut OVERSHOOT: usize = 0; unsafe { - OVERSHOOT = self.run(CYCLES_FULL_REFRESH - OVERSHOOT); + OVERSHOOT = CYCLES_FULL_REFRESH - self.run::(CYCLES_FULL_REFRESH - OVERSHOOT); + } + } + + /// like frame() but stop if a breakpoint is reached + fn frame_interruptible(&mut self) { + static mut OVERSHOOT: usize = 0; + unsafe { + OVERSHOOT = CYCLES_FULL_REFRESH - self.run::(CYCLES_FULL_REFRESH - OVERSHOOT); + } + } + + pub fn start_gdbserver(&mut self, port: u16) { + if self.is_debugger_attached() { + warn!("debugger already attached!"); + } else { + match start_gdb_server_thread(self, port) { + Ok(debugger) => { + info!("attached to the debugger, have fun!"); + self.debugger = Some(debugger) + } + Err(e) => { + error!("failed to start the debugger: {:?}", e); + } + } + } + } + + #[inline] + pub fn is_debugger_attached(&self) -> bool { + self.debugger.is_some() + } + + /// Recv & handle messages from the debugger, and return if we are stopped or not + pub fn debugger_run(&mut self) { + let mut should_stop = false; + let debugger = self.debugger.take().expect("debugger should be None here"); + let debugger = debugger + .handle_message(self, &mut should_stop) + .map_err(|_| "Failed to handle message") + .unwrap(); + + self.debugger = debugger; + + if let Some(debugger) = &mut self.debugger { + if should_stop { + debugger.notify_stop_reason(SingleThreadStopReason::DoneStep); + } else { + self.frame_interruptible(); + } + } else { + error!("debugger was disconnected!"); } } @@ -237,9 +295,9 @@ impl GameBoyAdvance { } /// Runs the emulation for a given amount of cycles - /// @return number of extra cycle ran in this iteration + /// @return number of cycle actually ran #[inline] - fn run(&mut self, cycles_to_run: usize) -> usize { + pub(super) fn run(&mut self, cycles_to_run: usize) -> usize { let run_start_time = self.scheduler.timestamp(); // Register an event to mark the end of this run @@ -251,14 +309,31 @@ impl GameBoyAdvance { // The tricky part is to avoid unnecessary calls for Scheduler::process_pending, // performance-wise it would be best to run as many cycles as fast as possible while we know there are no pending events. // Fast forward emulation until an event occurs - while self.scheduler.timestamp() <= self.scheduler.timestamp_of_next_event() { + 'run_unitl_next_event: while self.scheduler.timestamp() + <= self.scheduler.timestamp_of_next_event() + { // 3 Options: // 1. DMA is active - thus CPU is blocked // 2. DMA inactive and halt state is RUN - CPU can run // 3. DMA inactive and halt state is HALT - CPU is blocked match self.get_bus_master() { Some(BusMaster::Dma) => self.dma_step(), - Some(BusMaster::Cpu) => self.cpu_step(), + Some(BusMaster::Cpu) => { + self.cpu_step(); + if CHECK_BREAKPOINTS { + if let Some(bp) = self.cpu.check_breakpoint() { + debug!("Arm7tdmi breakpoint hit 0x{:08x}", bp); + self.scheduler.cancel_pending(EventType::RunLimitReached); + running = false; + + if let Some(debugger) = &mut self.debugger { + debugger.notify_breakpoint(bp); + } + + break 'run_unitl_next_event; + } + } + } None => { if self.io_devs.intc.irq_pending() { self.io_devs.haltcnt = HaltState::Running; @@ -273,8 +348,7 @@ impl GameBoyAdvance { self.handle_events(&mut running); } - let total_cycles_ran = self.scheduler.timestamp() - run_start_time; - total_cycles_ran - cycles_to_run + self.scheduler.timestamp() - run_start_time } fn handle_events(&mut self, run_limit_flag: &mut bool) { diff --git a/core/src/gdb_support.rs b/core/src/gdb_support.rs new file mode 100644 index 0000000..c94095f --- /dev/null +++ b/core/src/gdb_support.rs @@ -0,0 +1,168 @@ +use std::result; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread::JoinHandle; + +type SendSync = Arc>; + +use arm7tdmi::gdbstub::stub::{DisconnectReason, SingleThreadStopReason}; +use arm7tdmi::gdbstub::target::TargetError; +use arm7tdmi::gdbstub::target::{ext::base::singlethread::SingleThreadBase, Target}; +use arm7tdmi::gdbstub_arch::arm::reg::ArmCoreRegs; +use arm7tdmi::memory::Addr; +use crossbeam::channel::{Receiver, Sender}; + +// mod target; +mod event_loop; +pub(crate) mod gdb_thread; +mod memory_map; +mod target; + +use crate::GameBoyAdvance; + +#[derive(Debug)] +pub(crate) enum DebuggerMessage { + ReadRegs(SendSync), + WriteRegs(ArmCoreRegs), + ReadAddrs(Addr, SendSync>), + #[allow(unused)] + WriteAddrs(Addr, Box<[u8]>), + AddSwBreakpoint(Addr), + DelSwBreakpoint(Addr), + Stop, + Resume, + SingleStep, + Disconnected(DisconnectReason), +} + +pub struct DebuggerTarget { + tx: Sender, + operation_signal: Arc<(Mutex, Condvar)>, + stop_signal: Arc<(Mutex>, Condvar)>, + memory_map: String, +} + +impl DebuggerTarget { + #[inline] + pub fn wait_for_operation(&mut self) { + let (lock, cvar) = &*self.operation_signal; + let mut finished = lock.lock().unwrap(); + while !*finished { + finished = cvar.wait(finished).unwrap(); + } + *finished = false; + } +} + +pub(crate) struct DebuggerState { + rx: Receiver, + operation_signal: Arc<(Mutex, Condvar)>, + stop_signal: Arc<(Mutex>, Condvar)>, + thread: JoinHandle<()>, + stopped: bool, +} + +impl DebuggerState { + pub fn handle_message( + mut self, + gba: &mut GameBoyAdvance, + should_stop: &mut bool, + ) -> Result, TargetError<::Error>> { + if self.thread.is_finished() { + warn!("gdb server thread unexpectdly died"); + *should_stop = true; + self.thread.join().unwrap(); + return Ok(None); + } + if let Ok(msg) = self.rx.try_recv() { + use DebuggerMessage::*; + let mut result = match msg { + ReadRegs(regs) => { + let mut regs = regs.lock().unwrap(); + gba.cpu.read_registers(&mut regs)?; + debug!("Debugger requested to read regs: {:?}", regs); + Ok(Some(self)) + } + WriteRegs(regs) => { + debug!("Debugger requested to write regs: {:?}", regs); + gba.cpu.write_registers(®s)?; + Ok(Some(self)) + } + ReadAddrs(addr, data) => { + let mut data = data.lock().unwrap(); + debug!( + "Debugger requested to read {} bytes from 0x{:08x}", + data.len(), + addr + ); + gba.cpu.read_addrs(addr, &mut data)?; + Ok(Some(self)) + } + WriteAddrs(addr, data) => { + debug!( + "Debugger requested to write {} bytes at 0x{:08x}", + data.len(), + addr + ); + gba.cpu.write_addrs(addr, &data)?; + Ok(Some(self)) + } + Stop => { + debug!("Debugger requested stopped"); + self.stopped = true; + Ok(Some(self)) + } + Resume => { + debug!("Debugger requested resume"); + self.stopped = false; + Ok(Some(self)) + } + SingleStep => { + debug!("Debugger requested single step"); + gba.run::(1); + self.notify_stop_reason(SingleThreadStopReason::DoneStep); + self.stopped = true; + Ok(Some(self)) + } + AddSwBreakpoint(addr) => { + gba.cpu.add_breakpoint(addr); + Ok(Some(self)) + } + DelSwBreakpoint(addr) => { + gba.cpu.del_breakpoint(addr); + Ok(Some(self)) + } + Disconnected(reason) => { + debug!("Debugger disconnected due to {:?}", reason); + debug!("closing gdbserver thread"); + self.thread.join().unwrap(); + Ok(None) + } + }; + if let Ok(Some(result)) = &mut result { + let (lock, cvar) = &*result.operation_signal; + let mut finished = lock.lock().unwrap(); + *finished = true; + cvar.notify_one(); + *should_stop = result.stopped; + } else { + *should_stop = true; + } + result + } else { + *should_stop = self.stopped; + Ok(Some(self)) + } + } + + pub fn notify_stop_reason(&mut self, reason: SingleThreadStopReason) { + self.stopped = true; + let (lock, cvar) = &*self.stop_signal; + let mut stop_reason = lock.lock().unwrap(); + *stop_reason = reason; + cvar.notify_one(); + } + + pub fn notify_breakpoint(&mut self, _bp: Addr) { + self.notify_stop_reason(SingleThreadStopReason::SwBreak(())); + } +} diff --git a/core/src/gdb_support/event_loop.rs b/core/src/gdb_support/event_loop.rs new file mode 100644 index 0000000..e7593ad --- /dev/null +++ b/core/src/gdb_support/event_loop.rs @@ -0,0 +1,65 @@ +use std::time::Duration; + +use arm7tdmi::gdb::gdbstub::{ + conn::{Connection, ConnectionExt}, + stub::{run_blocking, SingleThreadStopReason}, + target::Target, +}; + +use super::{DebuggerMessage, DebuggerTarget}; + +pub struct DebuggerEventLoop {} + +impl run_blocking::BlockingEventLoop for DebuggerEventLoop { + type Target = DebuggerTarget; + type Connection = Box>; + type StopReason = SingleThreadStopReason; + + fn wait_for_stop_reason( + target: &mut Self::Target, + conn: &mut Self::Connection, + ) -> Result< + run_blocking::Event>, + run_blocking::WaitForStopReasonError< + ::Error, + ::Error, + >, + > { + let mut poll_incoming_data = || conn.peek().map(|b| b.is_some()).unwrap_or(true); + + loop { + if poll_incoming_data() { + let byte = conn + .read() + .map_err(run_blocking::WaitForStopReasonError::Connection)?; + return Ok(run_blocking::Event::IncomingData(byte)); + } else { + // try and wait for the stop reason + let (lock, cvar) = &*target.stop_signal; + let stop_reason = lock.lock().unwrap(); + let (stop_reason, timeout_result) = cvar + .wait_timeout(stop_reason, Duration::from_millis(10)) + .unwrap(); + if timeout_result.timed_out() { + // timed-out, try again later + continue; + } + info!("Target stopped due to {:?}!", stop_reason); + return Ok(run_blocking::Event::TargetStopped(*stop_reason)); + } + } + } + + fn on_interrupt( + target: &mut DebuggerTarget, + ) -> Result>, ::Error> { + info!("on_interrupt: sending stop message"); + target.tx.send(DebuggerMessage::Stop).unwrap(); + target.wait_for_operation(); + info!("Waiting for target to stop "); + let (lock, cvar) = &*target.stop_signal; + let stop_signal = lock.lock().unwrap(); + let stop_signal = cvar.wait(stop_signal).unwrap(); + Ok(Some(*stop_signal)) + } +} diff --git a/core/src/gdb_support/gdb_thread.rs b/core/src/gdb_support/gdb_thread.rs new file mode 100644 index 0000000..389fd77 --- /dev/null +++ b/core/src/gdb_support/gdb_thread.rs @@ -0,0 +1,63 @@ +use std::sync::{Arc, Condvar, Mutex}; + +use arm7tdmi::{ + gdb::wait_for_connection, + gdbstub::{ + common::Signal, + conn::ConnectionExt, + stub::{GdbStub, SingleThreadStopReason}, + }, +}; + +use crate::{GBAError, GameBoyAdvance}; + +use super::{event_loop::DebuggerEventLoop, DebuggerMessage, DebuggerState, DebuggerTarget}; + +/// Starts a gdbserver thread +pub(crate) fn start_gdb_server_thread( + gba: &mut GameBoyAdvance, + port: u16, +) -> Result { + let (tx, rx) = crossbeam::channel::unbounded(); + let operation_signal = Arc::new((Mutex::new(false), Condvar::new())); + let stop_signal = Arc::new(( + Mutex::new(SingleThreadStopReason::Signal(Signal::SIGINT)), + Condvar::new(), + )); + let stop_signal_2 = stop_signal.clone(); + let operation_signal_2 = operation_signal.clone(); + let memory_map = gba.sysbus.generate_memory_map_xml().unwrap(); + + let conn = wait_for_connection(port)?; + let thread = std::thread::spawn(move || { + debug!("starting GDB Server thread"); + let conn: Box> = Box::new(conn); + + let mut target = DebuggerTarget { + tx, + operation_signal: operation_signal_2, + stop_signal: stop_signal_2, + memory_map, + }; + let gdbserver = GdbStub::new(conn); + let disconnect_reason = gdbserver + .run_blocking::(&mut target) + .map_err(|e| e.to_string()) + .unwrap(); + target + .tx + .send(DebuggerMessage::Disconnected(disconnect_reason)) + .unwrap(); + }); + + let mut debugger = DebuggerState { + rx, + operation_signal, + stop_signal, + thread, + stopped: true, + }; + debugger.notify_stop_reason(SingleThreadStopReason::Signal(Signal::SIGINT)); + + Ok(debugger) +} diff --git a/core/src/gdb_support/memory_map.rs b/core/src/gdb_support/memory_map.rs new file mode 100644 index 0000000..3d2e4b8 --- /dev/null +++ b/core/src/gdb_support/memory_map.rs @@ -0,0 +1,56 @@ +use xml_builder::{XMLBuilder, XMLElement, XMLVersion}; + +use arm7tdmi::{ + gdb::{copy_range_to_buf, target::MemoryGdbInterface}, + memory::Addr, +}; + +use crate::sysbus::{consts, SysBus}; + +impl SysBus { + pub fn generate_memory_map_xml(&self) -> Result> { + let mut xml = XMLBuilder::new() + .version(XMLVersion::XML1_1) + .encoding("UTF-8".into()) + .build(); + let mut memory_map = XMLElement::new("memory-map"); + + let mut add_memory = |start: Addr, length: usize| -> Result<(), String> { + let mut memory = XMLElement::new("memory"); + memory.add_attribute("type", "ram"); // using "ram" for everything to allow use of sw-breakpoints + memory.add_attribute("start", &start.to_string()); + memory.add_attribute("length", &length.to_string()); + memory_map + .add_child(memory) + .map_err(|e| format!("failed to add child: {:?}", e))?; + Ok(()) + }; + + add_memory(consts::BIOS_ADDR, self.bios.len())?; + add_memory(consts::EWRAM_ADDR, self.ewram.len())?; + add_memory(consts::IWRAM_ADDR, self.iwram.len())?; + add_memory(consts::IOMEM_ADDR, 0x400)?; + add_memory(consts::PALRAM_ADDR, self.io.gpu.palette_ram.len())?; + add_memory(consts::VRAM_ADDR, self.io.gpu.vram.len())?; + add_memory(consts::OAM_ADDR, self.io.gpu.oam.len())?; + add_memory(consts::CART_BASE, self.cartridge.get_rom_bytes().len())?; + + xml.set_root_element(memory_map); + let mut writer = Vec::new(); + xml.generate(&mut writer) + .map_err(|e| format!("failed to generate xml: {:?}", e))?; + + Ok(String::from_utf8(writer)?) + } +} + +impl MemoryGdbInterface for SysBus { + fn memory_map_xml(&self, offset: u64, length: usize, buf: &mut [u8]) -> usize { + copy_range_to_buf( + self.generate_memory_map_xml().unwrap().as_bytes(), + offset, + length, + buf, + ) + } +} diff --git a/core/src/gdb_support/target.rs b/core/src/gdb_support/target.rs new file mode 100644 index 0000000..be8acb0 --- /dev/null +++ b/core/src/gdb_support/target.rs @@ -0,0 +1,155 @@ +use std::sync::{Arc, Mutex}; + +/// Implementing the Target trait for gdbstub +use arm7tdmi::gdb::{copy_range_to_buf, gdbstub, gdbstub_arch}; +use gdbstub::common::Signal; +use gdbstub::target::ext::base::singlethread::{ + SingleThreadBase, SingleThreadResume, SingleThreadSingleStep, +}; +use gdbstub::target::ext::base::singlethread::{SingleThreadResumeOps, SingleThreadSingleStepOps}; +use gdbstub::target::ext::base::BaseOps; +use gdbstub::target::ext::breakpoints::BreakpointsOps; +use gdbstub::target::{self, Target, TargetError, TargetResult}; +use gdbstub_arch::arm::reg::ArmCoreRegs; + +use super::{DebuggerMessage, DebuggerTarget}; + +impl Target for DebuggerTarget { + type Error = (); + type Arch = gdbstub_arch::arm::Armv4t; // as an example + + #[inline(always)] + fn base_ops(&mut self) -> BaseOps { + BaseOps::SingleThread(self) + } + + // opt-in to support for setting/removing breakpoints + #[inline(always)] + fn support_breakpoints(&mut self) -> Option> { + Some(self) + } + + fn support_memory_map(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadBase for DebuggerTarget { + fn read_registers(&mut self, regs: &mut ArmCoreRegs) -> TargetResult<(), Self> { + let regs_copy = Arc::new(Mutex::new(ArmCoreRegs::default())); + self.tx + .send(DebuggerMessage::ReadRegs(regs_copy.clone())) + .unwrap(); + self.wait_for_operation(); + regs_copy.lock().unwrap().clone_into(regs); + Ok(()) + } + + fn write_registers(&mut self, regs: &ArmCoreRegs) -> TargetResult<(), Self> { + self.tx + .send(DebuggerMessage::WriteRegs(regs.clone())) + .unwrap(); + self.wait_for_operation(); + Ok(()) + } + + fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> { + let buffer = Arc::new(Mutex::new(vec![0; data.len()].into_boxed_slice())); + self.tx + .send(DebuggerMessage::ReadAddrs(start_addr, buffer.clone())) + .unwrap(); + self.wait_for_operation(); + data.copy_from_slice(&buffer.lock().unwrap()); + Ok(()) + } + + fn write_addrs(&mut self, _start_addr: u32, _data: &[u8]) -> TargetResult<(), Self> { + // todo!("implement DebugWrite bus extention") + Err(TargetError::NonFatal) + } + + // most targets will want to support at resumption as well... + + #[inline(always)] + fn support_resume(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadResume for DebuggerTarget { + fn resume(&mut self, _signal: Option) -> Result<(), Self::Error> { + self.tx.send(DebuggerMessage::Resume).unwrap(); + self.wait_for_operation(); + Ok(()) + } + + // ...and if the target supports resumption, it'll likely want to support + // single-step resume as well + + #[inline(always)] + fn support_single_step(&mut self) -> Option> { + Some(self) + } +} + +impl SingleThreadSingleStep for DebuggerTarget { + fn step(&mut self, _signal: Option) -> Result<(), Self::Error> { + self.tx.send(DebuggerMessage::SingleStep).unwrap(); + self.wait_for_operation(); + self.tx.send(DebuggerMessage::Stop).unwrap(); + self.wait_for_operation(); + Ok(()) + } +} + +impl target::ext::memory_map::MemoryMap for DebuggerTarget { + fn memory_map_xml( + &self, + offset: u64, + length: usize, + buf: &mut [u8], + ) -> TargetResult { + Ok(copy_range_to_buf( + self.memory_map.as_bytes(), + offset, + length, + buf, + )) + } +} + +impl target::ext::breakpoints::Breakpoints for DebuggerTarget { + // there are several kinds of breakpoints - this target uses software breakpoints + #[inline(always)] + fn support_sw_breakpoint( + &mut self, + ) -> Option> { + Some(self) + } +} + +impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget { + fn add_sw_breakpoint( + &mut self, + addr: u32, + _kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { + self.tx + .send(DebuggerMessage::AddSwBreakpoint(addr)) + .unwrap(); + self.wait_for_operation(); + Ok(true) + } + + fn remove_sw_breakpoint( + &mut self, + addr: u32, + _kind: gdbstub_arch::arm::ArmBreakpointKind, + ) -> TargetResult { + self.tx + .send(DebuggerMessage::DelSwBreakpoint(addr)) + .unwrap(); + self.wait_for_operation(); + Ok(true) + } +} diff --git a/core/src/iodev.rs b/core/src/iodev.rs index c30ff3c..84c597b 100644 --- a/core/src/iodev.rs +++ b/core/src/iodev.rs @@ -145,7 +145,7 @@ impl BusIO for IoDevices { REG_POSTFLG => io.post_boot_flag as u16, REG_HALTCNT => 0, - REG_KEYINPUT => io.keyinput as u16, + REG_KEYINPUT => io.keyinput, x if DebugPort::is_debug_access(x) => io.debug.read(io_addr), diff --git a/core/src/lib.rs b/core/src/lib.rs index 77882ab..9c74739 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -45,11 +45,14 @@ pub use interrupt::SharedInterruptFlags; pub mod gba; pub use gba::GameBoyAdvance; pub mod dma; +pub mod gdb_support; pub mod keypad; mod mgba_debug; pub(crate) mod overrides; pub mod timer; +use arm7tdmi::gdb::gdbstub::stub::GdbStubError; + #[cfg(feature = "debugger")] pub mod debugger; @@ -59,6 +62,7 @@ pub enum GBAError { CartridgeLoadError(String), #[cfg(feature = "debugger")] DebuggerError(debugger::DebuggerError), + GdbError(String), } impl fmt::Display for GBAError { @@ -94,6 +98,12 @@ impl From for GBAError { } } +impl From> for GBAError { + fn from(err: GdbStubError<(), std::io::Error>) -> Self { + GBAError::GdbError(err.to_string()) + } +} + pub mod prelude { pub use super::cartridge::{Cartridge, GamepakBuilder}; #[cfg(feature = "debugger")] diff --git a/core/src/sysbus.rs b/core/src/sysbus.rs index 6518156..34a3540 100644 --- a/core/src/sysbus.rs +++ b/core/src/sysbus.rs @@ -22,7 +22,8 @@ pub mod consts { pub const PALRAM_ADDR: u32 = 0x0500_0000; pub const VRAM_ADDR: u32 = 0x0600_0000; pub const OAM_ADDR: u32 = 0x0700_0000; - pub const GAMEPAK_WS0_LO: u32 = 0x0800_0000; + pub const CART_BASE: u32 = 0x0800_0000; + pub const GAMEPAK_WS0_LO: u32 = CART_BASE; pub const GAMEPAK_WS0_HI: u32 = 0x0900_0000; pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000; pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000; @@ -148,9 +149,9 @@ pub struct SysBus { scheduler: Shared, arm_core: WeakPointer>, - bios: Bios, - ewram: Box<[u8]>, - iwram: Box<[u8]>, + pub(crate) bios: Bios, + pub(crate) ewram: Box<[u8]>, + pub(crate) iwram: Box<[u8]>, pub cartridge: Cartridge, cycle_luts: CycleLookupTables, diff --git a/platform/rustboyadvance-sdl2/src/input.rs b/platform/rustboyadvance-sdl2/src/input.rs index 17365f2..cabb32a 100644 --- a/platform/rustboyadvance-sdl2/src/input.rs +++ b/platform/rustboyadvance-sdl2/src/input.rs @@ -1,4 +1,3 @@ -use rustboyadvance_core::GameBoyAdvance; use sdl2::controller::Axis; use sdl2::controller::Button; use sdl2::keyboard::Scancode; @@ -8,31 +7,31 @@ use rustboyadvance_core::keypad as gba_keypad; use bit; use bit::BitIndex; -pub fn on_keyboard_key_down(gba: &mut GameBoyAdvance, scancode: Scancode) { +pub fn on_keyboard_key_down(key_state: &mut u16, scancode: Scancode) { if let Some(key) = scancode_to_keypad(scancode) { - gba.get_key_state_mut().set_bit(key as usize, false); + key_state.set_bit(key as usize, false); } } -pub fn on_keyboard_key_up(gba: &mut GameBoyAdvance, scancode: Scancode) { +pub fn on_keyboard_key_up(key_state: &mut u16, scancode: Scancode) { if let Some(key) = scancode_to_keypad(scancode) { - gba.get_key_state_mut().set_bit(key as usize, true); + key_state.set_bit(key as usize, true); } } -pub fn on_controller_button_down(gba: &mut GameBoyAdvance, button: Button) { +pub fn on_controller_button_down(key_state: &mut u16, button: Button) { if let Some(key) = controller_button_to_keypad(button) { - gba.get_key_state_mut().set_bit(key as usize, false); + key_state.set_bit(key as usize, false); } } -pub fn on_controller_button_up(gba: &mut GameBoyAdvance, button: Button) { +pub fn on_controller_button_up(key_state: &mut u16, button: Button) { if let Some(key) = controller_button_to_keypad(button) { - gba.get_key_state_mut().set_bit(key as usize, true); + key_state.set_bit(key as usize, true); } } -pub fn on_axis_motion(gba: &mut GameBoyAdvance, axis: Axis, val: i16) { +pub fn on_axis_motion(key_state: &mut u16, axis: Axis, val: i16) { use gba_keypad::Keys as GbaKeys; let keys = match axis { Axis::LeftX => (GbaKeys::Left, GbaKeys::Right), @@ -44,7 +43,6 @@ pub fn on_axis_motion(gba: &mut GameBoyAdvance, axis: Axis, val: i16) { } }; - let key_state = gba.get_key_state_mut(); // Axis motion is an absolute value in the range // [-32768, 32767]. Let's simulate a very rough dead // zone to ignore spurious events. diff --git a/platform/rustboyadvance-sdl2/src/main.rs b/platform/rustboyadvance-sdl2/src/main.rs index 6bab3dc..3c7af93 100644 --- a/platform/rustboyadvance-sdl2/src/main.rs +++ b/platform/rustboyadvance-sdl2/src/main.rs @@ -27,7 +27,7 @@ use rustboyadvance_core::prelude::*; use rustboyadvance_utils::FpsCounter; const LOG_DIR: &str = ".logs"; -const DEFAULT_GDB_SERVER_ADDR: &'static str = "localhost:1337"; +const DEFAULT_GDB_SERVER_PORT: u16 = 1337; fn ask_download_bios() { const OPEN_SOURCE_BIOS_URL: &'static str = @@ -109,7 +109,6 @@ fn main() -> Result<(), Box> { let mut event_pump = sdl_context.event_pump()?; 'running: loop { let start_time = time::Instant::now(); - for event in event_pump.poll_iter() { match event { Event::KeyDown { @@ -117,7 +116,7 @@ fn main() -> Result<(), Box> { .. } => match scancode { Scancode::Space => vsync = false, - k => input::on_keyboard_key_down(&mut gba, k), + k => input::on_keyboard_key_down(gba.get_key_state_mut(), k), }, Event::KeyUp { scancode: Some(scancode), @@ -132,8 +131,7 @@ fn main() -> Result<(), Box> { .unwrap(); info!("ending debugger...") } - #[cfg(feature = "gdb")] - Scancode::F2 => todo!("gdb"), + Scancode::F2 => gba.start_gdbserver(DEFAULT_GDB_SERVER_PORT), Scancode::F5 => { info!("Saving state ..."); let save = gba.save_state()?; @@ -155,17 +153,17 @@ fn main() -> Result<(), Box> { } } Scancode::Space => vsync = true, - k => input::on_keyboard_key_up(&mut gba, k), + k => input::on_keyboard_key_up(gba.get_key_state_mut(), k), }, Event::ControllerButtonDown { button, .. } => match button { Button::RightStick => vsync = !vsync, - b => input::on_controller_button_down(&mut gba, b), + b => input::on_controller_button_down(gba.get_key_state_mut(), b), }, Event::ControllerButtonUp { button, .. } => { - input::on_controller_button_up(&mut gba, button); + input::on_controller_button_up(gba.get_key_state_mut(), button); } Event::ControllerAxisMotion { axis, value, .. } => { - input::on_axis_motion(&mut gba, axis, value); + input::on_axis_motion(gba.get_key_state_mut(), axis, value); } Event::ControllerDeviceRemoved { which, .. } => { let removed = if let Some(active_controller) = &active_controller { @@ -196,7 +194,11 @@ fn main() -> Result<(), Box> { } } - gba.frame(); + if gba.is_debugger_attached() { + gba.debugger_run() + } else { + gba.frame(); + } renderer.render(gba.get_frame_buffer()); if let Some(fps) = fps_counter.tick() {