0xBAAAAAAD
address
Not all the peripheral memory can be accessed. Look at this program.
pub fn main() -> ! {
unsafe {
ptr::read_volatile(0x4800_1800 as *const u32);
}
loop {}
}
This address is close to the GPIOE_BSRR
address we used before but this
address is "invalid". Invalid in the sense that there's no register at this
address.
Now, let's try it. Make sure you have itmdump
running.
After executing the read_volatile
statement, you should see this in
itmdump
's console:
# itmdump's console
EXCEPTION HardFault @ PC=0x0800022a
We tried to do an invalid operation, reading memory that doesn't exist, so the processor raised an exception, a hardware exception.
In most cases, exceptions are raised when the processor attempts to perform an invalid operation. Exceptions break the normal flow of a program and force the processor to execute an exception handler, which is just a function/subroutine.
There are different kind of exceptions. Each kind of exception is raised by different conditions and each one is handled by a different exception handler.
The pg
crate provides a catch-all exception handler and that's what the
processor executed upon encountering the "invalid memory address" exception.
That handler is also what caused the EXCEPTION
line to be printed to the ITM.
This EXCEPTION
line provides information about the exception. It tells us its
kind: HardFault
and which instruction caused the exception: the one at address
0x0800022a
.
The exception handler also triggered a breakpoint (via bkpt!()
) so the
debugger should have halted your program while it was executing the exception
handler.
Let's disassemble the program around the bad instruction.
(gdb) disassemble /m 0x0800022a
Dump of assembler code for function core::ptr::read_volatile<u32>:
213 pub unsafe fn read_volatile<T>(src: *const T) -> T {
0x0800021c <+0>: sub sp, #20
0x0800021e <+2>: mov r1, r0
0x08000220 <+4>: str r0, [sp, #16]
0x08000222 <+6>: str r1, [sp, #4]
0x08000224 <+8>: b.n 0x8000226 <core::ptr::read_volatile<u32>+10>
0x08000226 <+10>: ldr r0, [sp, #16]
0x08000228 <+12>: str r0, [sp, #12]
0x0800022a <+14>: ldr r0, [r0, #0] <--
0x0800022c <+16>: str r0, [sp, #8]
0x08000232 <+22>: ldr r0, [sp, #0]
0x08000234 <+24>: add sp, #20
0x08000236 <+26>: bx lr
214 intrinsics::volatile_load(src)
0x0800022e <+18>: str r0, [sp, #0]
0x08000230 <+20>: b.n 0x8000232 <core::ptr::read_volatile<u32>+22>
The exception was caused by a ldr
instruction, a read instruction. The
instruction tried to read the memory at the address indicated by the r0
register. BTW, r0
is a CPU (processor) register not a microcontroller
register.
Wouldn't it be nice if we could check what was the value of the r0
register
right at the instant at which the exception was raised? Well, we can!
If you looked carefully at the GDB output right when the exception was hit, you probably saw this:
Program received signal SIGTRAP, Trace/breakpoint trap.
f3::exception::default_handler (sf=0x20009fa0) at $F3/src/exception.rs:82
The exception handler we are in right now was called with an argument. Let's inspect that argument:
(gdb) p sf
$5 = (cortex_m::StackFrame *) 0x20009fa8
(gdb) p/x *sf
$4 = cortex_m::StackFrame {
r0: 0x48001800,
r1: 0x48001800,
r2: 0xd,
r3: 0x40013800,
r12: 0x2,
lr: 0x8000217,
pc: 0x80001f4,
xpsr: 0x41000200
}
This StackFrame
struct contains the state of your program right before the
exception was hit. There's an r0
field in it. That's the value of r0
right
before the exception was raised. It contains the value 0x4800_1800
which is
the invalid address we fed to the read_volatile
function.