Return Address from Interrupt in STM32F

138 Views Asked by At

I want to know the return address from Interrupt in STM32F, especially arm processor.

In normal function, return address is saved in Link Register, but in interrupt, I found that the value of Link Register is 0xFFFFFF9.

So I found the Ref. manual of armv6, but I still don't know where the return address is saved.

enter image description here enter image description here

I attached part of the Ref. Manual.

I guessed that ReturnAddress(ExceptionType); is the place where the return address is saved.

So I checked [SP, #24] but the value was not the return address.

2

There are 2 best solutions below

0
old_timer On BEST ANSWER

It is all documented, all you have to do is try it.

.thumb_func
.global _start
_start:
stacktop: 
.word 0x20001000
.word reset 
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word svcall
.word hang
.word hang
.word hang
.word hang


.thumb_func
.globl TEST
TEST:
    mov r0,#0
    mov r1,#1
    mov r2,#2
    mov r3,#3
    svc 0
    bx lr

void svcall ( void )
{
...
}

dumping the stack

from arm docs

MemA[frameptr,4]     = R[0];
MemA[frameptr+0x4,4] = R[1];
MemA[frameptr+0x8,4] = R[2];
MemA[frameptr+0xC,4] = R[3];
MemA[frameptr+0x10,4] = R[12];
MemA[frameptr+0x14,4] = LR;
MemA[frameptr+0x18,4] = ReturnAddress();
MemA[frameptr+0x1C,4] = (xPSR<31:10>:frameptralign:xPSR<8:0>);

compiled svcall has a stack frame

080001e8 <svcall>:
 80001e8:   b570        push    {r4, r5, r6, lr}

covering the first part of the stack

00000003 
00000000 
00000000 
FFFFFFF9  lr

then the exception portion of the stack that the logic uses.

00000000 r0
00000001 r1
00000002 r2
00000003 r3
00000000 r12
08000267 lr
0800005E return address
21000000 psr


08000054 <TEST>:
 8000054:   2000        movs    r0, #0
 8000056:   2101        movs    r1, #1
 8000058:   2202        movs    r2, #2
 800005a:   2303        movs    r3, #3
 800005c:   df00        svc 0
 800005e:   4770        bx  lr

and that is the expected return address.

Not sure why you would care what the return address is. Note that that is not the address you would branch nor return to (as documented) it would be 800005f in this case, of course. But you would also have to restore the registers manually (r0,1,2,...) if you want to return to that address.

1
Ilya On

Interrupt return works the following way:

In the ISR, you branch to value loaded into link register. That link register does NOT contain the return address like it does in thread mode. In handler mode (ISR), it contains a magic value, that looks like 0xFFFFFFFx. That last hex digit defines the exact behavior.

Depending on magic value, your CPU takes either MSP register or PSP register, and uses this value as a stack pointer (which both of them are; all ISR always use MSP, threads can use either, MSP by default). The CPU treats that pointer as a pointer to thread program stack. It immediately reads the last values on the stack into CPU registers (not all, only R0-R3, R12, LR, PC, xPSR; depending on magic value, there can also be FPU registers there), removes them from stack. Those registers were automatically put on stack there when interrupt was triggered.

I will try to illustrate. Imagine we have a thread

Thread program stack memory (Full descending stack)

0x3000 data   
0x2FFC data  
0x2FF8 data  
0x2FF4 data <--- thread stack pointer, MSP or PSP (R13)

Interrupt happens

0x3000 data  
0x2FFC data  
0x2FF8 data  
0x2FF4 data  
0x2FF0 xPSR  
0x2FEC PC  
0x2FE8 LR  
0x2FE4 R12  
0x2FE0 R3  
0x2FDC R2  
0x2FD8 R1  
0x2FD4 R0  <--- Thread Stack Pointer when interrupt happened (R13)

Interrupt finishes running
Interrupt executes BX LR instruction, where LR contains 0xFFFFFFFx. It means the CPU uses Thread Stack Pointer and loads data from where it points to into CPU registers:

Thread resumes operation

0x3000 data  
0x2FFC data  
0x2FF8 data  
0x2FF4 data  <--- Thread Stack Pointer (R13)
0x2FF0 xPSR  //already loaded into CPU xPSR, Free memory
0x2FEC PC    //already loaded into CPU PC (R15), Free memory  
0x2FE8 LR    //already loaded into CPU LR (R14), Free memory  
0x2FE4 R12   //already loaded into CPU R12, Free memory  
0x2FE0 R3    //already loaded into CPU R3, Free memory  
0x2FDC R2    //already loaded into CPU R2, Free memory  
0x2FD8 R1    //already loaded into CPU R1, Free memory  
0x2FD4 R0    //already loaded into CPU R0, Free memory  

So if you want to return to some function in thread mode, for example, if you want to exit your RTOS kernel into a thread, all you have to do is to create a "fake" saved registers frame, and then return to it.

Example of jumping to arbitrary function from interrupt handler:

0x2FF4 <-- Desired thread stack start
0x2FF0 xPSR  //fake xPSR (all zeroes except 1 bit set that says it's thumb instruction set)  
0x2FEC PC    //function pointer, least significant bit always set to 1 (because thumb instruction set) 
0x2FE8 LR    //return address of that function  
0x2FE4 R12   //value you wanna have in R12 when function starts  
0x2FE0 R3    //value you wanna have in R3 when function starts, function argument 3  
0x2FDC R2    //value you wanna have in R2 when function starts, function argument 2  
0x2FD8 R1    //value you wanna have in R1 when function starts, function argument 1  
0x2FD4 R0    //value you wanna have in R0 when function starts, function argument 0  

In the interrupt handler, you do:

Set PSP to 0x2FD4 (if there are multiple processes/tasks, they all use the same PSP)
Set LR to 0xFFFFFFFx
BX LR

Which is also how you pass parameters to a function you return to. You just put them into stack into R0-R3 (remember it's 32-bit registers). When ISR exited, the memory will look like:

0x2FF4 <-- Stack pointer
0x2FF0 Free memory
0x2FEC Free memory   
0x2FE8 Free memory   
0x2FE4 Free memory 
0x2FE0 Free memory    
0x2FDC Free memory   
0x2FD8 Free memory    
0x2FD4 Free memory  

And you have also passed 4 function parameters to that function through what was in R0-R3 slots on stack.

If there is FPU context that needs to be preserved, it will be partially saved on stack too, in higher memory addresses than the CPU registers, so the layout of R0, R1...xPSR relatively to stack pointer will always stay the same, with FPU or not. Return to FPU context will simply require a different magic value in 0xFFFFFFFx.

This information is available in ARMv7-M Architecture Reference Manual, available partially online, fully as PDF, on ARM website

EDIT: desired stack start should be 8-byte aligned, I had 4-byte aligned here in the example. It will most likely work correctly, but the official recommendation is to always keep stack pointer 8-byte aligned (if it's not, then during interrupt stacking and unstacking, one extra dummy 32-bit word is pushed and popped into/from higher memory address than xPSR)