Gdb fixes
Former-commit-id: f8507472a94386902962d1ffead7dd6f85624d82 Former-commit-id: 15446ef199288e887b7bc02fec08f9798c4c6ea2
This commit is contained in:
parent
c8c1cdd57b
commit
838ca43ac4
|
@ -95,6 +95,7 @@ impl fmt::Display for CpuState {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Primitive, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Primitive, Copy, Clone, PartialEq, Eq)]
|
||||||
|
#[repr(u8)]
|
||||||
pub enum CpuMode {
|
pub enum CpuMode {
|
||||||
User = 0b10000,
|
User = 0b10000,
|
||||||
Fiq = 0b10001,
|
Fiq = 0b10001,
|
||||||
|
|
124
core/src/gba.rs
124
core/src/gba.rs
|
@ -2,7 +2,6 @@
|
||||||
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};
|
||||||
|
|
||||||
|
@ -251,20 +250,10 @@ impl GameBoyAdvance {
|
||||||
|
|
||||||
/// Recv & handle messages from the debugger, and return if we are stopped or not
|
/// Recv & handle messages from the debugger, and return if we are stopped or not
|
||||||
pub fn debugger_run(&mut self) {
|
pub fn debugger_run(&mut self) {
|
||||||
let mut should_interrupt_frame = false;
|
|
||||||
let debugger = self.debugger.take().expect("debugger should be None here");
|
let debugger = self.debugger.take().expect("debugger should be None here");
|
||||||
self.debugger = debugger.handle_incoming_requests(self, &mut should_interrupt_frame);
|
self.debugger = debugger.handle_incoming_requests(self);
|
||||||
|
|
||||||
if let Some(debugger) = &mut self.debugger {
|
|
||||||
if should_interrupt_frame {
|
|
||||||
debugger.notify_stop_reason(SingleThreadStopReason::DoneStep);
|
|
||||||
} else {
|
|
||||||
self.frame_interruptible();
|
self.frame_interruptible();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
error!("debugger was disconnected!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn dma_step(&mut self) {
|
fn dma_step(&mut self) {
|
||||||
|
@ -272,10 +261,15 @@ impl GameBoyAdvance {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn cpu_step(&mut self) {
|
fn cpu_interrupt(&mut self) {
|
||||||
if self.io_devs.intc.irq_pending() {
|
|
||||||
self.cpu.irq();
|
self.cpu.irq();
|
||||||
self.io_devs.haltcnt = HaltState::Running;
|
self.io_devs.haltcnt = HaltState::Running; // Clear out from low power mode
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn cpu_step(&mut self) {
|
||||||
|
if self.io_devs.intc.irq_pending() {
|
||||||
|
self.cpu_interrupt();
|
||||||
}
|
}
|
||||||
self.cpu.step();
|
self.cpu.step();
|
||||||
}
|
}
|
||||||
|
@ -285,7 +279,29 @@ impl GameBoyAdvance {
|
||||||
match (self.io_devs.dmac.is_active(), self.io_devs.haltcnt) {
|
match (self.io_devs.dmac.is_active(), self.io_devs.haltcnt) {
|
||||||
(true, _) => Some(BusMaster::Dma),
|
(true, _) => Some(BusMaster::Dma),
|
||||||
(false, HaltState::Running) => Some(BusMaster::Cpu),
|
(false, HaltState::Running) => Some(BusMaster::Cpu),
|
||||||
(false, _) => None,
|
(false, HaltState::Halt) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn single_step(&mut self) {
|
||||||
|
// 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(),
|
||||||
|
None => {
|
||||||
|
// Halt mode - system is in a low-power mode, only (IE and IF) can release CPU from this state.
|
||||||
|
if self.io_devs.intc.irq_pending() {
|
||||||
|
self.cpu_interrupt();
|
||||||
|
} else {
|
||||||
|
// Fast-forward to next pending HW event so we don't waste time idle-looping when we know the only way
|
||||||
|
// To get out of Halt mode is through an interrupt.
|
||||||
|
self.scheduler.fast_forward_to_next();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,60 +309,45 @@ impl GameBoyAdvance {
|
||||||
/// @return number of cycle actually ran
|
/// @return number of cycle actually ran
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn run<const CHECK_BREAKPOINTS: bool>(&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 start_time = self.scheduler.timestamp();
|
||||||
|
let end_time = start_time + cycles_to_run;
|
||||||
|
|
||||||
// Register an event to mark the end of this run
|
// Register an event to mark the end of this run
|
||||||
self.scheduler
|
self.scheduler
|
||||||
.schedule_at(EventType::RunLimitReached, run_start_time + cycles_to_run);
|
.schedule_at(EventType::RunLimitReached, end_time);
|
||||||
|
|
||||||
let mut running = true;
|
'running: loop {
|
||||||
while running {
|
// The tricky part is to avoid unnecessary calls for Scheduler::handle_events,
|
||||||
// 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
|
// Safety: Since we pushed a RunLimitReached event, we know this check has a hard limit
|
||||||
'run_unitl_next_event: while self.scheduler.timestamp()
|
while self.scheduler.timestamp()
|
||||||
<= self.scheduler.timestamp_of_next_event()
|
<= unsafe { self.scheduler.timestamp_of_next_event_unchecked() }
|
||||||
{
|
{
|
||||||
// 3 Options:
|
self.single_step();
|
||||||
// 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();
|
|
||||||
if CHECK_BREAKPOINTS {
|
if CHECK_BREAKPOINTS {
|
||||||
if let Some(bp) = self.cpu.check_breakpoint() {
|
if let Some(bp) = self.cpu.check_breakpoint() {
|
||||||
debug!("Arm7tdmi breakpoint hit 0x{:08x}", bp);
|
debug!("Arm7tdmi breakpoint hit 0x{:08x}", bp);
|
||||||
self.scheduler.cancel_pending(EventType::RunLimitReached);
|
self.scheduler.cancel_pending(EventType::RunLimitReached);
|
||||||
running = false;
|
let _ = self.handle_events();
|
||||||
|
|
||||||
if let Some(debugger) = &mut self.debugger {
|
if let Some(debugger) = &mut self.debugger {
|
||||||
debugger.notify_breakpoint(bp);
|
debugger.notify_breakpoint(bp);
|
||||||
}
|
}
|
||||||
|
break 'running;
|
||||||
break 'run_unitl_next_event;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if self.io_devs.intc.irq_pending() {
|
|
||||||
self.io_devs.haltcnt = HaltState::Running;
|
|
||||||
} else {
|
|
||||||
self.scheduler.fast_forward_to_next();
|
|
||||||
self.handle_events(&mut running)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handle_events(&mut running);
|
if self.handle_events() {
|
||||||
|
break 'running;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scheduler.timestamp() - run_start_time
|
self.scheduler.timestamp() - start_time
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_events(&mut self, run_limit_flag: &mut bool) {
|
/// Handle all pending scheduler events and return if run limit was reached.
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn handle_events(&mut self) -> bool {
|
||||||
let io = &mut (*self.io_devs);
|
let io = &mut (*self.io_devs);
|
||||||
while let Some((event, event_time)) = self.scheduler.pop_pending_event() {
|
while let Some((event, event_time)) = self.scheduler.pop_pending_event() {
|
||||||
// Since we only examine the scheduler queue every so often, most events will be handled late by a few cycles.
|
// Since we only examine the scheduler queue every so often, most events will be handled late by a few cycles.
|
||||||
|
@ -354,8 +355,8 @@ impl GameBoyAdvance {
|
||||||
// every cpu cycle, where in 99% of cases it will always be empty.
|
// every cpu cycle, where in 99% of cases it will always be empty.
|
||||||
let new_event = match event {
|
let new_event = match event {
|
||||||
EventType::RunLimitReached => {
|
EventType::RunLimitReached => {
|
||||||
*run_limit_flag = false;
|
// If we have pending events, we handle by the next frame.
|
||||||
None
|
return true;
|
||||||
}
|
}
|
||||||
EventType::DmaActivateChannel(channel_id) => {
|
EventType::DmaActivateChannel(channel_id) => {
|
||||||
io.dmac.activate_channel(channel_id);
|
io.dmac.activate_channel(channel_id);
|
||||||
|
@ -375,6 +376,7 @@ impl GameBoyAdvance {
|
||||||
self.scheduler.schedule_at(new_event, event_time + when)
|
self.scheduler.schedule_at(new_event, event_time + when)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_bios(&mut self) {
|
pub fn skip_bios(&mut self) {
|
||||||
|
@ -414,26 +416,6 @@ impl GameBoyAdvance {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "debugger")]
|
|
||||||
/// 'step' function that checks for breakpoints
|
|
||||||
/// TODO avoid code duplication
|
|
||||||
pub fn step_debugger(&mut self) -> Option<u32> {
|
|
||||||
// clear any pending DMAs
|
|
||||||
self.dma_step();
|
|
||||||
|
|
||||||
// Run the CPU
|
|
||||||
self.cpu_step();
|
|
||||||
|
|
||||||
let breakpoint = self.check_breakpoint();
|
|
||||||
|
|
||||||
let mut _running = true;
|
|
||||||
while let Some((event, cycles_late)) = self.scheduler.pop_pending_event() {
|
|
||||||
self.handle_event(event, cycles_late, &mut _running);
|
|
||||||
}
|
|
||||||
|
|
||||||
breakpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_frame_buffer(&self) -> &[u32] {
|
pub fn get_frame_buffer(&self) -> &[u32] {
|
||||||
self.sysbus.io.gpu.get_frame_buffer()
|
self.sysbus.io.gpu.get_frame_buffer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,14 @@ use arm7tdmi::gdbstub::target::TargetError;
|
||||||
use arm7tdmi::gdbstub::target::{ext::base::singlethread::SingleThreadBase, Target};
|
use arm7tdmi::gdbstub::target::{ext::base::singlethread::SingleThreadBase, Target};
|
||||||
use arm7tdmi::gdbstub_arch::arm::reg::ArmCoreRegs;
|
use arm7tdmi::gdbstub_arch::arm::reg::ArmCoreRegs;
|
||||||
use arm7tdmi::memory::Addr;
|
use arm7tdmi::memory::Addr;
|
||||||
use crossbeam::channel::{Receiver, Sender};
|
use crossbeam::channel::Receiver;
|
||||||
|
|
||||||
// mod target;
|
// mod target;
|
||||||
mod event_loop;
|
mod event_loop;
|
||||||
pub(crate) mod gdb_thread;
|
pub(crate) mod gdb_thread;
|
||||||
mod memory_map;
|
mod memory_map;
|
||||||
mod target;
|
mod target;
|
||||||
|
use target::DebuggerTarget;
|
||||||
|
|
||||||
use crate::GameBoyAdvance;
|
use crate::GameBoyAdvance;
|
||||||
|
|
||||||
|
@ -34,153 +35,119 @@ pub(crate) enum DebuggerRequest {
|
||||||
Disconnected(DisconnectReason),
|
Disconnected(DisconnectReason),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DebuggerTarget {
|
|
||||||
tx: Sender<DebuggerRequest>,
|
|
||||||
request_complete_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.request_complete_signal;
|
|
||||||
let mut finished = lock.lock().unwrap();
|
|
||||||
while !*finished {
|
|
||||||
finished = cvar.wait(finished).unwrap();
|
|
||||||
}
|
|
||||||
*finished = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct DebuggerRequestHandler {
|
pub(crate) struct DebuggerRequestHandler {
|
||||||
rx: Receiver<DebuggerRequest>,
|
rx: Receiver<DebuggerRequest>,
|
||||||
request_complete_signal: Arc<(Mutex<bool>, Condvar)>,
|
request_complete_signal: Arc<(Mutex<bool>, Condvar)>,
|
||||||
stop_signal: Arc<(Mutex<SingleThreadStopReason<u32>>, Condvar)>,
|
stop_signal: Arc<(Mutex<Option<SingleThreadStopReason<u32>>>, Condvar)>,
|
||||||
thread: JoinHandle<()>,
|
thread: JoinHandle<()>,
|
||||||
stopped: bool,
|
stopped: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DebuggerStatus {
|
impl DebuggerRequestHandler {
|
||||||
RequestComplete,
|
pub fn handle_incoming_requests(
|
||||||
ResumeRequested,
|
mut self,
|
||||||
StopRequested,
|
gba: &mut GameBoyAdvance,
|
||||||
Disconnected(DisconnectReason),
|
) -> Option<DebuggerRequestHandler> {
|
||||||
|
if self.thread.is_finished() {
|
||||||
|
warn!("gdb server thread unexpectdly died");
|
||||||
|
return self.terminate();
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
// Handle as much as messages as possible
|
||||||
|
while let Ok(mut req) = self.rx.try_recv() {
|
||||||
|
match self.handle_request(gba, &mut req) {
|
||||||
|
Ok(Some(disconnect_reason)) => {
|
||||||
|
debug!("Debugger disconnected due to {:?}", disconnect_reason);
|
||||||
|
debug!("closing gdbserver thread");
|
||||||
|
return self.terminate();
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(_) => {
|
||||||
|
error!("An error occured while handling debug request {:?}", req);
|
||||||
|
return self.terminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If target is in stopped state, stay here.
|
||||||
|
if !self.stopped {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DebuggerRequestHandler {
|
|
||||||
fn handle_request(
|
fn handle_request(
|
||||||
&mut self,
|
&mut self,
|
||||||
gba: &mut GameBoyAdvance,
|
gba: &mut GameBoyAdvance,
|
||||||
req: &mut DebuggerRequest,
|
req: &mut DebuggerRequest,
|
||||||
) -> Result<DebuggerStatus, TargetError<<DebuggerTarget as Target>::Error>> {
|
) -> Result<Option<DisconnectReason>, TargetError<<DebuggerTarget as Target>::Error>> {
|
||||||
use DebuggerRequest::*;
|
use DebuggerRequest::*;
|
||||||
match req {
|
match req {
|
||||||
ReadRegs(regs) => {
|
ReadRegs(regs) => {
|
||||||
let mut regs = regs.lock().unwrap();
|
let mut regs = regs.lock().unwrap();
|
||||||
gba.cpu.read_registers(&mut regs)?;
|
gba.cpu.read_registers(&mut regs)?;
|
||||||
debug!("Debugger requested to read regs: {:?}", regs);
|
trace!("Debugger requested to read regs: {:?}", regs);
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
WriteRegs(regs) => {
|
WriteRegs(regs) => {
|
||||||
debug!("Debugger requested to write regs: {:?}", regs);
|
trace!("Debugger requested to write regs: {:?}", regs);
|
||||||
gba.cpu.write_registers(regs)?;
|
gba.cpu.write_registers(regs)?;
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
ReadAddrs(addr, data) => {
|
ReadAddrs(addr, data) => {
|
||||||
let mut data = data.lock().unwrap();
|
let mut data = data.lock().unwrap();
|
||||||
debug!(
|
trace!(
|
||||||
"Debugger requested to read {} bytes from 0x{:08x}",
|
"Debugger requested to read {} bytes from 0x{:08x}",
|
||||||
data.len(),
|
data.len(),
|
||||||
addr
|
addr
|
||||||
);
|
);
|
||||||
gba.cpu.read_addrs(*addr, &mut data)?;
|
gba.cpu.read_addrs(*addr, &mut data)?;
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
WriteAddrs(addr, data) => {
|
WriteAddrs(addr, data) => {
|
||||||
debug!(
|
trace!(
|
||||||
"Debugger requested to write {} bytes at 0x{:08x}",
|
"Debugger requested to write {} bytes at 0x{:08x}",
|
||||||
data.len(),
|
data.len(),
|
||||||
addr
|
addr
|
||||||
);
|
);
|
||||||
gba.cpu.write_addrs(*addr, &data)?;
|
gba.cpu.write_addrs(*addr, &data)?;
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
Interrupt => {
|
Interrupt => {
|
||||||
debug!("Debugger requested stopped");
|
debug!("Ctrl-C from debugger");
|
||||||
self.notify_stop_reason(SingleThreadStopReason::Signal(Signal::SIGINT));
|
self.stopped = true;
|
||||||
Ok(DebuggerStatus::StopRequested)
|
self.complete_request(Some(SingleThreadStopReason::Signal(Signal::SIGINT)))
|
||||||
}
|
}
|
||||||
Resume => {
|
Resume => {
|
||||||
debug!("Debugger requested resume");
|
debug!("Resume");
|
||||||
self.stopped = false;
|
self.stopped = false;
|
||||||
Ok(DebuggerStatus::ResumeRequested)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
SingleStep => {
|
SingleStep => {
|
||||||
debug!("Debugger requested single step");
|
debug!("Debugger requested single step");
|
||||||
gba.cpu_step();
|
self.stopped = true;
|
||||||
let stop_reason = SingleThreadStopReason::DoneStep;
|
gba.single_step();
|
||||||
self.notify_stop_reason(stop_reason);
|
let _ = gba.handle_events();
|
||||||
Ok(DebuggerStatus::StopRequested)
|
self.complete_request(Some(SingleThreadStopReason::DoneStep))
|
||||||
}
|
}
|
||||||
AddSwBreakpoint(addr) => {
|
AddSwBreakpoint(addr) => {
|
||||||
gba.cpu.add_breakpoint(*addr);
|
gba.cpu.add_breakpoint(*addr);
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
DelSwBreakpoint(addr) => {
|
DelSwBreakpoint(addr) => {
|
||||||
gba.cpu.del_breakpoint(*addr);
|
gba.cpu.del_breakpoint(*addr);
|
||||||
Ok(DebuggerStatus::RequestComplete)
|
self.complete_request(None)
|
||||||
}
|
}
|
||||||
Disconnected(reason) => Ok(DebuggerStatus::Disconnected(*reason)),
|
Disconnected(reason) => Ok(Some(*reason)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminate(mut self, should_interrupt_frame: &mut bool) -> Option<DebuggerRequestHandler> {
|
fn terminate(mut self) -> Option<DebuggerRequestHandler> {
|
||||||
self.notify_stop_reason(SingleThreadStopReason::Exited(1));
|
self.notify_stop_reason(SingleThreadStopReason::Exited(1));
|
||||||
self.thread.join().unwrap();
|
self.thread.join().unwrap();
|
||||||
self.stopped = true;
|
|
||||||
*should_interrupt_frame = true;
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_incoming_requests(
|
|
||||||
mut self,
|
|
||||||
gba: &mut GameBoyAdvance,
|
|
||||||
should_interrupt_frame: &mut bool,
|
|
||||||
) -> Option<DebuggerRequestHandler> {
|
|
||||||
if self.thread.is_finished() {
|
|
||||||
warn!("gdb server thread unexpectdly died");
|
|
||||||
return self.terminate(should_interrupt_frame);
|
|
||||||
}
|
|
||||||
while let Ok(mut req) = self.rx.try_recv() {
|
|
||||||
match self.handle_request(gba, &mut req) {
|
|
||||||
Ok(DebuggerStatus::RequestComplete) => {
|
|
||||||
self.notify_request_complete();
|
|
||||||
}
|
|
||||||
Ok(DebuggerStatus::StopRequested) => {
|
|
||||||
self.stopped = true;
|
|
||||||
self.notify_request_complete();
|
|
||||||
}
|
|
||||||
Ok(DebuggerStatus::ResumeRequested) => {
|
|
||||||
self.stopped = false;
|
|
||||||
self.notify_request_complete();
|
|
||||||
}
|
|
||||||
Ok(DebuggerStatus::Disconnected(reason)) => {
|
|
||||||
debug!("Debugger disconnected due to {:?}", reason);
|
|
||||||
debug!("closing gdbserver thread");
|
|
||||||
return self.terminate(should_interrupt_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(_) => {
|
|
||||||
error!("An error occured while handling debug request {:?}", req);
|
|
||||||
return self.terminate(should_interrupt_frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*should_interrupt_frame = self.stopped;
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn notify_request_complete(&mut self) {
|
fn notify_request_complete(&mut self) {
|
||||||
let (lock, cvar) = &*self.request_complete_signal;
|
let (lock, cvar) = &*self.request_complete_signal;
|
||||||
let mut finished = lock.lock().unwrap();
|
let mut finished = lock.lock().unwrap();
|
||||||
|
@ -188,15 +155,27 @@ impl DebuggerRequestHandler {
|
||||||
cvar.notify_one();
|
cvar.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn complete_request(
|
||||||
|
&mut self,
|
||||||
|
stop_reason: Option<SingleThreadStopReason<u32>>,
|
||||||
|
) -> Result<Option<DisconnectReason>, TargetError<<DebuggerTarget as Target>::Error>> {
|
||||||
|
self.notify_request_complete();
|
||||||
|
if let Some(stop_reason) = stop_reason {
|
||||||
|
self.notify_stop_reason(stop_reason);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn notify_stop_reason(&mut self, reason: SingleThreadStopReason<u32>) {
|
pub fn notify_stop_reason(&mut self, reason: SingleThreadStopReason<u32>) {
|
||||||
self.stopped = true;
|
debug!("Notifying debugger on stop reason: {:?}", reason);
|
||||||
let (lock, cvar) = &*self.stop_signal;
|
let (lock, cvar) = &*self.stop_signal;
|
||||||
let mut stop_reason = lock.lock().unwrap();
|
let mut stop_reason = lock.lock().unwrap();
|
||||||
*stop_reason = reason;
|
*stop_reason = Some(reason);
|
||||||
cvar.notify_one();
|
cvar.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_breakpoint(&mut self, _bp: Addr) {
|
pub fn notify_breakpoint(&mut self, _bp: Addr) {
|
||||||
|
self.stopped = true;
|
||||||
self.notify_stop_reason(SingleThreadStopReason::SwBreak(()));
|
self.notify_stop_reason(SingleThreadStopReason::SwBreak(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ use arm7tdmi::gdb::gdbstub::{
|
||||||
target::Target,
|
target::Target,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{DebuggerRequest, DebuggerTarget};
|
use super::{target::DebuggerTarget, DebuggerRequest};
|
||||||
|
|
||||||
pub struct DebuggerEventLoop {}
|
pub(crate) struct DebuggerEventLoop {}
|
||||||
|
|
||||||
impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
||||||
type Target = DebuggerTarget;
|
type Target = DebuggerTarget;
|
||||||
|
@ -35,17 +35,12 @@ impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
||||||
return Ok(run_blocking::Event::IncomingData(byte));
|
return Ok(run_blocking::Event::IncomingData(byte));
|
||||||
} else {
|
} else {
|
||||||
// try and wait for the stop reason
|
// try and wait for the stop reason
|
||||||
let (lock, cvar) = &*target.stop_signal;
|
if let Some(stop_reason) =
|
||||||
let stop_reason = lock.lock().unwrap();
|
target.wait_for_stop_reason_timeout(Duration::from_millis(10))
|
||||||
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);
|
info!("Target stopped due to {:?}!", stop_reason);
|
||||||
return Ok(run_blocking::Event::TargetStopped(*stop_reason));
|
return Ok(run_blocking::Event::TargetStopped(stop_reason));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,12 +49,8 @@ impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
||||||
target: &mut DebuggerTarget,
|
target: &mut DebuggerTarget,
|
||||||
) -> Result<Option<SingleThreadStopReason<u32>>, <DebuggerTarget as Target>::Error> {
|
) -> Result<Option<SingleThreadStopReason<u32>>, <DebuggerTarget as Target>::Error> {
|
||||||
info!("on_interrupt: sending stop message");
|
info!("on_interrupt: sending stop message");
|
||||||
target.tx.send(DebuggerRequest::Interrupt).unwrap();
|
target.debugger_request(DebuggerRequest::Interrupt);
|
||||||
target.wait_for_operation();
|
|
||||||
info!("Waiting for target to stop <blocking>");
|
info!("Waiting for target to stop <blocking>");
|
||||||
let (lock, cvar) = &*target.stop_signal;
|
Ok(Some(target.wait_for_stop_reason_blocking()))
|
||||||
let stop_signal = lock.lock().unwrap();
|
|
||||||
let stop_signal = cvar.wait(stop_signal).unwrap();
|
|
||||||
Ok(Some(*stop_signal))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,8 @@ use arm7tdmi::{
|
||||||
|
|
||||||
use crate::{GBAError, GameBoyAdvance};
|
use crate::{GBAError, GameBoyAdvance};
|
||||||
|
|
||||||
use super::{
|
use super::target::DebuggerTarget;
|
||||||
event_loop::DebuggerEventLoop, DebuggerRequest, DebuggerRequestHandler, DebuggerTarget,
|
use super::{event_loop::DebuggerEventLoop, DebuggerRequestHandler};
|
||||||
};
|
|
||||||
|
|
||||||
/// Starts a gdbserver thread
|
/// Starts a gdbserver thread
|
||||||
pub(crate) fn start_gdb_server_thread(
|
pub(crate) fn start_gdb_server_thread(
|
||||||
|
@ -22,10 +21,7 @@ pub(crate) fn start_gdb_server_thread(
|
||||||
) -> Result<DebuggerRequestHandler, GBAError> {
|
) -> Result<DebuggerRequestHandler, GBAError> {
|
||||||
let (tx, rx) = crossbeam::channel::unbounded();
|
let (tx, rx) = crossbeam::channel::unbounded();
|
||||||
let request_complete_signal = Arc::new((Mutex::new(false), Condvar::new()));
|
let request_complete_signal = Arc::new((Mutex::new(false), Condvar::new()));
|
||||||
let stop_signal = Arc::new((
|
let stop_signal = Arc::new((Mutex::new(None), Condvar::new()));
|
||||||
Mutex::new(SingleThreadStopReason::Signal(Signal::SIGINT)),
|
|
||||||
Condvar::new(),
|
|
||||||
));
|
|
||||||
let stop_signal_2 = stop_signal.clone();
|
let stop_signal_2 = stop_signal.clone();
|
||||||
let request_complete_signal_2 = request_complete_signal.clone();
|
let request_complete_signal_2 = request_complete_signal.clone();
|
||||||
let memory_map = gba.sysbus.generate_memory_map_xml().unwrap();
|
let memory_map = gba.sysbus.generate_memory_map_xml().unwrap();
|
||||||
|
@ -35,21 +31,14 @@ pub(crate) fn start_gdb_server_thread(
|
||||||
debug!("starting GDB Server thread");
|
debug!("starting GDB Server thread");
|
||||||
let conn: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(conn);
|
let conn: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(conn);
|
||||||
|
|
||||||
let mut target = DebuggerTarget {
|
let mut target =
|
||||||
tx,
|
DebuggerTarget::new(tx, request_complete_signal_2, stop_signal_2, memory_map);
|
||||||
request_complete_signal: request_complete_signal_2,
|
|
||||||
stop_signal: stop_signal_2,
|
|
||||||
memory_map,
|
|
||||||
};
|
|
||||||
let gdbserver = GdbStub::new(conn);
|
let gdbserver = GdbStub::new(conn);
|
||||||
let disconnect_reason = gdbserver
|
let disconnect_reason = gdbserver
|
||||||
.run_blocking::<DebuggerEventLoop>(&mut target)
|
.run_blocking::<DebuggerEventLoop>(&mut target)
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
target
|
target.disconnect(disconnect_reason);
|
||||||
.tx
|
|
||||||
.send(DebuggerRequest::Disconnected(disconnect_reason))
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut debugger = DebuggerRequestHandler {
|
let mut debugger = DebuggerRequestHandler {
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Implementing the Target trait for gdbstub
|
/// Implementing the Target trait for gdbstub
|
||||||
use arm7tdmi::gdb::{copy_range_to_buf, gdbstub, gdbstub_arch};
|
use arm7tdmi::gdb::{copy_range_to_buf, gdbstub, gdbstub_arch};
|
||||||
|
use crossbeam::channel::Sender;
|
||||||
use gdbstub::common::Signal;
|
use gdbstub::common::Signal;
|
||||||
|
use gdbstub::stub::{DisconnectReason, SingleThreadStopReason};
|
||||||
use gdbstub::target::ext::base::singlethread::{
|
use gdbstub::target::ext::base::singlethread::{
|
||||||
SingleThreadBase, SingleThreadResume, SingleThreadSingleStep,
|
SingleThreadBase, SingleThreadResume, SingleThreadSingleStep,
|
||||||
};
|
};
|
||||||
|
@ -12,7 +15,78 @@ use gdbstub::target::ext::breakpoints::BreakpointsOps;
|
||||||
use gdbstub::target::{self, Target, TargetError, TargetResult};
|
use gdbstub::target::{self, Target, TargetError, TargetResult};
|
||||||
use gdbstub_arch::arm::reg::ArmCoreRegs;
|
use gdbstub_arch::arm::reg::ArmCoreRegs;
|
||||||
|
|
||||||
use super::{DebuggerRequest, DebuggerTarget};
|
use super::DebuggerRequest;
|
||||||
|
|
||||||
|
pub(crate) struct DebuggerTarget {
|
||||||
|
tx: Sender<DebuggerRequest>,
|
||||||
|
request_complete_signal: Arc<(Mutex<bool>, Condvar)>,
|
||||||
|
pub(crate) stop_signal: Arc<(Mutex<Option<SingleThreadStopReason<u32>>>, Condvar)>,
|
||||||
|
pub(crate) memory_map: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerTarget {
|
||||||
|
pub fn new(
|
||||||
|
tx: Sender<DebuggerRequest>,
|
||||||
|
request_complete_signal: Arc<(Mutex<bool>, Condvar)>,
|
||||||
|
stop_signal: Arc<(Mutex<Option<SingleThreadStopReason<u32>>>, Condvar)>,
|
||||||
|
memory_map: String,
|
||||||
|
) -> DebuggerTarget {
|
||||||
|
DebuggerTarget {
|
||||||
|
tx,
|
||||||
|
request_complete_signal,
|
||||||
|
stop_signal,
|
||||||
|
memory_map,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugger_request(&mut self, req: DebuggerRequest) {
|
||||||
|
let (lock, cvar) = &*self.request_complete_signal;
|
||||||
|
let mut finished = lock.lock().unwrap();
|
||||||
|
*finished = false;
|
||||||
|
|
||||||
|
// now send the request
|
||||||
|
self.tx.send(req).unwrap();
|
||||||
|
|
||||||
|
// wait for the notification
|
||||||
|
while !*finished {
|
||||||
|
finished = cvar.wait(finished).unwrap();
|
||||||
|
}
|
||||||
|
*finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_stop_reason_timeout(
|
||||||
|
&mut self,
|
||||||
|
timeout: Duration,
|
||||||
|
) -> Option<SingleThreadStopReason<u32>> {
|
||||||
|
let (lock, cvar) = &*self.stop_signal;
|
||||||
|
let mut stop_reason = lock.lock().unwrap();
|
||||||
|
if let Some(stop_reason) = stop_reason.take() {
|
||||||
|
return Some(stop_reason);
|
||||||
|
}
|
||||||
|
let (mut stop_reason, timeout_result) = cvar.wait_timeout(stop_reason, timeout).unwrap();
|
||||||
|
if timeout_result.timed_out() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(stop_reason.take().expect("None is not expected here"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wait_for_stop_reason_blocking(&mut self) -> SingleThreadStopReason<u32> {
|
||||||
|
let (lock, cvar) = &*self.stop_signal;
|
||||||
|
let mut stop_reason = lock.lock().unwrap();
|
||||||
|
if let Some(stop_reason) = stop_reason.take() {
|
||||||
|
return stop_reason;
|
||||||
|
}
|
||||||
|
let mut stop_reason = cvar.wait(stop_reason).unwrap();
|
||||||
|
stop_reason.take().expect("None is not expected here")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disconnect(&mut self, disconnect_reason: DisconnectReason) {
|
||||||
|
self.tx
|
||||||
|
.send(DebuggerRequest::Disconnected(disconnect_reason))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Target for DebuggerTarget {
|
impl Target for DebuggerTarget {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
@ -37,28 +111,19 @@ impl Target for DebuggerTarget {
|
||||||
impl SingleThreadBase for DebuggerTarget {
|
impl SingleThreadBase for DebuggerTarget {
|
||||||
fn read_registers(&mut self, regs: &mut ArmCoreRegs) -> TargetResult<(), Self> {
|
fn read_registers(&mut self, regs: &mut ArmCoreRegs) -> TargetResult<(), Self> {
|
||||||
let regs_copy = Arc::new(Mutex::new(ArmCoreRegs::default()));
|
let regs_copy = Arc::new(Mutex::new(ArmCoreRegs::default()));
|
||||||
self.tx
|
self.debugger_request(DebuggerRequest::ReadRegs(regs_copy.clone()));
|
||||||
.send(DebuggerRequest::ReadRegs(regs_copy.clone()))
|
|
||||||
.unwrap();
|
|
||||||
self.wait_for_operation();
|
|
||||||
regs_copy.lock().unwrap().clone_into(regs);
|
regs_copy.lock().unwrap().clone_into(regs);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_registers(&mut self, regs: &ArmCoreRegs) -> TargetResult<(), Self> {
|
fn write_registers(&mut self, regs: &ArmCoreRegs) -> TargetResult<(), Self> {
|
||||||
self.tx
|
self.debugger_request(DebuggerRequest::WriteRegs(regs.clone()));
|
||||||
.send(DebuggerRequest::WriteRegs(regs.clone()))
|
|
||||||
.unwrap();
|
|
||||||
self.wait_for_operation();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> {
|
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()));
|
let buffer = Arc::new(Mutex::new(vec![0; data.len()].into_boxed_slice()));
|
||||||
self.tx
|
self.debugger_request(DebuggerRequest::ReadAddrs(start_addr, buffer.clone()));
|
||||||
.send(DebuggerRequest::ReadAddrs(start_addr, buffer.clone()))
|
|
||||||
.unwrap();
|
|
||||||
self.wait_for_operation();
|
|
||||||
data.copy_from_slice(&buffer.lock().unwrap());
|
data.copy_from_slice(&buffer.lock().unwrap());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -78,8 +143,7 @@ impl SingleThreadBase for DebuggerTarget {
|
||||||
|
|
||||||
impl SingleThreadResume for DebuggerTarget {
|
impl SingleThreadResume for DebuggerTarget {
|
||||||
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
||||||
self.tx.send(DebuggerRequest::Resume).unwrap();
|
self.debugger_request(DebuggerRequest::Resume);
|
||||||
self.wait_for_operation();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,8 +158,7 @@ impl SingleThreadResume for DebuggerTarget {
|
||||||
|
|
||||||
impl SingleThreadSingleStep for DebuggerTarget {
|
impl SingleThreadSingleStep for DebuggerTarget {
|
||||||
fn step(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
fn step(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
||||||
self.tx.send(DebuggerRequest::SingleStep).unwrap();
|
self.debugger_request(DebuggerRequest::SingleStep);
|
||||||
self.wait_for_operation();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,10 +195,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget {
|
||||||
addr: u32,
|
addr: u32,
|
||||||
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
||||||
) -> TargetResult<bool, Self> {
|
) -> TargetResult<bool, Self> {
|
||||||
self.tx
|
self.debugger_request(DebuggerRequest::AddSwBreakpoint(addr));
|
||||||
.send(DebuggerRequest::AddSwBreakpoint(addr))
|
|
||||||
.unwrap();
|
|
||||||
self.wait_for_operation();
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,10 +204,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget {
|
||||||
addr: u32,
|
addr: u32,
|
||||||
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
||||||
) -> TargetResult<bool, Self> {
|
) -> TargetResult<bool, Self> {
|
||||||
self.tx
|
self.debugger_request(DebuggerRequest::DelSwBreakpoint(addr));
|
||||||
.send(DebuggerRequest::DelSwBreakpoint(addr))
|
|
||||||
.unwrap();
|
|
||||||
self.wait_for_operation();
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use self::consts::*;
|
||||||
pub enum HaltState {
|
pub enum HaltState {
|
||||||
Running,
|
Running,
|
||||||
Halt, // In Halt mode, the CPU is paused as long as (IE AND IF)=0,
|
Halt, // In Halt mode, the CPU is paused as long as (IE AND IF)=0,
|
||||||
Stop, // In Stop mode, most of the hardware including sound and video are paused
|
// Stop, // In Stop mode, most of the hardware including sound and video are paused TODO: handle
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
@ -288,7 +288,7 @@ impl BusIO for IoDevices {
|
||||||
REG_POSTFLG => io.post_boot_flag = value != 0,
|
REG_POSTFLG => io.post_boot_flag = value != 0,
|
||||||
REG_HALTCNT => {
|
REG_HALTCNT => {
|
||||||
if value & 0x80 != 0 {
|
if value & 0x80 != 0 {
|
||||||
io.haltcnt = HaltState::Stop;
|
// io.haltcnt = HaltState::Stop;
|
||||||
panic!("Can't handle HaltCtrl == Stop yet");
|
panic!("Can't handle HaltCtrl == Stop yet");
|
||||||
} else {
|
} else {
|
||||||
io.haltcnt = HaltState::Halt;
|
io.haltcnt = HaltState::Halt;
|
||||||
|
|
|
@ -194,9 +194,12 @@ impl Scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// The event queue is assumed to be not empty
|
/// Safety - Onyl safe to call when we know the event queue is not empty
|
||||||
pub fn timestamp_of_next_event(&self) -> usize {
|
pub unsafe fn timestamp_of_next_event_unchecked(&self) -> usize {
|
||||||
self.events.peek().unwrap_or_else(|| unreachable!()).time
|
self.events
|
||||||
|
.peek()
|
||||||
|
.unwrap_or_else(|| std::hint::unreachable_unchecked())
|
||||||
|
.time
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -107,7 +107,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// normal_panic(panic_info);
|
// normal_panic(panic_info);
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
|
|
||||||
if opts.skip_bios {
|
if opts.skip_bios {
|
||||||
println!("Skipping bios animation..");
|
println!("Skipping bios animation..");
|
||||||
gba.skip_bios();
|
gba.skip_bios();
|
||||||
|
|
Reference in a new issue