use armv4t_emu::{reg, Memory}; use gdbstub::common::Tid; use gdbstub::target; use gdbstub::target::ext::base::multithread::{ GdbInterrupt, MultiThreadOps, ResumeAction, ThreadStopReason, }; use gdbstub::target::ext::breakpoints::WatchKind; use gdbstub::target::{Target, TargetError, TargetResult}; use crate::emu::{CpuId, Emu, Event}; fn event_to_stopreason(e: Event, id: CpuId) -> ThreadStopReason { let tid = cpuid_to_tid(id); match e { Event::Halted => ThreadStopReason::Terminated(19), // SIGSTOP Event::Break => ThreadStopReason::SwBreak(tid), Event::WatchWrite(addr) => ThreadStopReason::Watch { tid, kind: WatchKind::Write, addr, }, Event::WatchRead(addr) => ThreadStopReason::Watch { tid, kind: WatchKind::Read, addr, }, } } fn cpuid_to_tid(id: CpuId) -> Tid { match id { CpuId::Cpu => Tid::new(1).unwrap(), CpuId::Cop => Tid::new(2).unwrap(), } } fn tid_to_cpuid(tid: Tid) -> Result { match tid.get() { 1 => Ok(CpuId::Cpu), 2 => Ok(CpuId::Cop), _ => Err("specified invalid core"), } } impl Target for Emu { type Arch = gdbstub_arch::arm::Armv4t; type Error = &'static str; #[inline(always)] fn base_ops(&mut self) -> target::ext::base::BaseOps { target::ext::base::BaseOps::MultiThread(self) } #[inline(always)] fn breakpoints(&mut self) -> Option> { Some(self) } } impl MultiThreadOps for Emu { fn resume( &mut self, default_resume_action: ResumeAction, gdb_interrupt: GdbInterrupt<'_>, ) -> Result, Self::Error> { // In general, the behavior of multi-threaded systems during debugging is // determined by the system scheduler. On certain systems, this behavior can be // configured using the GDB command `set scheduler-locking _mode_`, but at the // moment, `gdbstub` doesn't plumb-through that configuration command. let default_resume_action_is_step = match default_resume_action { ResumeAction::Step => true, ResumeAction::Continue => false, _ => return Err("no support for resuming with signal"), }; match self .resume_action_is_step .unwrap_or(default_resume_action_is_step) { true => match self.step() { Some((event, id)) => Ok(event_to_stopreason(event, id)), None => Ok(ThreadStopReason::DoneStep), }, false => { let mut gdb_interrupt = gdb_interrupt.no_async(); let mut cycles: usize = 0; loop { // check for GDB interrupt every 1024 instructions if cycles % 1024 == 0 && gdb_interrupt.pending() { return Ok(ThreadStopReason::GdbInterrupt); } cycles += 1; if let Some((event, id)) = self.step() { return Ok(event_to_stopreason(event, id)); }; } } } } // FIXME: properly handle multiple actions fn clear_resume_actions(&mut self) -> Result<(), Self::Error> { self.resume_action_is_step = None; Ok(()) } // FIXME: properly handle multiple actions fn set_resume_action(&mut self, _tid: Tid, action: ResumeAction) -> Result<(), Self::Error> { // in this emulator, each core runs in lock-step, so we don't actually care // about the specific tid. In real integrations, you very much should! if self.resume_action_is_step.is_some() { return Ok(()); } self.resume_action_is_step = match action { ResumeAction::Step => Some(true), ResumeAction::Continue => Some(false), _ => return Err("no support for resuming with signal"), }; Ok(()) } fn read_registers( &mut self, regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, tid: Tid, ) -> TargetResult<(), Self> { let cpu = match tid_to_cpuid(tid).map_err(TargetError::Fatal)? { CpuId::Cpu => &mut self.cpu, CpuId::Cop => &mut self.cop, }; let mode = cpu.mode(); for i in 0..13 { regs.r[i] = cpu.reg_get(mode, i as u8); } regs.sp = cpu.reg_get(mode, reg::SP); regs.lr = cpu.reg_get(mode, reg::LR); regs.pc = cpu.reg_get(mode, reg::PC); regs.cpsr = cpu.reg_get(mode, reg::CPSR); Ok(()) } fn write_registers( &mut self, regs: &gdbstub_arch::arm::reg::ArmCoreRegs, tid: Tid, ) -> TargetResult<(), Self> { let cpu = match tid_to_cpuid(tid).map_err(TargetError::Fatal)? { CpuId::Cpu => &mut self.cpu, CpuId::Cop => &mut self.cop, }; let mode = cpu.mode(); for i in 0..13 { cpu.reg_set(mode, i, regs.r[i as usize]); } cpu.reg_set(mode, reg::SP, regs.sp); cpu.reg_set(mode, reg::LR, regs.lr); cpu.reg_set(mode, reg::PC, regs.pc); cpu.reg_set(mode, reg::CPSR, regs.cpsr); Ok(()) } fn read_addrs( &mut self, start_addr: u32, data: &mut [u8], _tid: Tid, // same address space for each core ) -> 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], _tid: Tid, // same address space for each core ) -> TargetResult<(), Self> { for (addr, val) in (start_addr..).zip(data.iter().copied()) { self.mem.w8(addr, val) } Ok(()) } fn list_active_threads( &mut self, register_thread: &mut dyn FnMut(Tid), ) -> Result<(), Self::Error> { register_thread(cpuid_to_tid(CpuId::Cpu)); register_thread(cpuid_to_tid(CpuId::Cop)); Ok(()) } } impl target::ext::breakpoints::Breakpoints for Emu { fn sw_breakpoint(&mut self) -> Option> { Some(self) } fn hw_watchpoint(&mut self) -> Option> { Some(self) } } impl target::ext::breakpoints::SwBreakpoint for Emu { fn add_sw_breakpoint( &mut self, addr: u32, _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { self.breakpoints.push(addr); Ok(true) } fn remove_sw_breakpoint( &mut self, addr: u32, _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { match self.breakpoints.iter().position(|x| *x == addr) { None => return Ok(false), Some(pos) => self.breakpoints.remove(pos), }; Ok(true) } } impl target::ext::breakpoints::HwWatchpoint for Emu { fn add_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult { self.watchpoints.push(addr); let entry = self.watchpoint_kind.entry(addr).or_insert((false, false)); match kind { WatchKind::Write => entry.1 = true, WatchKind::Read => entry.0 = true, WatchKind::ReadWrite => entry.0 = true, // arbitrary }; Ok(true) } fn remove_hw_watchpoint(&mut self, addr: u32, kind: WatchKind) -> TargetResult { let entry = self.watchpoint_kind.entry(addr).or_insert((false, false)); match kind { WatchKind::Write => entry.1 = false, WatchKind::Read => entry.0 = false, WatchKind::ReadWrite => entry.0 = false, // arbitrary }; if !self.watchpoint_kind.contains_key(&addr) { let pos = match self.watchpoints.iter().position(|x| *x == addr) { None => return Ok(false), Some(pos) => pos, }; self.watchpoints.remove(pos); } Ok(true) } }