Experimental gdbserver

Former-commit-id: e78618b03c745bb9820216e6d9f8c1f4cade28d5
Former-commit-id: 5851f5930e07d8132e643bbe6773bdd0bd42fad6
This commit is contained in:
Michel Heily 2022-09-17 02:19:46 +03:00
parent 9abc08fffe
commit c47d9e1f11
19 changed files with 774 additions and 77 deletions

112
Cargo.lock generated
View file

@ -438,27 +438,62 @@ dependencies = [
"itertools 0.9.0", "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]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.4.4" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils 0.7.2",
"maybe-uninit", "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]] [[package]]
name = "crossbeam-deque" name = "crossbeam-deque"
version = "0.7.3" version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
dependencies = [ dependencies = [
"crossbeam-epoch", "crossbeam-epoch 0.8.2",
"crossbeam-utils", "crossbeam-utils 0.7.2",
"maybe-uninit", "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]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.8.2" version = "0.8.2"
@ -467,13 +502,37 @@ checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if 0.1.10", "cfg-if 0.1.10",
"crossbeam-utils", "crossbeam-utils 0.7.2",
"lazy_static", "lazy_static",
"maybe-uninit", "maybe-uninit",
"memoffset", "memoffset 0.5.6",
"scopeguard", "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]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.7.2" version = "0.7.2"
@ -485,6 +544,16 @@ dependencies = [
"lazy_static", "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]] [[package]]
name = "csv" name = "csv"
version = "1.1.3" version = "1.1.3"
@ -945,6 +1014,15 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "memory_units" name = "memory_units"
version = "0.4.0" version = "0.4.0"
@ -1116,6 +1194,12 @@ version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" checksum = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22"
[[package]]
name = "once_cell"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
version = "11.1.2" version = "11.1.2"
@ -1243,7 +1327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"crossbeam-deque", "crossbeam-deque 0.7.3",
"either", "either",
"rayon-core", "rayon-core",
] ]
@ -1254,9 +1338,9 @@ version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf" checksum = "e8c4fec834fb6e6d2dd5eece3c7b432a52f0ba887cf40e595190c4107edc08bf"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel 0.4.4",
"crossbeam-deque", "crossbeam-deque 0.7.3",
"crossbeam-utils", "crossbeam-utils 0.7.2",
"lazy_static", "lazy_static",
"num_cpus", "num_cpus",
] ]
@ -1320,7 +1404,7 @@ dependencies = [
"base64", "base64",
"blake2b_simd", "blake2b_simd",
"constant_time_eq", "constant_time_eq",
"crossbeam-utils", "crossbeam-utils 0.7.2",
] ]
[[package]] [[package]]
@ -1342,6 +1426,7 @@ dependencies = [
"chrono", "chrono",
"colored 1.9.3", "colored 1.9.3",
"criterion", "criterion",
"crossbeam",
"debug_stub_derive", "debug_stub_derive",
"enum-primitive-derive", "enum-primitive-derive",
"fuzzy-matcher", "fuzzy-matcher",
@ -1359,6 +1444,7 @@ dependencies = [
"sha2", "sha2",
"smart-default", "smart-default",
"time 0.2.22", "time 0.2.22",
"xml-builder",
"yaml-rust", "yaml-rust",
"zip", "zip",
] ]
@ -2149,6 +2235,12 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "xml-builder"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aff3eebeb8c12d38dd6dcbd07082929a55764d13ad695632c35132ff7cf61ec"
[[package]] [[package]]
name = "yaml-rust" name = "yaml-rust"
version = "0.4.4" version = "0.4.4"

View file

@ -1,3 +1,4 @@
use log::debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use super::exception::Exception; pub use super::exception::Exception;
@ -118,7 +119,7 @@ pub struct Arm7tdmiCore<I: MemoryInterface> {
pub banks: BankedRegisters, pub banks: BankedRegisters,
/// Hardware breakpoints for use by gdb /// Hardware breakpoints for use by gdb
pub breakpoints: Vec<Addr>, breakpoints: Vec<Addr>,
/// Deprecated in-house debugger state /// Deprecated in-house debugger state
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
@ -196,6 +197,28 @@ impl<I: MemoryInterface> Arm7tdmiCore<I> {
self.bus = i; 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<u32> {
let next_pc = self.get_next_pc();
for bp in &self.breakpoints {
if (*bp & !1) == next_pc {
return Some(*bp);
}
}
None
}
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
pub fn set_verbose(&mut self, v: bool) { pub fn set_verbose(&mut self, v: bool) {
self.dbg.verbose = v; self.dbg.verbose = v;

View file

@ -1,6 +1,5 @@
use gdbstub::target; use gdbstub::target;
use gdbstub::target::TargetResult; use gdbstub::target::TargetResult;
use log::debug;
use crate::Arm7tdmiCore; use crate::Arm7tdmiCore;
@ -22,8 +21,7 @@ impl<I: MemoryGdbInterface> target::ext::breakpoints::SwBreakpoint for Arm7tdmiC
addr: u32, addr: u32,
_kind: gdbstub_arch::arm::ArmBreakpointKind, _kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> { ) -> TargetResult<bool, Self> {
debug!("adding breakpoint {:08x}", addr); self.add_breakpoint(addr);
self.breakpoints.push(addr);
Ok(true) Ok(true)
} }
@ -32,25 +30,7 @@ impl<I: MemoryGdbInterface> target::ext::breakpoints::SwBreakpoint for Arm7tdmiC
addr: u32, addr: u32,
_kind: gdbstub_arch::arm::ArmBreakpointKind, _kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> { ) -> TargetResult<bool, Self> {
match self.breakpoints.iter().position(|x| *x == addr) { self.del_breakpoint(addr);
None => Ok(false), Ok(true)
Some(pos) => {
debug!("deleting breakpoint {:08x}", addr);
self.breakpoints.remove(pos);
Ok(true)
}
}
}
}
impl<I: MemoryGdbInterface> Arm7tdmiCore<I> {
pub fn check_breakpoint(&self) -> Option<u32> {
let next_pc = self.get_next_pc();
for bp in &self.breakpoints {
if (*bp & !1) == next_pc {
return Some(*bp);
}
}
None
} }
} }

View file

@ -41,7 +41,7 @@ impl<I: MemoryGdbInterface> SingleThreadBase for Arm7tdmiCore<I> {
&mut self, &mut self,
regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs,
) -> TargetResult<(), Self> { ) -> TargetResult<(), Self> {
regs.pc = self.get_reg(REG_PC); regs.pc = self.get_next_pc();
regs.lr = self.get_reg(REG_LR); regs.lr = self.get_reg(REG_LR);
regs.sp = self.get_reg(REG_SP); regs.sp = self.get_reg(REG_SP);
regs.r[..].copy_from_slice(&self.gpr[..13]); regs.r[..].copy_from_slice(&self.gpr[..13]);

View file

@ -24,6 +24,7 @@ use memory::Addr;
pub mod disass; pub mod disass;
pub mod exception; pub mod exception;
pub mod gdb; pub mod gdb;
pub use gdb::{gdbstub, gdbstub_arch};
pub mod psr; pub mod psr;
mod simple_memory; mod simple_memory;
pub use simple_memory::SimpleMemory; pub use simple_memory::SimpleMemory;

View file

@ -5,8 +5,8 @@ authors = ["Michel Heily <michelheily@gmail.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
arm7tdmi = { "path" = "../arm7tdmi" } arm7tdmi = { path = '../arm7tdmi' }
rustboyadvance-utils = { "path" = "../utils" } rustboyadvance-utils = { path = "../utils" }
cfg-if = "1.0.0" cfg-if = "1.0.0"
serde = { version = "1.0.104", features = ["derive", "rc"] } serde = { version = "1.0.104", features = ["derive", "rc"] }
bincode = "1.2.1" bincode = "1.2.1"
@ -41,6 +41,8 @@ bit_reverse = "0.1.8"
yaml-rust = "0.4" yaml-rust = "0.4"
lazy_static = "1.4.0" lazy_static = "1.4.0"
smart-default = "0.6.0" smart-default = "0.6.0"
crossbeam = "0.8.2"
xml-builder = "0.5.0"
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"

View file

@ -34,6 +34,11 @@ impl Bios {
fn read_allowed(&self) -> bool { fn read_allowed(&self) -> bool {
self.arm_core.pc < 0x4000 self.arm_core.pc < 0x4000
} }
#[inline]
pub(crate) fn len(&self) -> usize {
self.rom.len()
}
} }
/// Impl of Bus trait for Bios /// Impl of Bus trait for Bios

View file

@ -11,6 +11,9 @@ use rustboyadvance_utils::elf::{load_elf, GoblinError};
use rustboyadvance_utils::read_bin_file; use rustboyadvance_utils::read_bin_file;
use zip::ZipArchive; use zip::ZipArchive;
#[cfg(feature = "elf_support")]
use crate::sysbus::consts::CART_BASE;
pub enum LoadRom { pub enum LoadRom {
#[cfg(feature = "elf_support")] #[cfg(feature = "elf_support")]
Elf { Elf {
@ -30,8 +33,7 @@ impl From<GoblinError> for GBAError {
#[cfg(feature = "elf_support")] #[cfg(feature = "elf_support")]
pub(super) fn try_load_elf(elf_bytes: &[u8]) -> LoadRomResult { pub(super) fn try_load_elf(elf_bytes: &[u8]) -> LoadRomResult {
const CART_BASE: usize = 0x0800_0000; let elf = load_elf(elf_bytes, CART_BASE as usize)?;
let elf = load_elf(elf_bytes, CART_BASE)?;
Ok(LoadRom::Elf { Ok(LoadRom::Elf {
data: elf.data, data: elf.data,
symbols: elf.symbols, symbols: elf.symbols,

View file

@ -2,9 +2,12 @@
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc; use std::rc::Rc;
use arm7tdmi::gdbstub::stub::SingleThreadStopReason;
use bincode; use bincode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::gdb_support::{gdb_thread::start_gdb_server_thread, DebuggerState};
use super::cartridge::Cartridge; use super::cartridge::Cartridge;
use super::dma::DmaController; use super::dma::DmaController;
use super::gpu::*; use super::gpu::*;
@ -21,12 +24,13 @@ use arm7tdmi::{self, Arm7tdmiCore};
use rustboyadvance_utils::Shared; use rustboyadvance_utils::Shared;
pub struct GameBoyAdvance { pub struct GameBoyAdvance {
pub cpu: Box<Arm7tdmiCore<SysBus>>, pub(crate) cpu: Box<Arm7tdmiCore<SysBus>>,
pub sysbus: Shared<SysBus>, pub(crate) sysbus: Shared<SysBus>,
pub io_devs: Shared<IoDevices>, pub(crate) io_devs: Shared<IoDevices>,
pub scheduler: SharedScheduler, pub(crate) scheduler: SharedScheduler,
interrupt_flags: SharedInterruptFlags, interrupt_flags: SharedInterruptFlags,
pub audio_interface: DynAudioInterface, audio_interface: DynAudioInterface,
pub(crate) debugger: Option<DebuggerState>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -106,6 +110,7 @@ impl GameBoyAdvance {
audio_interface, audio_interface,
scheduler, scheduler,
interrupt_flags, interrupt_flags,
debugger: None,
}; };
gba.sysbus.init(gba.cpu.weak_ptr()); gba.sysbus.init(gba.cpu.weak_ptr());
@ -150,6 +155,7 @@ impl GameBoyAdvance {
interrupt_flags: interrupts, interrupt_flags: interrupts,
audio_interface, audio_interface,
scheduler, scheduler,
debugger: None,
}) })
} }
@ -206,10 +212,62 @@ impl GameBoyAdvance {
&mut self.sysbus.io.keyinput &mut self.sysbus.io.keyinput
} }
/// Advance the emulation for one frame worth of time
pub fn frame(&mut self) { pub fn frame(&mut self) {
static mut OVERSHOOT: usize = 0; static mut OVERSHOOT: usize = 0;
unsafe { unsafe {
OVERSHOOT = self.run(CYCLES_FULL_REFRESH - OVERSHOOT); OVERSHOOT = CYCLES_FULL_REFRESH - self.run::<false>(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::<true>(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 /// 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] #[inline]
fn run(&mut self, cycles_to_run: usize) -> usize { pub(super) fn run<const CHECK_BREAKPOINTS: bool>(&mut self, cycles_to_run: usize) -> usize {
let run_start_time = self.scheduler.timestamp(); let run_start_time = self.scheduler.timestamp();
// Register an event to mark the end of this run // 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, // 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. // 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 // 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: // 3 Options:
// 1. DMA is active - thus CPU is blocked // 1. DMA is active - thus CPU is blocked
// 2. DMA inactive and halt state is RUN - CPU can run // 2. DMA inactive and halt state is RUN - CPU can run
// 3. DMA inactive and halt state is HALT - CPU is blocked // 3. DMA inactive and halt state is HALT - CPU is blocked
match self.get_bus_master() { match self.get_bus_master() {
Some(BusMaster::Dma) => self.dma_step(), 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 => { None => {
if self.io_devs.intc.irq_pending() { if self.io_devs.intc.irq_pending() {
self.io_devs.haltcnt = HaltState::Running; self.io_devs.haltcnt = HaltState::Running;
@ -273,8 +348,7 @@ impl GameBoyAdvance {
self.handle_events(&mut running); self.handle_events(&mut running);
} }
let total_cycles_ran = self.scheduler.timestamp() - run_start_time; self.scheduler.timestamp() - run_start_time
total_cycles_ran - cycles_to_run
} }
fn handle_events(&mut self, run_limit_flag: &mut bool) { fn handle_events(&mut self, run_limit_flag: &mut bool) {

168
core/src/gdb_support.rs Normal file
View file

@ -0,0 +1,168 @@
use std::result;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::JoinHandle;
type SendSync<T> = Arc<Mutex<T>>;
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<ArmCoreRegs>),
WriteRegs(ArmCoreRegs),
ReadAddrs(Addr, SendSync<Box<[u8]>>),
#[allow(unused)]
WriteAddrs(Addr, Box<[u8]>),
AddSwBreakpoint(Addr),
DelSwBreakpoint(Addr),
Stop,
Resume,
SingleStep,
Disconnected(DisconnectReason),
}
pub struct DebuggerTarget {
tx: Sender<DebuggerMessage>,
operation_signal: Arc<(Mutex<bool>, Condvar)>,
stop_signal: Arc<(Mutex<SingleThreadStopReason<u32>>, 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<DebuggerMessage>,
operation_signal: Arc<(Mutex<bool>, Condvar)>,
stop_signal: Arc<(Mutex<SingleThreadStopReason<u32>>, Condvar)>,
thread: JoinHandle<()>,
stopped: bool,
}
impl DebuggerState {
pub fn handle_message(
mut self,
gba: &mut GameBoyAdvance,
should_stop: &mut bool,
) -> Result<Option<DebuggerState>, TargetError<<DebuggerTarget as Target>::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(&regs)?;
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::<true>(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<u32>) {
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(()));
}
}

View file

@ -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<dyn ConnectionExt<Error = std::io::Error>>;
type StopReason = SingleThreadStopReason<u32>;
fn wait_for_stop_reason(
target: &mut Self::Target,
conn: &mut Self::Connection,
) -> Result<
run_blocking::Event<SingleThreadStopReason<u32>>,
run_blocking::WaitForStopReasonError<
<Self::Target as Target>::Error,
<Self::Connection as Connection>::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<Option<SingleThreadStopReason<u32>>, <DebuggerTarget as Target>::Error> {
info!("on_interrupt: sending stop message");
target.tx.send(DebuggerMessage::Stop).unwrap();
target.wait_for_operation();
info!("Waiting for target to stop <blocking>");
let (lock, cvar) = &*target.stop_signal;
let stop_signal = lock.lock().unwrap();
let stop_signal = cvar.wait(stop_signal).unwrap();
Ok(Some(*stop_signal))
}
}

View file

@ -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<DebuggerState, GBAError> {
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<dyn ConnectionExt<Error = std::io::Error>> = 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::<DebuggerEventLoop>(&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)
}

View file

@ -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<String, Box<dyn std::error::Error>> {
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,
)
}
}

View file

@ -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<Self::Arch, Self::Error> {
BaseOps::SingleThread(self)
}
// opt-in to support for setting/removing breakpoints
#[inline(always)]
fn support_breakpoints(&mut self) -> Option<BreakpointsOps<Self>> {
Some(self)
}
fn support_memory_map(&mut self) -> Option<target::ext::memory_map::MemoryMapOps<Self>> {
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<SingleThreadResumeOps<Self>> {
Some(self)
}
}
impl SingleThreadResume for DebuggerTarget {
fn resume(&mut self, _signal: Option<Signal>) -> 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<SingleThreadSingleStepOps<'_, Self>> {
Some(self)
}
}
impl SingleThreadSingleStep for DebuggerTarget {
fn step(&mut self, _signal: Option<Signal>) -> 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<usize, Self> {
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<target::ext::breakpoints::SwBreakpointOps<'_, Self>> {
Some(self)
}
}
impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget {
fn add_sw_breakpoint(
&mut self,
addr: u32,
_kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> {
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<bool, Self> {
self.tx
.send(DebuggerMessage::DelSwBreakpoint(addr))
.unwrap();
self.wait_for_operation();
Ok(true)
}
}

View file

@ -145,7 +145,7 @@ impl BusIO for IoDevices {
REG_POSTFLG => io.post_boot_flag as u16, REG_POSTFLG => io.post_boot_flag as u16,
REG_HALTCNT => 0, 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), x if DebugPort::is_debug_access(x) => io.debug.read(io_addr),

View file

@ -45,11 +45,14 @@ pub use interrupt::SharedInterruptFlags;
pub mod gba; pub mod gba;
pub use gba::GameBoyAdvance; pub use gba::GameBoyAdvance;
pub mod dma; pub mod dma;
pub mod gdb_support;
pub mod keypad; pub mod keypad;
mod mgba_debug; mod mgba_debug;
pub(crate) mod overrides; pub(crate) mod overrides;
pub mod timer; pub mod timer;
use arm7tdmi::gdb::gdbstub::stub::GdbStubError;
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
pub mod debugger; pub mod debugger;
@ -59,6 +62,7 @@ pub enum GBAError {
CartridgeLoadError(String), CartridgeLoadError(String),
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]
DebuggerError(debugger::DebuggerError), DebuggerError(debugger::DebuggerError),
GdbError(String),
} }
impl fmt::Display for GBAError { impl fmt::Display for GBAError {
@ -94,6 +98,12 @@ impl From<zip::result::ZipError> for GBAError {
} }
} }
impl From<GdbStubError<(), std::io::Error>> for GBAError {
fn from(err: GdbStubError<(), std::io::Error>) -> Self {
GBAError::GdbError(err.to_string())
}
}
pub mod prelude { pub mod prelude {
pub use super::cartridge::{Cartridge, GamepakBuilder}; pub use super::cartridge::{Cartridge, GamepakBuilder};
#[cfg(feature = "debugger")] #[cfg(feature = "debugger")]

View file

@ -22,7 +22,8 @@ pub mod consts {
pub const PALRAM_ADDR: u32 = 0x0500_0000; pub const PALRAM_ADDR: u32 = 0x0500_0000;
pub const VRAM_ADDR: u32 = 0x0600_0000; pub const VRAM_ADDR: u32 = 0x0600_0000;
pub const OAM_ADDR: u32 = 0x0700_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_WS0_HI: u32 = 0x0900_0000;
pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000; pub const GAMEPAK_WS1_LO: u32 = 0x0A00_0000;
pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000; pub const GAMEPAK_WS1_HI: u32 = 0x0B00_0000;
@ -148,9 +149,9 @@ pub struct SysBus {
scheduler: Shared<Scheduler>, scheduler: Shared<Scheduler>,
arm_core: WeakPointer<Arm7tdmiCore<SysBus>>, arm_core: WeakPointer<Arm7tdmiCore<SysBus>>,
bios: Bios, pub(crate) bios: Bios,
ewram: Box<[u8]>, pub(crate) ewram: Box<[u8]>,
iwram: Box<[u8]>, pub(crate) iwram: Box<[u8]>,
pub cartridge: Cartridge, pub cartridge: Cartridge,
cycle_luts: CycleLookupTables, cycle_luts: CycleLookupTables,

View file

@ -1,4 +1,3 @@
use rustboyadvance_core::GameBoyAdvance;
use sdl2::controller::Axis; use sdl2::controller::Axis;
use sdl2::controller::Button; use sdl2::controller::Button;
use sdl2::keyboard::Scancode; use sdl2::keyboard::Scancode;
@ -8,31 +7,31 @@ use rustboyadvance_core::keypad as gba_keypad;
use bit; use bit;
use bit::BitIndex; 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) { 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) { 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) { 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) { 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; use gba_keypad::Keys as GbaKeys;
let keys = match axis { let keys = match axis {
Axis::LeftX => (GbaKeys::Left, GbaKeys::Right), 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 // Axis motion is an absolute value in the range
// [-32768, 32767]. Let's simulate a very rough dead // [-32768, 32767]. Let's simulate a very rough dead
// zone to ignore spurious events. // zone to ignore spurious events.

View file

@ -27,7 +27,7 @@ use rustboyadvance_core::prelude::*;
use rustboyadvance_utils::FpsCounter; use rustboyadvance_utils::FpsCounter;
const LOG_DIR: &str = ".logs"; 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() { fn ask_download_bios() {
const OPEN_SOURCE_BIOS_URL: &'static str = const OPEN_SOURCE_BIOS_URL: &'static str =
@ -109,7 +109,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut event_pump = sdl_context.event_pump()?; let mut event_pump = sdl_context.event_pump()?;
'running: loop { 'running: loop {
let start_time = time::Instant::now(); let start_time = time::Instant::now();
for event in event_pump.poll_iter() { for event in event_pump.poll_iter() {
match event { match event {
Event::KeyDown { Event::KeyDown {
@ -117,7 +116,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.. ..
} => match scancode { } => match scancode {
Scancode::Space => vsync = false, 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 { Event::KeyUp {
scancode: Some(scancode), scancode: Some(scancode),
@ -132,8 +131,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.unwrap(); .unwrap();
info!("ending debugger...") info!("ending debugger...")
} }
#[cfg(feature = "gdb")] Scancode::F2 => gba.start_gdbserver(DEFAULT_GDB_SERVER_PORT),
Scancode::F2 => todo!("gdb"),
Scancode::F5 => { Scancode::F5 => {
info!("Saving state ..."); info!("Saving state ...");
let save = gba.save_state()?; let save = gba.save_state()?;
@ -155,17 +153,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
Scancode::Space => vsync = true, 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 { Event::ControllerButtonDown { button, .. } => match button {
Button::RightStick => vsync = !vsync, 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, .. } => { 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, .. } => { 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, .. } => { Event::ControllerDeviceRemoved { which, .. } => {
let removed = if let Some(active_controller) = &active_controller { let removed = if let Some(active_controller) = &active_controller {
@ -196,7 +194,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
gba.frame(); if gba.is_debugger_attached() {
gba.debugger_run()
} else {
gba.frame();
}
renderer.render(gba.get_frame_buffer()); renderer.render(gba.get_frame_buffer());
if let Some(fps) = fps_counter.tick() { if let Some(fps) = fps_counter.tick() {