285 lines
8.4 KiB
Rust
285 lines
8.4 KiB
Rust
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<u8> {
|
|
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<Self::Arch, Self::Error> {
|
|
target::ext::base::BaseOps::SingleThread(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn breakpoints(&mut self) -> Option<target::ext::breakpoints::BreakpointsOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn extended_mode(&mut self) -> Option<target::ext::extended_mode::ExtendedModeOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn monitor_cmd(&mut self) -> Option<target::ext::monitor_cmd::MonitorCmdOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn section_offsets(&mut self) -> Option<target::ext::section_offsets::SectionOffsetsOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn target_description_xml_override(
|
|
&mut self,
|
|
) -> Option<target::ext::target_description_xml_override::TargetDescriptionXmlOverrideOps<Self>>
|
|
{
|
|
Some(self)
|
|
}
|
|
}
|
|
|
|
impl Emu {
|
|
fn inner_resume(
|
|
&mut self,
|
|
action: ResumeAction,
|
|
mut check_gdb_interrupt: impl FnMut() -> bool,
|
|
) -> Result<StopReason<u32>, &'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<StopReason<u32>, 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<target::ext::base::SingleRegisterAccessOps<(), Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn support_reverse_cont(&mut self) -> Option<SingleThreadReverseContOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn support_reverse_step(&mut self) -> Option<SingleThreadReverseStepOps<Self>> {
|
|
Some(self)
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn support_resume_range_step(
|
|
&mut self,
|
|
) -> Option<target::ext::base::singlethread::SingleThreadRangeSteppingOps<Self>> {
|
|
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<StopReason<u32>, 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<StopReason<u32>, 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<StopReason<u32>, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|