Difference between revisions of "Stack Overflow Prevention"

From SEGGER Wiki
Jump to: navigation, search
(The stack check functions)
(The stack check functions)
Line 87: Line 87:
   
 
<source lang="asm">
 
<source lang="asm">
__SEGGER_STOP_GROW_R3:
+
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
 
mrs r12, CONTROL
 
lsls r12, #30
 
lsls r12, #30
  +
//
  +
// Calculate the new stack pointer position
  +
//
 
sub r12, sp, r3
 
sub r12, sp, r3
  +
//
  +
// Load corresponding stack limit
  +
//
 
ite mi
 
ite mi
ldrmi r3, =__SEGGER_STOP_LIMIT_PSP
+
ldrmi r3, =__SEGGER_STOP_Limit_PSP
ldrpl r3, =__SEGGER_STOP_LIMIT_MSP
+
ldrpl r3, =__SEGGER_STOP_Limit_MSP
 
ldr r3, [r3]
 
ldr r3, [r3]
  +
//
  +
// Compare with new stack value
  +
//
 
cmp r12, r3
 
cmp r12, r3
blt .L_R3_2
+
blt L(MightError)
  +
L(Done):
.L_R3_1:
 
  +
//
  +
// Check passed.
  +
// Adjust stack and return
  +
//
 
mov sp, r12
 
mov sp, r12
 
bx lr
 
bx lr
  +
L(MightError):
.L_R3_2:
 
  +
//
  +
// A stack limit value of 0 means: Check is disabled
  +
//
 
cmp r3, #0
 
cmp r3, #0
beq .L_R3_1
+
beq L(Done)
b __SEGGER_STOP_ON_ERROR
+
b __SEGGER_STOP_X_OnError
  +
END_FUNC __SEGGER_STOP_GROW_R3
   
__SEGGER_STOP_GROW_R4:
+
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
 
mrs r12, CONTROL
 
lsls r12, #30
 
lsls r12, #30
  +
//
  +
// Calculate the new stack pointer position
  +
//
 
sub r12, sp, r4
 
sub r12, sp, r4
  +
//
  +
// Load corresponding stack limit
  +
//
 
ite mi
 
ite mi
ldrmi r4, =__SEGGER_STOP_LIMIT_PSP
+
ldrmi r4, =__SEGGER_STOP_Limit_PSP
ldrpl r4, =__SEGGER_STOP_LIMIT_MSP
+
ldrpl r4, =__SEGGER_STOP_Limit_MSP
 
ldr r4, [r4]
 
ldr r4, [r4]
  +
//
  +
// Compare with new stack value
  +
//
 
cmp r12, r4
 
cmp r12, r4
blt .L_R4_2
+
blt L(MightError)
  +
L(Done):
.L_R4_1:
 
  +
//
  +
// Check passed.
  +
// Adjust stack and return
  +
//
 
mov sp, r12
 
mov sp, r12
 
bx lr
 
bx lr
  +
L(MightError):
.L_R4_2:
 
  +
//
  +
// A stack limit value of 0 means: Check is disabled
  +
//
 
cmp r4, #0
 
cmp r4, #0
beq .L_R4_1
+
beq L(Done)
b __SEGGER_STOP_ON_ERROR
+
//
  +
// Store limit value in R3
  +
//
  +
mov r3, r4
  +
b __SEGGER_STOP_X_OnError
  +
END_FUNC __SEGGER_STOP_GROW_R4
   
__SEGGER_STOP_GROW_0:
+
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
 
mrs r12, CONTROL
 
lsls r12, #30
 
lsls r12, #30
  +
//
  +
// Load corresponding stack limit
  +
//
 
ite mi
 
ite mi
ldrmi r12, =__SEGGER_STOP_LIMIT_PSP
+
ldrmi r12, =__SEGGER_STOP_Limit_PSP
ldrpl r12, =__SEGGER_STOP_LIMIT_MSP
+
ldrpl r12, =__SEGGER_STOP_Limit_MSP
 
ldr r12, [r12]
 
ldr r12, [r12]
  +
//
  +
// Compare with current stack value
  +
//
 
cmp sp, r12
 
cmp sp, r12
blt .L_0_2
+
blt L(MightError)
  +
L(Done):
.L_0_1:
 
  +
//
  +
// Check passed. Return.
  +
//
 
bx lr
 
bx lr
  +
L(MightError):
.L_0_2:
 
  +
//
  +
// A stack limit value of 0 means: Check is disabled
  +
//
 
cmp r12, #0
 
cmp r12, #0
beq .L_0_1
+
beq L(Done)
b __SEGGER_STOP_ON_ERROR
+
//
  +
// 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
 
</source>
 
</source>
   

Revision as of 17:50, 12 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:

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

Stack limit

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