Stack Overflow Prevention

From SEGGER Wiki
Revision as of 18:28, 12 June 2023 by Johannes (talk | contribs)
Jump to: navigation, search

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
    ...

Stack Check and Error Handling

The following stack check functions must be implemented when Stack Overflow Prevention is enabled. In Embedded Studio, they are automatically added with the standard library.

  • __SEGGER_STOP_GROW_R3
  • __SEGGER_STOP_GROW_R4
  • __SEGGER_STOP_GROW_0

On stack overflow the stack check functions should jump to a user-provided error handling callback __SEGGER_STOP_X_OnError().

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:

START_FUNC __SEGGER_STOP_GROW_R3
        //
        // Check which stack pointer is currently used:
        // bit 1 of CONTROL register 0 -> MSP, 1 -> PSP
        //
        mrs     r12, CONTROL
        lsls    r12, #30
        //
        // Calculate the new stack pointer position
        //
        sub     r12, sp, r3
        //
        // Load corresponding stack limit
        //
        ite     mi
        ldrmi   r3, =__SEGGER_STOP_Limit_PSP
        ldrpl   r3, =__SEGGER_STOP_Limit_MSP
        ldr     r3, [r3]
        //
        // Compare with new stack value
        //
        cmp     r12, r3
        blt     L(MightError)
L(Done):
        //
        // Check passed.
        // Adjust stack and return
        //
        mov     sp, r12
        bx      lr
L(MightError):
        //
        // A stack limit value of 0 means: Check is disabled
        //
        cmp     r3, #0
        beq     L(Done)
        b       __SEGGER_STOP_X_OnError
END_FUNC __SEGGER_STOP_GROW_R3

START_FUNC __SEGGER_STOP_GROW_R4
        //
        // Check which stack pointer is currently used:
        // bit 1 of CONTROL register 0 -> MSP, 1 -> PSP
        //
        mrs     r12, CONTROL
        lsls    r12, #30
        //
        // Calculate the new stack pointer position
        //
        sub     r12, sp, r4
        //
        // Load corresponding stack limit
        //
        ite     mi
        ldrmi   r4, =__SEGGER_STOP_Limit_PSP
        ldrpl   r4, =__SEGGER_STOP_Limit_MSP
        ldr     r4, [r4]
        //
        // Compare with new stack value
        //
        cmp     r12, r4
        blt     L(MightError)
L(Done):
        //
        // Check passed.
        // Adjust stack and return
        //
        mov     sp, r12
        bx      lr
L(MightError):
        //
        // A stack limit value of 0 means: Check is disabled
        //
        cmp     r4, #0
        beq     L(Done)
        //
        // Store limit value in R3
        //
        mov     r3, r4
        b       __SEGGER_STOP_X_OnError
END_FUNC __SEGGER_STOP_GROW_R4

START_FUNC __SEGGER_STOP_GROW_0
        //
        // Check which stack pointer is currently used:
        // bit 1 of CONTROL register 0 -> MSP, 1 -> PSP
        //
        mrs     r12, CONTROL
        lsls    r12, #30
        //
        // Load corresponding stack limit
        //
        ite     mi
        ldrmi   r12, =__SEGGER_STOP_Limit_PSP
        ldrpl   r12, =__SEGGER_STOP_Limit_MSP
        ldr     r12, [r12]
        //
        // Compare with current stack value
        //
        cmp     sp, r12
        blt     L(MightError)
L(Done):
        //
        // Check passed. Return.
        //
        bx      lr
L(MightError):
        //
        // A stack limit value of 0 means: Check is disabled
        //
        cmp     r12, #0
        beq     L(Done)
        //
        // Store limit value in R3
        // Store overflowed SP in R12
        //
        mov     r3, r12
        mov     r12, sp
        b       __SEGGER_STOP_X_OnError
END_FUNC __SEGGER_STOP_GROW_0

The error handling callback

To make sure the error handling callback does not use the overflowing stack, it should be implemented in pure assembly and must not be compiled with stack overflow checks.

In the default implementation, __SEGGER_STOP_X_OnError is defined as

 __attribute__((naked, no_stack_overflow_check)) void __SEGGER_STOP_X_OnError(void);

It is tail-called by the stack check functions and does not follow the regular calling conventions. The stack limit value, new stack pointer value, and caller are passed in R3, R12, and LR.

The error handling callback might reset the overflowing stack to a safe value. If it does, it may call additional functions, such as to log the error and reset the system.

extern unsigned char __STACKSIZE__[];             // Linker-generated symbol for system stack size.
extern unsigned char __stack_start__[];           // Linker-generated symbol for lower bound of system stack.
extern unsigned char __stack_end__[];             // Linker-generated symbol for lower bound of system stack.

extern unsigned char __STACKSIZE_PROCESS__[];     // Linker-generated symbol for process stack size.
extern unsigned char __stack_process_start__[];   // Linker-generated symbol for lower bound of process stack.
extern unsigned char __stack_process_end__[];     // Linker-generated symbol for upper bound of process stack.

/*********************************************************************
*
*       _HandleStackError()
*
*  Function description
*   Example error handler for stack overflow prevention.
*   Log error and halt system.
*
*  Parameters
*   SP:        New (overflown) stack pointer value.
*   Limit:     Stack pointer limit value.
*   Caller:    Caller of the stack checking function.
*   UsedStack: < 0: Process Stack. >= 0: Main Stack.
*
*  Additional information
*   When __SEGGER_STOP_RESET_STACK is set, the error handler runs
*   on a freshly initialized stack and may call other functions.
*   When __SEGGER_STOP_RESET_STACK is not set,
*   no other function must be called.
*
*   The error handler must not return.
*   It should trigger a system reset or stay in an endless loop.
*
*   This function should not be compiled with stack overflow checks.
*/
__attribute__((used, no_stack_overflow_check)) static void _HandleStackError(unsigned int SP, unsigned int Limit, unsigned int Caller, int UsedStack) {
  //
  // Only call any function if the stack has been reset
  //
  fprintf(stderr, "SYSTEM ABORT: Stack Overflow Prevented at 0x%.8X.\n"
                  "              SP:      0x%.8X\n"
                  "              Limit:   0x%.8X\n",
                  Caller, SP, Limit);
  //
  // Enable interrupt if main stack did not overflow.
  //
  if (UsedStack < 0) {
    asm("cpsie i");
  }
  //
  // Stay in endless loop.
  // TODO: Reset the system instead.
  //
  for (;;) { ; }
}

/*********************************************************************
*
*       __SEGGER_STOP_X_OnError()
*
*  Function description
*   Callback called by stack check on stack overflow.
*   Reset stack pointer to safe value and call error handler.
*
*  Additional information
*   This callback is tail-called by the stack checking functions.
*   It assumes that:
*     - R3 contains the stack limit value.
*     - R12 contains the new stack pointer value.
*     - LR contains the caller of the stack checking function.
*     - The stack pointer has not been adjusted.
*     - __stack_end__ is the start of the main stack.
*     - __stack__process_end__ is the start of the process stack.
*
*   This function does not follow the regular calling convention.
*   This callback is implemented as naked to make sure the compiler
*   does not add a prologue which might use the stack.
*
*   This function must not be compiled with stack overflow checks.
*/
void __SEGGER_STOP_X_OnError(void) {
  asm(
      "cpsid i\n"                       // Disable interrupts
      "mov     r0, r12\n"               // Save overflowed SP
      "mov     r1, r3\n"                // Save SP limit
      "sub     r2, lr, #5\n"            // Save caller
      "mrs     r3, CONTROL\n"           // Get currently used stack
      "lsls    r3, #30\n"
      "ittee   pl\n"                    // Reset this stack
      "ldrpl   r12, =__stack_end__\n"
      "msrpl   msp, r12\n"
      "ldrmi   r12, =__stack_process_end__\n"
      "msrmi   psp, r12\n"
      "bl _HandleStackError\n"            // Call error handler
      "b .\n"                             // Stay here
      );
}

Stack Limits

The startup code must initialize at least initialize the main stack limit variable __SEGGER_STOP_Limit_MSP. In the default implementation the symbol is automatically initialized to its default value by the runtime init.

To adjust the value, such as to change the space reserved for saved registers, and to initialize __SEGGER_STOP_Limit_PSP, __SEGGER_STOP_X_InitLimits should be implemented and called.

With the SEGGER Linker, __SEGGER_STOP_X_InitLimits can be called automatically by the runtime init:

 initialize by calling __SEGGER_STOP_X_InitLimits    { section .data.stop.* };

With the GNU Linker, __SEGGER_STOP_X_InitLimits should be called as the first function in main:

int main(void) {
  int NumItems;
  
#if !defined (__SEGGER_LINKER)
  //
  // Optionally initialize stack limits if not done by runtime init.
  //
  __SEGGER_STOP_X_InitLimits();
#endif
  ...
}

Calling functions before runtime init

When the system calls functions before runtime init, which is default on Cortex-M with call to SystemInit in Reset_Handler, __SEGGER_STOP_Limit_MSP should be set to 0, to disable stack checks.

Reset_Handler:
        .extern __SEGGER_STOP_Limit_MSP
        //
        // Initialize main stack limit to 0 to disable stack checks before runtime init
        //
        movs    R0, #0
        ldr     R1, =__SEGGER_STOP_Limit_MSP
        str     R0, [R1]
        //
        // Call SystemInit
        //
        bl      SystemInit
        ...

Using an RTOS

When using an RTOS or different mechanism for multi tasking, the task switching routines must update the stack limit variable (usually __SEGGER_STOP_Limit_PSP) when switching stacks.

ChangeTask:
  ...
  ldr      r0, [r1, #0]   // OS.pCurTask
  ldr      r3, [r0, #8]   // OS.pCurTask->pStackBottom
  add      r3, #100       // Buffer before stack overflow
  ldr      r2, =__SEGGER_STOP_Limit_PSP
  str      r3, [r2]       // Update stack limit
  ...

It is recommended to enable stack check for all tasks. However, an RTOS might disable stack check for some tasks, by setting the limit variable to 0.