Difference between revisions of "Stack Overflow Prevention"

From SEGGER Wiki
Jump to: navigation, search
(The stack check functions)
(The stack check functions)
Line 88: Line 88:
 
<source lang="asm">
 
<source lang="asm">
 
__SEGGER_STOP_GROW_R3:
 
__SEGGER_STOP_GROW_R3:
mrs r12, IPSR
+
mrs r12, CONTROL
lsls r12, #23
+
lsls r12, #30
ite eq
+
sub r12, sp, r3
ldreq r12, =StackLimitUser
+
ite mi
ldrne r12, =StackLimitSys
+
ldrmi r3, =__SEGGER_STOP_LIMIT_PSP
sub r3, sp, r3
+
ldrpl r3, =__SEGGER_STOP_LIMIT_MSP
ldr r12, [r12]
+
ldr r3, [r3]
cmp r3, r12
+
cmp r12, r3
itt hs
+
blt .L_R3_2
  +
.L_R3_1:
movhs sp, r3
 
bxhs lr
+
mov sp, r12
b StackOverflow
+
bx lr
  +
.L_R3_2:
  +
cmp r3, #0
  +
beq .L_R3_1
  +
b __SEGGER_STOP_ON_ERROR
   
 
__SEGGER_STOP_GROW_R4:
 
__SEGGER_STOP_GROW_R4:
mrs r12, IPSR
+
mrs r12, CONTROL
lsls r12, #23
+
lsls r12, #30
ite eq
+
sub r12, sp, r4
ldreq r12, =StackLimitUser
+
ite mi
ldrne r12, =StackLimitSys
+
ldrmi r4, =__SEGGER_STOP_LIMIT_PSP
sub r4, sp, r4
+
ldrpl r4, =__SEGGER_STOP_LIMIT_MSP
ldr r12, [r12]
+
ldr r4, [r4]
cmp r4, r12
+
cmp r12, r4
itt hs
+
blt .L_R4_2
  +
.L_R4_1:
movhs sp, r4
 
bxhs lr
+
mov sp, r12
b StackOverflow
+
bx lr
  +
.L_R4_2:
  +
cmp r4, #0
  +
beq .L_R4_1
  +
b __SEGGER_STOP_ON_ERROR
   
 
__SEGGER_STOP_GROW_0:
 
__SEGGER_STOP_GROW_0:
mrs r12, IPSR
+
mrs r12, CONTROL
lsls r12, #23
+
lsls r12, #30
ite eq
+
ite mi
ldreq r12, =StackLimitUser
+
ldrmi r12, =__SEGGER_STOP_LIMIT_PSP
ldrne r12, =StackLimitSys
+
ldrpl r12, =__SEGGER_STOP_LIMIT_MSP
 
ldr r12, [r12]
 
ldr r12, [r12]
 
cmp sp, r12
 
cmp sp, r12
it hs
+
blt .L_0_2
  +
.L_0_1:
bxhs lr
 
b StackOverflow
+
bx lr
  +
.L_0_2:
  +
cmp r12, #0
  +
beq .L_0_1
  +
b __SEGGER_STOP_ON_ERROR
 
</source>
 
</source>
   

Revision as of 13:17, 2 June 2023

Introduction

In order to detect stack overflows in a running embedded system the Segger compiler is able to generate code to check for stack overflows in every function. This can be activated using the command line switch -mstack-overflow-check (cc1 interface). For secure systems, a stack overflow must be detected before any memory is destroyed by the overflowing stack, therefore a check is required on any changed of the stack pointer and before any large stack growth.

Compiler generated code

If stack overflow checking is activated in the compiler different code is the generated as follows:

  1. Functions that don't touch the stack are not changed.
  2. In functions that uses a local stack frame but don't use R3 as a function parameter: The set up of the stack frame (usually a sub sp, #size) is replaced by loading the required size into register R3, then calling the function __SEGGER_STOP_GROW_R3().
  3. In functions that uses a local stack frame and uses R3 as a function parameter: Same as 2., but using register R4 instead of R3 and calling the function __SEGGER_STOP_GROW_R4(). This means, that R4 must be pushed at function entry.
  4. In functions that don't use a local stack frame but need to save register on the stack, the function __SEGGER_STOP_GROW_0() (with no parameter) is called after pushing the registers.
  5. Functions that need dynamic stack allocation (for example if alloca() or variable sized arrays are used) will also call __SEGGER_STOP_GROW_R3(). Because this may happen in the middle of a function, the register allocator will be instructed to make sure, R3 can be used as argument.

The called functions then can check for a stack overflow using a stack limit that is stored in a global variable. These functions are called after registers to be saved are pushed on the stack. Therefore the stack limit must be calculated such that there is always space for:

  • All general purpose registers that may be pushed at function entry (R0 - R11, LR). There are some optimization that leads to pushing R0 - R3.
  • All callee saved floating point / vector registers (D8 - D15)
  • Registers save on interrupt entry (8 words)
  • 3 (spare) words for alignment and emergency spill splots

Stack checking code generation can be disabled for single functions with __attribute__((no_stack_overflow_check)), which may be required for internal RTOS functions.

Generated code examples

Original code:

ReadReqEP:
    push    {lr}
    sub     sp, #12
    and     r12, r0, #7
    ...

is changed to

ReadReqEP:
    push    {lr}
    movs    r3, #12
    bl      __SEGGER_STOP_GROW_R3
    and     r12, r0, #7
    ...

Original code:

 
SEGGER_CRC_Calc:                     
    push    {r4, r5, r6, r7, lr}       
    sub     sp, #64                    
    mov     lr, r2                    
    ...

is changed to

 
SEGGER_CRC_Calc:
    push    {r4, r5, r6, r7, lr}
    movs    r4, #64
    bl      __SEGGER_STOP_GROW_R4
    mov     lr, r2
    ...

Original code:

SEGGER_CRC_CalcBitByBit:
    push    {r4, r5, lr}
    cbz     r1, .LBB0_4
    ...

is changed to

SEGGER_CRC_CalcBitByBit:
    push    {r4, r5, lr}
    bl      __SEGGER_STOP_GROW_0
    cbz     r1, .LBB0_4
    ...

The stack check functions

The functions called by the compiler generated code must check the remaining stack size and must not return in case of a stack overflow. For efficiency the functions do not follow the standard calling convention. Therefore the functions must not modify any registers except R12 and the register containing the size argument (if any). The functions also must adjust the stack pointer before they return.

A sample implementation for a Cortex-M CPU with 2 stack pointers is like this:

__SEGGER_STOP_GROW_R3:
        mrs     r12, CONTROL
        lsls    r12, #30
        sub     r12, sp, r3
        ite     mi
        ldrmi   r3, =__SEGGER_STOP_LIMIT_PSP
        ldrpl   r3, =__SEGGER_STOP_LIMIT_MSP
        ldr     r3, [r3]
        cmp     r12, r3
        blt     .L_R3_2
.L_R3_1:
        mov     sp, r12
        bx      lr
.L_R3_2:
        cmp     r3, #0
        beq     .L_R3_1
        b       __SEGGER_STOP_ON_ERROR

__SEGGER_STOP_GROW_R4:
        mrs     r12, CONTROL
        lsls    r12, #30
        sub     r12, sp, r4
        ite     mi
        ldrmi   r4, =__SEGGER_STOP_LIMIT_PSP
        ldrpl   r4, =__SEGGER_STOP_LIMIT_MSP
        ldr     r4, [r4]
        cmp     r12, r4
        blt     .L_R4_2
.L_R4_1:
        mov     sp, r12
        bx      lr
.L_R4_2:
        cmp     r4, #0
        beq     .L_R4_1
        b       __SEGGER_STOP_ON_ERROR

__SEGGER_STOP_GROW_0:
        mrs     r12, CONTROL
        lsls    r12, #30
        ite     mi
        ldrmi   r12, =__SEGGER_STOP_LIMIT_PSP
        ldrpl   r12, =__SEGGER_STOP_LIMIT_MSP
        ldr     r12, [r12]
        cmp     sp, r12
        blt     .L_0_2
.L_0_1:
        bx      lr
.L_0_2:
        cmp     r12, #0
        beq     .L_0_1
        b       __SEGGER_STOP_ON_ERROR

Stack limit

The startup code or the RTOS is responsible for setting the StackLimit global variable and keeping it up to date.