Difference between revisions of "Stack Overflow Prevention"

From SEGGER Wiki
Jump to: navigation, search
(Compiler generated code)
(Generated code examples)
Line 39: Line 39:
 
push {lr}
 
push {lr}
 
movs r3, #12
 
movs r3, #12
bl __SeggerChkStkR3
+
bl __SEGGER_STOP_GROW_R3
 
and r12, r0, #7
 
and r12, r0, #7
 
...
 
...
Line 57: Line 57:
 
push {r4, r5, r6, r7, lr}
 
push {r4, r5, r6, r7, lr}
 
movs r4, #64
 
movs r4, #64
bl __SeggerChkStkR4
+
bl __SEGGER_STOP_GROW_R4
 
mov lr, r2
 
mov lr, r2
 
...
 
...
Line 73: Line 73:
 
SEGGER_CRC_CalcBitByBit:
 
SEGGER_CRC_CalcBitByBit:
 
push {r4, r5, lr}
 
push {r4, r5, lr}
bl __SeggerChkStk0
+
bl __SEGGER_STOP_GROW_0
 
cbz r1, .LBB0_4
 
cbz r1, .LBB0_4
 
...
 
...

Revision as of 13:51, 31 May 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:

__SeggerChkStkR3:
        mrs     r12, IPSR
        lsls    r12, #23
        ite     eq
        ldreq   r12, =StackLimitUser
        ldrne   r12, =StackLimitSys
        sub     r3, sp, r3
        ldr     r12, [r12]
        cmp     r3, r12
        itt     hs
        movhs   sp, r3
        bxhs    lr
        b       StackOverflow

__SeggerChkStkR4:
        mrs     r12, IPSR
        lsls    r12, #23
        ite     eq
        ldreq   r12, =StackLimitUser
        ldrne   r12, =StackLimitSys
        sub     r4, sp, r4
        ldr     r12, [r12]
        cmp     r4, r12
        itt     hs
        movhs   sp, r4
        bxhs    lr
        b       StackOverflow

__SeggerChkStk0:
        mrs     r12, IPSR
        lsls    r12, #23
        ite     eq
        ldreq   r12, =StackLimitUser
        ldrne   r12, =StackLimitSys
        ldr     r12, [r12]
        cmp     sp, r12
        it      hs
        bxhs    lr
        b       StackOverflow

Stack limit

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