feat: Add basic gdbserver feature
Initially I began implementing the gdb protocol on my own, but I then found a nice work-in-progress crate on https://github.com/daniel5151/gdbstub that suited my needs. Former-commit-id: f77557cbbd8652c2ed05ac439efc1956d8e99729
This commit is contained in:
parent
96cacb5f4c
commit
c50ab1ecd8
|
@ -37,6 +37,8 @@ arrayvec = "0.5.1"
|
|||
|
||||
rustyline = {version = "6.0.0", optional = true}
|
||||
nom = {version = "5.0.0", optional = true}
|
||||
gdbstub = {git = "https://github.com/daniel5151/gdbstub.git", optional = true, features = ["std"], rev = "9686df6d74c4bf45cbc5746273b82640e8852b6d"}
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "rba-sdl2"
|
||||
|
@ -51,6 +53,7 @@ path = "src/plat/minifb/main.rs"
|
|||
|
||||
[features]
|
||||
debugger = ["nom", "rustyline"]
|
||||
gdb = ["gdbstub"]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
|
|
@ -84,6 +84,8 @@ Special key bindings
|
|||
| Key | Function |
|
||||
|-------------- |-------------------- |
|
||||
| Space (hold) | Disable 60fps cap |
|
||||
| F1 | Custom debugger (requires --features debugger) |
|
||||
| F2 | Spawn gdbserver (experimetnal, requires --features gdb) |
|
||||
| F5 | Save snapshot file |
|
||||
| F9 | Load snapshot file |
|
||||
|
||||
|
|
|
@ -8,9 +8,10 @@ use std::fmt;
|
|||
|
||||
pub use super::exception::Exception;
|
||||
use super::CpuAction;
|
||||
#[cfg(feature = "debugger")]
|
||||
use super::DecodedInstruction;
|
||||
use super::{
|
||||
arm::*, psr::RegPSR, thumb::ThumbInstruction, Addr, CpuMode, CpuState, DecodedInstruction,
|
||||
InstructionDecoder,
|
||||
arm::*, psr::RegPSR, thumb::ThumbInstruction, Addr, CpuMode, CpuState, InstructionDecoder,
|
||||
};
|
||||
|
||||
use crate::core::bus::Bus;
|
||||
|
@ -34,6 +35,8 @@ pub struct Core {
|
|||
pub(super) bs_carry_out: bool,
|
||||
|
||||
pipeline: [u32; 2],
|
||||
|
||||
#[cfg(feature = "debugger")]
|
||||
pub last_executed: Option<DecodedInstruction>,
|
||||
|
||||
pub cycles: usize,
|
||||
|
@ -296,8 +299,8 @@ impl Core {
|
|||
#[cfg(feature = "debugger")]
|
||||
{
|
||||
self.gpr_previous = self.get_registers();
|
||||
}
|
||||
self.last_executed = Some(DecodedInstruction::Arm(decoded_arm));
|
||||
}
|
||||
let result = self.exec_arm(sb, decoded_arm);
|
||||
match result {
|
||||
CpuAction::AdvancePC => self.advance_arm(),
|
||||
|
@ -310,8 +313,8 @@ impl Core {
|
|||
#[cfg(feature = "debugger")]
|
||||
{
|
||||
self.gpr_previous = self.get_registers();
|
||||
}
|
||||
self.last_executed = Some(DecodedInstruction::Thumb(decoded_thumb));
|
||||
}
|
||||
let result = self.exec_thumb(sb, decoded_thumb);
|
||||
match result {
|
||||
CpuAction::AdvancePC => self.advance_thumb(),
|
||||
|
@ -320,7 +323,7 @@ impl Core {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn reload_pipeline16(&mut self, sb: &mut SysBus) {
|
||||
pub fn reload_pipeline16(&mut self, sb: &mut SysBus) {
|
||||
self.pipeline[0] = sb.read_16(self.pc) as u32;
|
||||
self.N_cycle16(sb, self.pc);
|
||||
self.advance_thumb();
|
||||
|
@ -330,7 +333,7 @@ impl Core {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn reload_pipeline32(&mut self, sb: &mut SysBus) {
|
||||
pub fn reload_pipeline32(&mut self, sb: &mut SysBus) {
|
||||
self.pipeline[0] = sb.read_32(self.pc);
|
||||
self.N_cycle16(sb, self.pc);
|
||||
self.advance_arm();
|
||||
|
|
|
@ -19,11 +19,11 @@ pub struct GameBoyAdvance {
|
|||
pub sysbus: Box<SysBus>,
|
||||
pub cpu: arm7tdmi::Core,
|
||||
|
||||
video_device: Rc<RefCell<dyn VideoInterface>>,
|
||||
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||
input_device: Rc<RefCell<dyn InputInterface>>,
|
||||
pub video_device: Rc<RefCell<dyn VideoInterface>>,
|
||||
pub audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||
pub input_device: Rc<RefCell<dyn InputInterface>>,
|
||||
|
||||
cycles_to_next_event: usize,
|
||||
pub cycles_to_next_event: usize,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
@ -120,8 +120,8 @@ impl GameBoyAdvance {
|
|||
self.sysbus.io.gpu.skip_bios();
|
||||
}
|
||||
|
||||
fn step_cpu(&mut self, io: &mut IoDevices) -> usize {
|
||||
if io.intc.irq_pending() && self.cpu.last_executed.is_some() {
|
||||
pub fn step_cpu(&mut self, io: &mut IoDevices) -> usize {
|
||||
if io.intc.irq_pending() {
|
||||
self.cpu.irq(&mut self.sysbus);
|
||||
io.haltcnt = HaltState::Running;
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ impl GameBoyAdvance {
|
|||
}
|
||||
|
||||
pub fn step(&mut self) {
|
||||
// // I hate myself for doing this, but rust left me no choice.
|
||||
// I hate myself for doing this, but rust left me no choice.
|
||||
let io = unsafe {
|
||||
let ptr = &mut *self.sysbus as *mut SysBus;
|
||||
&mut (*ptr).io as &mut IoDevices
|
||||
|
|
142
src/gdb.rs
Normal file
142
src/gdb.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use super::core::arm7tdmi::CpuState;
|
||||
use super::core::interrupt::*;
|
||||
use super::core::iodev::IoDevices;
|
||||
use super::core::sysbus::SysBus;
|
||||
use super::core::Bus;
|
||||
use super::core::GameBoyAdvance;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use gdbstub::{Access, GdbStub, Target, TargetState};
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::net::{TcpListener, TcpStream, ToSocketAddrs};
|
||||
|
||||
pub type GdbServer = GdbStub<GameBoyAdvance, TcpStream>;
|
||||
|
||||
pub fn spawn_gdb_server<A: ToSocketAddrs + fmt::Display>(
|
||||
addr: A,
|
||||
) -> Result<GdbServer, Box<dyn std::error::Error>> {
|
||||
info!("spawning gdbserver, listening on {}", addr);
|
||||
|
||||
let sock = TcpListener::bind(addr)?;
|
||||
let (stream, addr) = sock.accept()?;
|
||||
info!("got connection from {}", addr);
|
||||
|
||||
Ok(GdbServer::new(stream))
|
||||
}
|
||||
|
||||
impl Target for GameBoyAdvance {
|
||||
type Usize = u32;
|
||||
type Error = ();
|
||||
|
||||
fn step(
|
||||
&mut self,
|
||||
mut _log_mem_access: impl FnMut(Access<u32>),
|
||||
) -> Result<TargetState, Self::Error> {
|
||||
static mut S_TOTAL_CYCLES: usize = 0;
|
||||
|
||||
let io = unsafe {
|
||||
let ptr = &mut *self.sysbus as *mut SysBus;
|
||||
&mut (*ptr).io as &mut IoDevices
|
||||
};
|
||||
|
||||
// clear any pending DMAs
|
||||
let mut irqs = IrqBitmask(0);
|
||||
while io.dmac.is_active() {
|
||||
io.dmac.perform_work(&mut self.sysbus, &mut irqs);
|
||||
}
|
||||
io.intc.request_irqs(irqs);
|
||||
|
||||
// run the CPU, ignore haltcnt
|
||||
let cycles = self.step_cpu(io);
|
||||
io.timers.update(cycles, &mut self.sysbus, &mut irqs);
|
||||
|
||||
unsafe {
|
||||
S_TOTAL_CYCLES += cycles;
|
||||
}
|
||||
|
||||
if self.cycles_to_next_event <= unsafe { S_TOTAL_CYCLES } {
|
||||
let mut cycles_to_next_event = std::usize::MAX;
|
||||
io.gpu.step(
|
||||
unsafe { S_TOTAL_CYCLES },
|
||||
&mut self.sysbus,
|
||||
&mut irqs,
|
||||
&mut cycles_to_next_event,
|
||||
&self.video_device,
|
||||
);
|
||||
io.sound.update(
|
||||
unsafe { S_TOTAL_CYCLES },
|
||||
&mut cycles_to_next_event,
|
||||
&self.audio_device,
|
||||
);
|
||||
self.cycles_to_next_event = cycles_to_next_event;
|
||||
|
||||
unsafe {
|
||||
S_TOTAL_CYCLES = 0;
|
||||
};
|
||||
} else {
|
||||
self.cycles_to_next_event -= unsafe { S_TOTAL_CYCLES };
|
||||
}
|
||||
|
||||
io.intc.request_irqs(irqs);
|
||||
|
||||
Ok(TargetState::Running)
|
||||
}
|
||||
|
||||
fn read_pc(&mut self) -> u32 {
|
||||
self.cpu.get_next_pc()
|
||||
}
|
||||
|
||||
// read the specified memory addresses from the target
|
||||
fn read_addrs(&mut self, addr: std::ops::Range<u32>, mut push_byte: impl FnMut(u8)) {
|
||||
for addr in addr {
|
||||
push_byte(self.sysbus.read_8(addr))
|
||||
}
|
||||
}
|
||||
|
||||
// write data to the specified memory addresses
|
||||
fn write_addrs(&mut self, mut get_addr_val: impl FnMut() -> Option<(u32, u8)>) {
|
||||
while let Some((addr, val)) = get_addr_val() {
|
||||
self.sysbus.write_8(addr, val);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_registers(&mut self, mut push_reg: impl FnMut(&[u8])) {
|
||||
// general purpose registers
|
||||
for i in 0..15 {
|
||||
push_reg(&self.cpu.get_reg(i).to_le_bytes());
|
||||
}
|
||||
push_reg(&self.cpu.get_next_pc().to_le_bytes());
|
||||
// Floating point registers, unused
|
||||
for _ in 0..25 {
|
||||
push_reg(&[0, 0, 0, 0]);
|
||||
}
|
||||
push_reg(&self.cpu.cpsr.get().to_le_bytes());
|
||||
}
|
||||
|
||||
fn write_registers(&mut self, regs: &[u8]) {
|
||||
let mut rdr = Cursor::new(regs);
|
||||
for i in 0..15 {
|
||||
self.cpu.set_reg(i, rdr.read_u32::<LittleEndian>().unwrap());
|
||||
}
|
||||
let new_pc = rdr.read_u32::<LittleEndian>().unwrap();
|
||||
self.cpu.set_reg(15, new_pc);
|
||||
|
||||
self.cpu.cpsr.set(rdr.read_u32::<LittleEndian>().unwrap());
|
||||
|
||||
match self.cpu.cpsr.state() {
|
||||
CpuState::ARM => self.cpu.reload_pipeline32(&mut self.sysbus),
|
||||
CpuState::THUMB => self.cpu.reload_pipeline16(&mut self.sysbus),
|
||||
};
|
||||
}
|
||||
|
||||
fn target_description_xml() -> Option<&'static str> {
|
||||
Some(
|
||||
r#"
|
||||
<target version="1.0">
|
||||
<architecture>armv4t</architecture>
|
||||
</target>"#,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -25,6 +25,9 @@ pub mod util;
|
|||
pub mod core;
|
||||
pub mod disass;
|
||||
|
||||
#[cfg(feature = "gdb")]
|
||||
pub mod gdb;
|
||||
|
||||
#[cfg(feature = "debugger")]
|
||||
pub mod debugger;
|
||||
|
||||
|
@ -56,6 +59,7 @@ pub trait InputInterface {
|
|||
pub mod prelude {
|
||||
pub use super::core::arm7tdmi;
|
||||
pub use super::core::cartridge::GamepakBuilder;
|
||||
pub use super::core::Bus;
|
||||
pub use super::core::{GBAError, GBAResult, GameBoyAdvance};
|
||||
#[cfg(feature = "debugger")]
|
||||
pub use super::debugger::Debugger;
|
||||
|
|
|
@ -29,7 +29,12 @@ args:
|
|||
help: Skip running bios and start from the ROM instead
|
||||
- debug:
|
||||
long: debug
|
||||
help: Start with the debugger attached
|
||||
help: Use the custom debugger
|
||||
- with_gdbserver:
|
||||
long: with-gdbserver
|
||||
help: Start with experimental gdbserver
|
||||
conflicts_with:
|
||||
- debug
|
||||
- script_file:
|
||||
long: script-file
|
||||
short: f
|
||||
|
|
|
@ -27,6 +27,9 @@ extern crate log;
|
|||
use flexi_logger;
|
||||
use flexi_logger::*;
|
||||
|
||||
#[cfg(feature = "gdb")]
|
||||
use gdbstub;
|
||||
|
||||
mod audio;
|
||||
mod input;
|
||||
mod video;
|
||||
|
@ -36,6 +39,8 @@ use input::create_input;
|
|||
use video::{create_video_interface, SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||
|
||||
use rustboyadvance_ng::core::cartridge::BackupType;
|
||||
#[cfg(feature = "gdb")]
|
||||
use rustboyadvance_ng::gdb::spawn_gdb_server;
|
||||
use rustboyadvance_ng::prelude::*;
|
||||
use rustboyadvance_ng::util::FpsCounter;
|
||||
|
||||
|
@ -60,6 +65,29 @@ fn wait_for_rom(event_pump: &mut EventPump) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn spawn_and_run_gdb_server(gba: &mut GameBoyAdvance) -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "gdb")]
|
||||
{
|
||||
let mut gdb = spawn_gdb_server(format!("localhost:{}", 1337))?;
|
||||
let result = match gdb.run(gba) {
|
||||
Ok(state) => {
|
||||
info!("Disconnected from GDB. Target state: {:?}", state);
|
||||
Ok(())
|
||||
}
|
||||
Err(gdbstub::Error::TargetError(e)) => Err(e),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
|
||||
info!("Debugger session ended, result={:?}", result);
|
||||
}
|
||||
#[cfg(not(feature = "gdb"))]
|
||||
{
|
||||
error!("Please compile me with 'gdb' feature")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fs::create_dir_all(LOG_DIR).expect(&format!("could not create log directory ({})", LOG_DIR));
|
||||
flexi_logger::Logger::with_env_or_str("info")
|
||||
|
@ -78,6 +106,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
let skip_bios = matches.occurrences_of("skip_bios") != 0;
|
||||
|
||||
let debug = matches.occurrences_of("debug") != 0;
|
||||
let with_gdbserver = matches.occurrences_of("with_gdbserver") != 0;
|
||||
|
||||
info!("Initializing SDL2 context");
|
||||
let sdl_context = sdl2::init().expect("failed to initialize sdl2");
|
||||
|
@ -161,6 +190,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
}
|
||||
}
|
||||
|
||||
if with_gdbserver {
|
||||
spawn_and_run_gdb_server(&mut gba)?;
|
||||
}
|
||||
|
||||
let mut fps_counter = FpsCounter::default();
|
||||
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
|
||||
'running: loop {
|
||||
|
@ -192,6 +225,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
info!("ending debugger...");
|
||||
break;
|
||||
}
|
||||
#[cfg(feature = "gdb")]
|
||||
Event::KeyUp {
|
||||
keycode: Some(Keycode::F2),
|
||||
..
|
||||
} => {
|
||||
spawn_and_run_gdb_server(&mut gba)?;
|
||||
}
|
||||
Event::KeyUp {
|
||||
keycode: Some(Keycode::F5),
|
||||
..
|
||||
|
|
Reference in a new issue