Difference between revisions of "Stack Overflow Prevention"
(→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, |
+ | mrs r12, CONTROL |
− | lsls r12, # |
+ | lsls r12, #30 |
− | + | sub r12, sp, r3 |
|
− | + | ite mi |
|
− | + | ldrmi r3, =__SEGGER_STOP_LIMIT_PSP |
|
− | + | ldrpl r3, =__SEGGER_STOP_LIMIT_MSP |
|
− | ldr |
+ | ldr r3, [r3] |
− | cmp |
+ | cmp r12, r3 |
− | + | blt .L_R3_2 |
|
+ | .L_R3_1: |
||
− | movhs sp, r3 |
||
− | + | mov sp, r12 |
|
− | + | 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, |
+ | mrs r12, CONTROL |
− | lsls r12, # |
+ | lsls r12, #30 |
− | + | sub r12, sp, r4 |
|
− | + | ite mi |
|
− | + | ldrmi r4, =__SEGGER_STOP_LIMIT_PSP |
|
− | + | ldrpl r4, =__SEGGER_STOP_LIMIT_MSP |
|
− | ldr |
+ | ldr r4, [r4] |
− | cmp |
+ | cmp r12, r4 |
− | + | blt .L_R4_2 |
|
+ | .L_R4_1: |
||
− | movhs sp, r4 |
||
− | + | mov sp, r12 |
|
− | + | 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, |
+ | mrs r12, CONTROL |
− | lsls r12, # |
+ | lsls r12, #30 |
− | ite |
+ | ite mi |
− | + | ldrmi r12, =__SEGGER_STOP_LIMIT_PSP |
|
− | + | ldrpl r12, =__SEGGER_STOP_LIMIT_MSP |
|
ldr r12, [r12] |
ldr r12, [r12] |
||
cmp sp, r12 |
cmp sp, r12 |
||
− | + | blt .L_0_2 |
|
+ | .L_0_1: |
||
− | bxhs lr |
||
− | + | 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
Contents
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:
- Functions that don't touch the stack are not changed.
- 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().
- 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.
- 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.
- 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.