Here is what I want to achieve: Whenever I receive a hard fault or watchdog interrupt, I will save the previous instruction's address to some RAM location that will survive a reset.
Kinetis M64 watchdog gives me 256 CPU cycles before it issues the reset, which should be enough time to save a few things. The question is where do I find these addresses? When an IRQ happens, LR holds an exception value instead of the actual return address.
I want to do this without an SWD probe attached, so the device can self-report whenever something goes wrong.
The best you can do, I think, is to find the address of the instruction that the handler would return to if it was allowed to return. This will usually be the instruction after the one that caused the fault, though this is not guaranteed (for example if the fault was caused by a branch instruction).
On entry to the handler the link register contains a code which tells you, among other things, which stack was in use when the exception occurred. See for example here for the Cortex-M4.
Just before branching to the exception handler, the CPU will push r0-r3
, r12
, LR
(r14
), PC
(r15
) and xPSR
to the active stack. If your device has a FPU and it's enabled, floating-point context may also be pushed, or space left for it. The ARM stack is full descending, and registers are stored in ascending order of register number in ascending memory addresses, so on entry to the exception handler whichever stack pointer was in use will be pointing to the stacked value of r0
; it stands to reason therefore that 6 words (24 bytes) above that will be the stacked value of PC
, which is the return address for the exception handler.
So the process for finding the instruction after the one that caused the fault, assuming it wasn't caused by a branch, is:
LR
to find out which stack was in user0-r3
are all available because they're pushed on entry to the handler)Whether the instruction that caused the fault is located 2 or 4 bytes before this return address depends on the instruction of course, the Thumb-2 instruction set is mixed 16- and 32-bit. And of course it might be located somewhere else entirely!
Bear in mind that if the MSP
was in use prior to the fault, then the handler will be using the same stack, and all of this will work only if nothing has been pushed to the stack in the handler function's prologue. The easiest thing to do may be to write the handler in assembly language. It can always call a C function after it's finished messing with stacks to complete whatever termination process you have in mind.
One last thing, it's probably worth also saving the stacked value of LR
. If the stacked value of PC
tells you nothing of use (for example because it's zero, the code having attempted to branch to an invalid address), then the stacked value of LR
will at least tell you where the last BL
instruction was encountered, and if you're lucky this will be the branch that caused the fault. Even if you're not that lucky it may help you to narrow down your search.
Here's some (untested) code that might do what you want. It's written in ARMASM syntax, so you'll need to change the odd thing if you're using a different toolchain:
IMPORT cHandler
TST lr, 0x4 ; Is bit 2 of LR clear?
ITE eq
MRSEQ r3, MSP ; If so, MSP was in use
MRSNE r3, PSP ; Otherwise, PSP was in use
LDR r0, [r3, #24] ; Load the stacked PC into r0
LDR r1, [r3, #20] ; Load the stacked LR into r1
B cHandler ; Tail-call a C function to finish the job
If the C function cHandler
has the prototype
void cHandler(void * PC, void * LR);
then the last line of the assembly language handler above will call this function, passing the recovered stacked PC as the first argument and the recovered stacked LR as the second.