blob: 0c8f6dca1b0dbe6b971b29161ac9ac23481399d9 [file] [log] [blame]
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<u32> {
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<CpuId, &'static str> {
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<Self::Arch, Self::Error> {
target::ext::base::BaseOps::MultiThread(self)
}
#[inline(always)]
fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
Some(self)
}
}
impl MultiThreadOps for Emu {
fn resume(
&mut self,
default_resume_action: ResumeAction,
gdb_interrupt: GdbInterrupt<'_>,
) -> Result<ThreadStopReason<u32>, 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<target::ext::breakpoints::SwBreakpointOps<Self>> {
Some(self)
}
fn hw_watchpoint(&mut self) -> Option<target::ext::breakpoints::HwWatchpointOps<Self>> {
Some(self)
}
}
impl target::ext::breakpoints::SwBreakpoint for Emu {
fn add_sw_breakpoint(
&mut self,
addr: u32,
_kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> {
self.breakpoints.push(addr);
Ok(true)
}
fn remove_sw_breakpoint(
&mut self,
addr: u32,
_kind: gdbstub_arch::arm::ArmBreakpointKind,
) -> TargetResult<bool, Self> {
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<bool, Self> {
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<bool, Self> {
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)
}
}