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:
- Examine
LR
to find out which stack was in use
- Load the appropriate stack pointer into a free register (
r0-r3
are all available because they're pushed on entry to the handler)
- Read the word 24 bytes above this stack pointer to find the return address for 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.
Code
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.