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)]
|
||||
#[repr(u8)]
|
||||
pub enum CpuMode {
|
||||
User = 0b10000,
|
||||
Fiq = 0b10001,
|
||||
|
|
138
core/src/gba.rs
138
core/src/gba.rs
|
@ -2,7 +2,6 @@
|
|||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use arm7tdmi::gdbstub::stub::SingleThreadStopReason;
|
||||
use bincode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -251,19 +250,9 @@ impl GameBoyAdvance {
|
|||
|
||||
/// Recv & handle messages from the debugger, and return if we are stopped or not
|
||||
pub fn debugger_run(&mut self) {
|
||||
let mut should_interrupt_frame = false;
|
||||
let debugger = self.debugger.take().expect("debugger should be None here");
|
||||
self.debugger = debugger.handle_incoming_requests(self, &mut should_interrupt_frame);
|
||||
|
||||
if let Some(debugger) = &mut self.debugger {
|
||||
if should_interrupt_frame {
|
||||
debugger.notify_stop_reason(SingleThreadStopReason::DoneStep);
|
||||
} else {
|
||||
self.frame_interruptible();
|
||||
}
|
||||
} else {
|
||||
error!("debugger was disconnected!");
|
||||
}
|
||||
self.debugger = debugger.handle_incoming_requests(self);
|
||||
self.frame_interruptible();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -272,10 +261,15 @@ impl GameBoyAdvance {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn cpu_step(&mut self) {
|
||||
fn cpu_interrupt(&mut self) {
|
||||
self.cpu.irq();
|
||||
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.irq();
|
||||
self.io_devs.haltcnt = HaltState::Running;
|
||||
self.cpu_interrupt();
|
||||
}
|
||||
self.cpu.step();
|
||||
}
|
||||
|
@ -285,7 +279,29 @@ impl GameBoyAdvance {
|
|||
match (self.io_devs.dmac.is_active(), self.io_devs.haltcnt) {
|
||||
(true, _) => Some(BusMaster::Dma),
|
||||
(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
|
||||
#[inline]
|
||||
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
|
||||
self.scheduler
|
||||
.schedule_at(EventType::RunLimitReached, run_start_time + cycles_to_run);
|
||||
.schedule_at(EventType::RunLimitReached, end_time);
|
||||
|
||||
let mut running = true;
|
||||
while running {
|
||||
// The tricky part is to avoid unnecessary calls for Scheduler::process_pending,
|
||||
'running: loop {
|
||||
// The tricky part is to avoid unnecessary calls for Scheduler::handle_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
|
||||
'run_unitl_next_event: while self.scheduler.timestamp()
|
||||
<= self.scheduler.timestamp_of_next_event()
|
||||
// Safety: Since we pushed a RunLimitReached event, we know this check has a hard limit
|
||||
while self.scheduler.timestamp()
|
||||
<= unsafe { self.scheduler.timestamp_of_next_event_unchecked() }
|
||||
{
|
||||
// 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();
|
||||
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;
|
||||
} else {
|
||||
self.scheduler.fast_forward_to_next();
|
||||
self.handle_events(&mut running)
|
||||
self.single_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);
|
||||
let _ = self.handle_events();
|
||||
if let Some(debugger) = &mut self.debugger {
|
||||
debugger.notify_breakpoint(bp);
|
||||
}
|
||||
break '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);
|
||||
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.
|
||||
|
@ -354,8 +355,8 @@ impl GameBoyAdvance {
|
|||
// every cpu cycle, where in 99% of cases it will always be empty.
|
||||
let new_event = match event {
|
||||
EventType::RunLimitReached => {
|
||||
*run_limit_flag = false;
|
||||
None
|
||||
// If we have pending events, we handle by the next frame.
|
||||
return true;
|
||||
}
|
||||
EventType::DmaActivateChannel(channel_id) => {
|
||||
io.dmac.activate_channel(channel_id);
|
||||
|
@ -375,6 +376,7 @@ impl GameBoyAdvance {
|
|||
self.scheduler.schedule_at(new_event, event_time + when)
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn skip_bios(&mut self) {
|
||||
|
@ -414,26 +416,6 @@ impl GameBoyAdvance {
|
|||
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] {
|
||||
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_arch::arm::reg::ArmCoreRegs;
|
||||
use arm7tdmi::memory::Addr;
|
||||
use crossbeam::channel::{Receiver, Sender};
|
||||
use crossbeam::channel::Receiver;
|
||||
|
||||
// mod target;
|
||||
mod event_loop;
|
||||
pub(crate) mod gdb_thread;
|
||||
mod memory_map;
|
||||
mod target;
|
||||
use target::DebuggerTarget;
|
||||
|
||||
use crate::GameBoyAdvance;
|
||||
|
||||
|
@ -34,153 +35,119 @@ pub(crate) enum DebuggerRequest {
|
|||
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 {
|
||||
rx: Receiver<DebuggerRequest>,
|
||||
request_complete_signal: Arc<(Mutex<bool>, Condvar)>,
|
||||
stop_signal: Arc<(Mutex<SingleThreadStopReason<u32>>, Condvar)>,
|
||||
stop_signal: Arc<(Mutex<Option<SingleThreadStopReason<u32>>>, Condvar)>,
|
||||
thread: JoinHandle<()>,
|
||||
stopped: bool,
|
||||
}
|
||||
|
||||
enum DebuggerStatus {
|
||||
RequestComplete,
|
||||
ResumeRequested,
|
||||
StopRequested,
|
||||
Disconnected(DisconnectReason),
|
||||
}
|
||||
|
||||
impl DebuggerRequestHandler {
|
||||
pub fn handle_incoming_requests(
|
||||
mut self,
|
||||
gba: &mut GameBoyAdvance,
|
||||
) -> 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)
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
&mut self,
|
||||
gba: &mut GameBoyAdvance,
|
||||
req: &mut DebuggerRequest,
|
||||
) -> Result<DebuggerStatus, TargetError<<DebuggerTarget as Target>::Error>> {
|
||||
) -> Result<Option<DisconnectReason>, TargetError<<DebuggerTarget as Target>::Error>> {
|
||||
use DebuggerRequest::*;
|
||||
match req {
|
||||
ReadRegs(regs) => {
|
||||
let mut regs = regs.lock().unwrap();
|
||||
gba.cpu.read_registers(&mut regs)?;
|
||||
debug!("Debugger requested to read regs: {:?}", regs);
|
||||
Ok(DebuggerStatus::RequestComplete)
|
||||
trace!("Debugger requested to read regs: {:?}", regs);
|
||||
self.complete_request(None)
|
||||
}
|
||||
WriteRegs(regs) => {
|
||||
debug!("Debugger requested to write regs: {:?}", regs);
|
||||
trace!("Debugger requested to write regs: {:?}", regs);
|
||||
gba.cpu.write_registers(regs)?;
|
||||
Ok(DebuggerStatus::RequestComplete)
|
||||
self.complete_request(None)
|
||||
}
|
||||
ReadAddrs(addr, data) => {
|
||||
let mut data = data.lock().unwrap();
|
||||
debug!(
|
||||
trace!(
|
||||
"Debugger requested to read {} bytes from 0x{:08x}",
|
||||
data.len(),
|
||||
addr
|
||||
);
|
||||
gba.cpu.read_addrs(*addr, &mut data)?;
|
||||
Ok(DebuggerStatus::RequestComplete)
|
||||
self.complete_request(None)
|
||||
}
|
||||
WriteAddrs(addr, data) => {
|
||||
debug!(
|
||||
trace!(
|
||||
"Debugger requested to write {} bytes at 0x{:08x}",
|
||||
data.len(),
|
||||
addr
|
||||
);
|
||||
gba.cpu.write_addrs(*addr, &data)?;
|
||||
Ok(DebuggerStatus::RequestComplete)
|
||||
self.complete_request(None)
|
||||
}
|
||||
Interrupt => {
|
||||
debug!("Debugger requested stopped");
|
||||
self.notify_stop_reason(SingleThreadStopReason::Signal(Signal::SIGINT));
|
||||
Ok(DebuggerStatus::StopRequested)
|
||||
debug!("Ctrl-C from debugger");
|
||||
self.stopped = true;
|
||||
self.complete_request(Some(SingleThreadStopReason::Signal(Signal::SIGINT)))
|
||||
}
|
||||
Resume => {
|
||||
debug!("Debugger requested resume");
|
||||
debug!("Resume");
|
||||
self.stopped = false;
|
||||
Ok(DebuggerStatus::ResumeRequested)
|
||||
self.complete_request(None)
|
||||
}
|
||||
SingleStep => {
|
||||
debug!("Debugger requested single step");
|
||||
gba.cpu_step();
|
||||
let stop_reason = SingleThreadStopReason::DoneStep;
|
||||
self.notify_stop_reason(stop_reason);
|
||||
Ok(DebuggerStatus::StopRequested)
|
||||
self.stopped = true;
|
||||
gba.single_step();
|
||||
let _ = gba.handle_events();
|
||||
self.complete_request(Some(SingleThreadStopReason::DoneStep))
|
||||
}
|
||||
AddSwBreakpoint(addr) => {
|
||||
gba.cpu.add_breakpoint(*addr);
|
||||
Ok(DebuggerStatus::RequestComplete)
|
||||
self.complete_request(None)
|
||||
}
|
||||
DelSwBreakpoint(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.thread.join().unwrap();
|
||||
self.stopped = true;
|
||||
*should_interrupt_frame = true;
|
||||
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) {
|
||||
let (lock, cvar) = &*self.request_complete_signal;
|
||||
let mut finished = lock.lock().unwrap();
|
||||
|
@ -188,15 +155,27 @@ impl DebuggerRequestHandler {
|
|||
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>) {
|
||||
self.stopped = true;
|
||||
debug!("Notifying debugger on stop reason: {:?}", reason);
|
||||
let (lock, cvar) = &*self.stop_signal;
|
||||
let mut stop_reason = lock.lock().unwrap();
|
||||
*stop_reason = reason;
|
||||
*stop_reason = Some(reason);
|
||||
cvar.notify_one();
|
||||
}
|
||||
|
||||
pub fn notify_breakpoint(&mut self, _bp: Addr) {
|
||||
self.stopped = true;
|
||||
self.notify_stop_reason(SingleThreadStopReason::SwBreak(()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ use arm7tdmi::gdb::gdbstub::{
|
|||
target::Target,
|
||||
};
|
||||
|
||||
use super::{DebuggerRequest, DebuggerTarget};
|
||||
use super::{target::DebuggerTarget, DebuggerRequest};
|
||||
|
||||
pub struct DebuggerEventLoop {}
|
||||
pub(crate) struct DebuggerEventLoop {}
|
||||
|
||||
impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
||||
type Target = DebuggerTarget;
|
||||
|
@ -35,17 +35,12 @@ impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
|||
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;
|
||||
if let Some(stop_reason) =
|
||||
target.wait_for_stop_reason_timeout(Duration::from_millis(10))
|
||||
{
|
||||
info!("Target stopped due to {:?}!", stop_reason);
|
||||
return Ok(run_blocking::Event::TargetStopped(stop_reason));
|
||||
}
|
||||
info!("Target stopped due to {:?}!", stop_reason);
|
||||
return Ok(run_blocking::Event::TargetStopped(*stop_reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,12 +49,8 @@ impl run_blocking::BlockingEventLoop for DebuggerEventLoop {
|
|||
target: &mut DebuggerTarget,
|
||||
) -> Result<Option<SingleThreadStopReason<u32>>, <DebuggerTarget as Target>::Error> {
|
||||
info!("on_interrupt: sending stop message");
|
||||
target.tx.send(DebuggerRequest::Interrupt).unwrap();
|
||||
target.wait_for_operation();
|
||||
target.debugger_request(DebuggerRequest::Interrupt);
|
||||
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))
|
||||
Ok(Some(target.wait_for_stop_reason_blocking()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,8 @@ use arm7tdmi::{
|
|||
|
||||
use crate::{GBAError, GameBoyAdvance};
|
||||
|
||||
use super::{
|
||||
event_loop::DebuggerEventLoop, DebuggerRequest, DebuggerRequestHandler, DebuggerTarget,
|
||||
};
|
||||
use super::target::DebuggerTarget;
|
||||
use super::{event_loop::DebuggerEventLoop, DebuggerRequestHandler};
|
||||
|
||||
/// Starts a gdbserver thread
|
||||
pub(crate) fn start_gdb_server_thread(
|
||||
|
@ -22,10 +21,7 @@ pub(crate) fn start_gdb_server_thread(
|
|||
) -> Result<DebuggerRequestHandler, GBAError> {
|
||||
let (tx, rx) = crossbeam::channel::unbounded();
|
||||
let request_complete_signal = Arc::new((Mutex::new(false), Condvar::new()));
|
||||
let stop_signal = Arc::new((
|
||||
Mutex::new(SingleThreadStopReason::Signal(Signal::SIGINT)),
|
||||
Condvar::new(),
|
||||
));
|
||||
let stop_signal = Arc::new((Mutex::new(None), Condvar::new()));
|
||||
let stop_signal_2 = stop_signal.clone();
|
||||
let request_complete_signal_2 = request_complete_signal.clone();
|
||||
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");
|
||||
let conn: Box<dyn ConnectionExt<Error = std::io::Error>> = Box::new(conn);
|
||||
|
||||
let mut target = DebuggerTarget {
|
||||
tx,
|
||||
request_complete_signal: request_complete_signal_2,
|
||||
stop_signal: stop_signal_2,
|
||||
memory_map,
|
||||
};
|
||||
let mut target =
|
||||
DebuggerTarget::new(tx, request_complete_signal_2, 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(DebuggerRequest::Disconnected(disconnect_reason))
|
||||
.unwrap();
|
||||
target.disconnect(disconnect_reason);
|
||||
});
|
||||
|
||||
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
|
||||
use arm7tdmi::gdb::{copy_range_to_buf, gdbstub, gdbstub_arch};
|
||||
use crossbeam::channel::Sender;
|
||||
use gdbstub::common::Signal;
|
||||
use gdbstub::stub::{DisconnectReason, SingleThreadStopReason};
|
||||
use gdbstub::target::ext::base::singlethread::{
|
||||
SingleThreadBase, SingleThreadResume, SingleThreadSingleStep,
|
||||
};
|
||||
|
@ -12,7 +15,78 @@ use gdbstub::target::ext::breakpoints::BreakpointsOps;
|
|||
use gdbstub::target::{self, Target, TargetError, TargetResult};
|
||||
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 {
|
||||
type Error = ();
|
||||
|
@ -37,28 +111,19 @@ impl Target for DebuggerTarget {
|
|||
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(DebuggerRequest::ReadRegs(regs_copy.clone()))
|
||||
.unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::ReadRegs(regs_copy.clone()));
|
||||
regs_copy.lock().unwrap().clone_into(regs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_registers(&mut self, regs: &ArmCoreRegs) -> TargetResult<(), Self> {
|
||||
self.tx
|
||||
.send(DebuggerRequest::WriteRegs(regs.clone()))
|
||||
.unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::WriteRegs(regs.clone()));
|
||||
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(DebuggerRequest::ReadAddrs(start_addr, buffer.clone()))
|
||||
.unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::ReadAddrs(start_addr, buffer.clone()));
|
||||
data.copy_from_slice(&buffer.lock().unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -78,8 +143,7 @@ impl SingleThreadBase for DebuggerTarget {
|
|||
|
||||
impl SingleThreadResume for DebuggerTarget {
|
||||
fn resume(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
||||
self.tx.send(DebuggerRequest::Resume).unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::Resume);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -94,8 +158,7 @@ impl SingleThreadResume for DebuggerTarget {
|
|||
|
||||
impl SingleThreadSingleStep for DebuggerTarget {
|
||||
fn step(&mut self, _signal: Option<Signal>) -> Result<(), Self::Error> {
|
||||
self.tx.send(DebuggerRequest::SingleStep).unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::SingleStep);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -132,10 +195,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget {
|
|||
addr: u32,
|
||||
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
||||
) -> TargetResult<bool, Self> {
|
||||
self.tx
|
||||
.send(DebuggerRequest::AddSwBreakpoint(addr))
|
||||
.unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::AddSwBreakpoint(addr));
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
|
@ -144,10 +204,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget {
|
|||
addr: u32,
|
||||
_kind: gdbstub_arch::arm::ArmBreakpointKind,
|
||||
) -> TargetResult<bool, Self> {
|
||||
self.tx
|
||||
.send(DebuggerRequest::DelSwBreakpoint(addr))
|
||||
.unwrap();
|
||||
self.wait_for_operation();
|
||||
self.debugger_request(DebuggerRequest::DelSwBreakpoint(addr));
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ use self::consts::*;
|
|||
pub enum HaltState {
|
||||
Running,
|
||||
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)]
|
||||
|
@ -288,7 +288,7 @@ impl BusIO for IoDevices {
|
|||
REG_POSTFLG => io.post_boot_flag = value != 0,
|
||||
REG_HALTCNT => {
|
||||
if value & 0x80 != 0 {
|
||||
io.haltcnt = HaltState::Stop;
|
||||
// io.haltcnt = HaltState::Stop;
|
||||
panic!("Can't handle HaltCtrl == Stop yet");
|
||||
} else {
|
||||
io.haltcnt = HaltState::Halt;
|
||||
|
|
|
@ -194,9 +194,12 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
/// The event queue is assumed to be not empty
|
||||
pub fn timestamp_of_next_event(&self) -> usize {
|
||||
self.events.peek().unwrap_or_else(|| unreachable!()).time
|
||||
/// Safety - Onyl safe to call when we know the event queue is not empty
|
||||
pub unsafe fn timestamp_of_next_event_unchecked(&self) -> usize {
|
||||
self.events
|
||||
.peek()
|
||||
.unwrap_or_else(|| std::hint::unreachable_unchecked())
|
||||
.time
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -107,7 +107,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
// normal_panic(panic_info);
|
||||
// }));
|
||||
|
||||
|
||||
if opts.skip_bios {
|
||||
println!("Skipping bios animation..");
|
||||
gba.skip_bios();
|
||||
|
|
Reference in a new issue