M0AGX / LB9MG

Amateur radio and embedded systems

Very simple stack overflow detection for Cortex-M and RISC-V

There are many ways of detecting and dealing with stack overflow in embedded systems. Here is a very, very easy stack canary with minimal overhead that can be used on Cortex-M and RISC-V with GNU and LLVM toolchains.

The first thing to realize is that the default GNU linker script (at least for Cortex-M and RISC-V) provides a symbol __bss_end__ that is the next address after the static data section. Older versions of the script may call it _ebss.

Quick reminder how the classic C memory layout looks like from Wikipedia.

C application layout

Let's assume that the heap is not used. If the stack grows over the bss section you are in deep trouble. In C you can access the __bss_end__ like any other variable using extern.

1
2
3
#define STACK_GUARD_VALUE 0xCAFED00D
extern uint32_t __bss_end__; //symbol provided by linker script
volatile uint32_t *_stack_guard_ptr = &__bss_end__;

The technique is as simple as writing a magic value to the address of __bss_end__ and checking it periodically, for example in main(). volatile is needed to force the compiler to generate code that does an actual read every time.

Writing

1
*_stack_guard_ptr = STACK_GUARD_VALUE;

Checking

1
2
3
if (*_stack_guard_ptr != STACK_GUARD_VALUE) {
    __BKPT(20);
}

On RISC-V the equivalent is asm("ebreak"). Breakpoint is just one of the options, definitely very obvious during development under debugger control. An infinite loop is also easy to spot, however a watchdog reset will not leave much debugging information so leaving explicit breadcrumbs is probably the best.

Complete code example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#define STACK_GUARD_VALUE 0xCAFED00D
extern uint32_t __bss_end__; //symbol provided by linker script
volatile uint32_t *_stack_guard_ptr = &__bss_end__;

void main(void){
    // initialization 1

    // initialization 2

    *_stack_guard_ptr = STACK_GUARD_VALUE;

    while (1){ //main loop
        // task 1

        // task 2

        if (*_stack_guard_ptr != STACK_GUARD_VALUE) {
            //Set debugging breadcrumbs, reset etc.
        }
    } //end of main loop
}