use core::convert::TryInto; use armv4t_emu::{reg, Memory}; use gdbstub::target; use gdbstub::target::ext::base::singlethread::{ GdbInterrupt, ResumeAction, SingleThreadOps, SingleThreadReverseContOps, SingleThreadReverseStepOps, StopReason, }; use gdbstub::target::ext::breakpoints::WatchKind; use gdbstub::target::{Target, TargetError, TargetResult}; use gdbstub_arch::arm::reg::id::ArmCoreRegId; use crate::emu::{Emu, Event}; // Additional GDB extensions mod breakpoints; mod extended_mode; mod monitor_cmd; mod section_offsets; mod target_description_xml_override; /// Turn a `ArmCoreRegId` into an internal register number of `armv4t_emu`. fn cpu_reg_id(id: ArmCoreRegId) -> Option { match id { ArmCoreRegId::Gpr(i) => Some(i), ArmCoreRegId::Sp => Some(reg::SP), ArmCoreRegId::Lr => Some(reg::LR), ArmCoreRegId::Pc => Some(reg::PC), ArmCoreRegId::Cpsr => Some(reg::CPSR), _ => None, } } impl Target for Emu { type Arch = gdbstub_arch::arm::Armv4t; type Error = &'static str; // --------------- IMPORTANT NOTE --------------- // Always remember to annotate IDET enable methods with `inline(always)`! // Without this annotation, LLVM might fail to dead-code-eliminate nested IDET // implementations, resulting in unnecessary binary bloat. #[inline(always)] fn base_ops(&mut self) -> target::ext::base::BaseOps { target::ext::base::BaseOps::SingleThread(self) } #[inline(always)] fn breakpoints(&mut self) -> Option> { Some(self) } #[inline(always)] fn extended_mode(&mut self) -> Option> { Some(self) } #[inline(always)] fn monitor_cmd(&mut self) -> Option> { Some(self) } #[inline(always)] fn section_offsets(&mut self) -> Option> { Some(self) } #[inline(always)] fn target_description_xml_override( &mut self, ) -> Option> { Some(self) } } impl Emu { fn inner_resume( &mut self, action: ResumeAction, mut check_gdb_interrupt: impl FnMut() -> bool, ) -> Result, &'static str> { let event = match action { ResumeAction::Step => match self.step() { Some(e) => e, None => return Ok(StopReason::DoneStep), }, ResumeAction::Continue => { let mut cycles = 0; loop { if let Some(event) = self.step() { break event; }; // check for GDB interrupt every 1024 instructions cycles += 1; if cycles % 1024 == 0 && check_gdb_interrupt() { return Ok(StopReason::GdbInterrupt); } } } _ => return Err("cannot resume with signal"), }; Ok(match event { Event::Halted => StopReason::Terminated(19), // SIGSTOP Event::Break => StopReason::SwBreak, Event::WatchWrite(addr) => StopReason::Watch { kind: WatchKind::Write, addr, }, Event::WatchRead(addr) => StopReason::Watch { kind: WatchKind::Read, addr, }, }) } } impl SingleThreadOps for Emu { fn resume( &mut self, action: ResumeAction, gdb_interrupt: GdbInterrupt<'_>, ) -> Result, Self::Error> { let mut gdb_interrupt = gdb_interrupt.no_async(); self.inner_resume(action, || gdb_interrupt.pending()) } fn read_registers( &mut self, regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, ) -> TargetResult<(), Self> { let mode = self.cpu.mode(); for i in 0..13 { regs.r[i] = self.cpu.reg_get(mode, i as u8); } regs.sp = self.cpu.reg_get(mode, reg::SP); regs.lr = self.cpu.reg_get(mode, reg::LR); regs.pc = self.cpu.reg_get(mode, reg::PC); regs.cpsr = self.cpu.reg_get(mode, reg::CPSR); Ok(()) } fn write_registers( &mut self, regs: &gdbstub_arch::arm::reg::ArmCoreRegs, ) -> TargetResult<(), Self> { let mode = self.cpu.mode(); for i in 0..13 { self.cpu.reg_set(mode, i, regs.r[i as usize]); } self.cpu.reg_set(mode, reg::SP, regs.sp); self.cpu.reg_set(mode, reg::LR, regs.lr); self.cpu.reg_set(mode, reg::PC, regs.pc); self.cpu.reg_set(mode, reg::CPSR, regs.cpsr); Ok(()) } fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> { for (addr, val) in (start_addr..).zip(data.iter_mut()) { *val = self.mem.r8(addr) } Ok(()) } fn write_addrs(&mut self, start_addr: u32, data: &[u8]) -> TargetResult<(), Self> { for (addr, val) in (start_addr..).zip(data.iter().copied()) { self.mem.w8(addr, val) } Ok(()) } #[inline(always)] fn single_register_access( &mut self, ) -> Option> { Some(self) } #[inline(always)] fn support_reverse_cont(&mut self) -> Option> { Some(self) } #[inline(always)] fn support_reverse_step(&mut self) -> Option> { Some(self) } #[inline(always)] fn support_resume_range_step( &mut self, ) -> Option> { Some(self) } } impl target::ext::base::SingleRegisterAccess<()> for Emu { fn read_register( &mut self, _tid: (), reg_id: gdbstub_arch::arm::reg::id::ArmCoreRegId, dst: &mut [u8], ) -> TargetResult<(), Self> { if let Some(i) = cpu_reg_id(reg_id) { let w = self.cpu.reg_get(self.cpu.mode(), i); dst.copy_from_slice(&w.to_le_bytes()); Ok(()) } else { Err(().into()) } } fn write_register( &mut self, _tid: (), reg_id: gdbstub_arch::arm::reg::id::ArmCoreRegId, val: &[u8], ) -> TargetResult<(), Self> { let w = u32::from_le_bytes( val.try_into() .map_err(|_| TargetError::Fatal("invalid data"))?, ); if let Some(i) = cpu_reg_id(reg_id) { self.cpu.reg_set(self.cpu.mode(), i, w); Ok(()) } else { Err(().into()) } } } impl target::ext::base::singlethread::SingleThreadReverseCont for Emu { fn reverse_cont( &mut self, gdb_interrupt: GdbInterrupt<'_>, ) -> Result, Self::Error> { // FIXME: actually implement reverse step eprintln!( "FIXME: Not actually reverse-continuing. Performing forwards continue instead..." ); self.resume(ResumeAction::Continue, gdb_interrupt) } } impl target::ext::base::singlethread::SingleThreadReverseStep for Emu { fn reverse_step( &mut self, gdb_interrupt: GdbInterrupt<'_>, ) -> Result, Self::Error> { // FIXME: actually implement reverse step eprintln!( "FIXME: Not actually reverse-stepping. Performing single forwards step instead..." ); self.resume(ResumeAction::Step, gdb_interrupt) } } impl target::ext::base::singlethread::SingleThreadRangeStepping for Emu { fn resume_range_step( &mut self, start: u32, end: u32, gdb_interrupt: GdbInterrupt<'_>, ) -> Result, Self::Error> { let mut gdb_interrupt = gdb_interrupt.no_async(); loop { match self.inner_resume(ResumeAction::Step, || gdb_interrupt.pending())? { StopReason::DoneStep => {} stop_reason => return Ok(stop_reason), } if !(start..end).contains(&self.cpu.reg_get(self.cpu.mode(), reg::PC)) { return Ok(StopReason::DoneStep); } } } }