1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
use crate::cpu::ProgramStatus;
/// All the data captured during an exception about the previous state of the program.
///
/// These fields are placed in a specific order so that `overlay.S` builds the struct properly.
#[derive(Debug, Clone, Default, PartialEq)]
#[repr(C)]
pub struct DebugEventContext {
/// The Program Status Register from before the exception.
pub cpsr: ProgramStatus,
/// The stack pointer from before the exception.
pub stack_pointer: u32,
/// The link register from before the exception.
pub link_register: u32,
/// Floating point status and control register.
pub fpscr: u32,
/// Floating point registers d0 through d31
pub vfp_registers: [u64; 32],
/// Registers r0 through r12
pub registers: [u32; 13],
/// The address at which the abort occurred.
///
/// This is calculated using the Link Register (`lr`) at abort time, which is set to this
/// address plus an offset when an exception occurs.
pub program_counter: u32,
}
impl Registers for DebugEventContext {
type ProgramCounter = u32;
fn pc(&self) -> Self::ProgramCounter {
self.program_counter
}
fn gdb_serialize(&self, mut write_byte: impl FnMut(Option<u8>)) {
let mut send = move |bytes: &[u8]| {
for &b in bytes {
write_byte(Some(b));
}
};
for r in self.registers {
send(&r.to_le_bytes());
}
send(&self.stack_pointer.to_le_bytes());
send(&self.link_register.to_le_bytes());
send(&self.program_counter.to_le_bytes());
send(&self.cpsr.raw_value().to_le_bytes());
for d in self.vfp_registers {
send(&d.to_le_bytes());
}
send(&self.fpscr.to_le_bytes());
}
fn gdb_deserialize(&mut self, mut bytes: &[u8]) -> Result<(), ()> {
fn read<const N: usize>(bytes: &mut &[u8]) -> Result<[u8; N], ()> {
let Some((left, right)) = bytes.split_at_checked(N) else {
return Err(());
};
*bytes = right;
Ok(<[u8; N]>::try_from(left).unwrap())
}
for r in &mut self.registers {
*r = u32::from_le_bytes(read(&mut bytes)?);
}
self.stack_pointer = u32::from_le_bytes(read(&mut bytes)?);
self.link_register = u32::from_le_bytes(read(&mut bytes)?);
self.program_counter = u32::from_le_bytes(read(&mut bytes)?);
self.cpsr = ProgramStatus::new_with_raw_value(u32::from_le_bytes(read(&mut bytes)?));
for d in &mut self.vfp_registers {
*d = u64::from_le_bytes(read(&mut bytes)?);
}
self.fpscr = u32::from_le_bytes(read(&mut bytes)?);
Ok(())
}
}
#[cfg(target_arch = "arm")]
pub(crate) mod arm {
use core::{
arch::asm,
array,
ffi::c_void,
mem::MaybeUninit,
sync::atomic::{AtomicBool, AtomicU32, Ordering},
};
use aarch32_cpu::asm::dsb;
use crate::{
DEBUGGER,
cpu::{exception::VectorBaseAddressRegister, instruction::Instruction},
exceptions::DebugEventContext,
};
core::arch::global_asm!(
#[cfg(feature = "freertos")]
".set FREERTOS, 1",
#[cfg(feature = "pros")]
".set PROS, 1",
include_str!("./overlay.s"),
options(raw),
);
const ABORT_STACK_SIZE: usize = 0x8000; // 32KB
#[repr(C, align(8))]
struct AbortStack(MaybeUninit<[u8; const { ABORT_STACK_SIZE }]>);
static mut ABORT_STACK: AbortStack = AbortStack(MaybeUninit::uninit());
/// Handles a debug event.
///
/// This function is called from the abort handler routines in `overlay.S` once they've been
/// activated with [`install_vectors`].
///
/// # Safety
///
/// Must be passed a debug event context that's valid for reads and writes and lives for the
/// duration of this function call.
///
/// This function must be called with interrupts disabled. The implementation may re-enable them
/// during the function call, but they will be disabled again before returning.
///
/// The callee must always resume the system scheduler after calling this function.
#[unsafe(export_name = "v5gdb_handle_debug_event")]
pub unsafe extern "aapcs" fn handle_debug_event(ctx: *mut DebugEventContext) -> bool {
unsafe { DEBUGGER.get().unwrap().handle_debug_event(&mut *ctx) }
}
/// Minimum interval, in milliseconds, between calls to `Debugger::poll`.
const IRQ_POLL_INTERVAL_MS: u32 = 10;
/// The system time (ms) at which the IRQ hook last ran `Debugger::poll`.
static LAST_POLL_TIME_MS: AtomicU32 = AtomicU32::new(0);
/// IRQ handler callback.
///
/// This is called from `v5gdb_irq_handler` at the beginning of every IRQ exception and is
/// responsible for periodically invoking [`Debugger::poll`](crate::Debugger::poll).
///
/// # Notes
///
/// This runs in interrupt context on VEXos's 8 KiB IRQ-mode stack, so it must remain fairly
/// lightweight with no allocation or blocking or calls to non-thread-safe functions.
#[unsafe(export_name = "v5gdb_irq_poll")]
pub extern "aapcs" fn irq_poll() {
let now = unsafe { vex_sdk::vexSystemTimeGet() };
let last = LAST_POLL_TIME_MS.load(Ordering::Relaxed);
// `wrapping_sub` keeps this correct across a u32 millisecond wraparound.
if now.wrapping_sub(last) < IRQ_POLL_INTERVAL_MS {
return;
}
LAST_POLL_TIME_MS.store(now, Ordering::Relaxed);
if let Some(debugger) = DEBUGGER.get() {
debugger.poll();
}
}
static ORIGINAL_VECTOR_ADDRESSES_SET: AtomicBool = AtomicBool::new(false);
/// Registers a set of custom CPU exception handlers that can handle debug events.
pub fn install_vectors() {
unsafe extern "C" {
#[link_name = "v5gdb_debugger_vector_table"]
static debugger_vector_table: c_void;
#[link_name = "v5gdb_original_vector_addresses"]
static mut original_vector_addresses: [u32; 8];
}
if !ORIGINAL_VECTOR_ADDRESSES_SET.swap(true, Ordering::Relaxed) {
let old_vbar = VectorBaseAddressRegister::read();
critical_section::with(|_| unsafe {
// No exceptions should be allowed to occur while updating the vector table,
// since the vector table is responsible for handling those
// exceptions.
asm!("cpsid f", options(nostack, nomem, preserves_flags));
// The default stack that VEXos gives us in abort mode is only 1kb, which is
// extremely inadequate for what we're doing in the debug event handler, so we
// need to load our own stack region.
//
// In an effort to avoid requiring linkerscript modification, we're storing this
// stack as an uninitialized static global rather than giving it it's own
// explicit linker section.
asm!(
// abort mode
"cps #0b10111",
"ldr sp, ={abort_stack}+{stack_size}",
// back to sys mode
"cps #0b11111",
abort_stack = sym ABORT_STACK,
stack_size = const ABORT_STACK_SIZE,
options(nostack, preserves_flags)
);
original_vector_addresses =
array::from_fn(|i| old_vbar.ptr().byte_add(i * size_of::<u32>()) as u32);
dsb();
asm!("cpsie f", options(nostack, nomem, preserves_flags));
});
}
unsafe {
let overlay_table_ptr = &raw const debugger_vector_table;
VectorBaseAddressRegister::new(overlay_table_ptr.cast()).write();
}
}
impl DebugEventContext {
/// Read the ARM instruction which the exception would return to.
///
/// # Safety
///
/// The caller must ensure the return address is valid for volatile reads. This might not be
/// the case if, for example, the exception was a prefetch abort caused by the instruction
/// being inaccessible.
#[must_use]
pub unsafe fn read_instr(&self) -> Instruction {
let ptr = self.program_counter as *mut u32;
unsafe { Instruction::read(ptr, self.cpsr.thumb()) }
}
/// Load the address or instruction which the faulting instruction attempted to operate on.
///
/// # Safety
///
/// This function accesses CPU state that's set post-exception. The caller must ensure that
/// this state has not been invalidated.
#[must_use]
pub unsafe fn target(&self) -> usize {
let target: usize;
unsafe {
core::arch::asm!(
"mrc p15, 0, {ifar}, c6, c0, 1",
ifar = out(reg) target,
options(nomem, nostack, preserves_flags)
);
}
target
}
}
}
#[cfg(target_arch = "arm")]
pub use arm::*;
use gdbstub::arch::Registers;