Preventing Stack Overflow in Embedded Systems: The –stack Option as a ‘Memory Firewall’

Hello everyone, I am a sharing-loving programmer. I am happy to share my experiences and insights from my work.

-begin-

In embedded development, stack overflow is a subtle and dangerous issue—programs may suddenly crash after running for a while, or variable values may be inexplicably altered, making troubleshooting quite challenging. This is often due to insufficient stack space, which leads to critical data being overwritten during function calls. The linker option -Wl,–stack can directly specify the stack size of the program, setting a ‘safety boundary’ for the stack space, thereby reducing the risk of stack overflow from the source.

Function of the option:

-Wl,–stack=SIZE is used to specify the stack space size of the program during the linking phase (in bytes). It tells the operating system or embedded kernel to allocate a fixed size of stack memory for the current program. For example, -Wl,–stack=0x10000 indicates setting the stack size to 64KB. For bare-metal programs without an operating system, this option affects the layout of the stack in the linker script.

Usage scenarios:

The program contains deep recursive calls (such as parsing nested protocols or traversing tree structures), and the default stack may be insufficient; Large local variables are defined within functions (such as buffers or arrays), consuming a significant amount of stack space; During debugging, unexpected modifications to variables are found, suspected to be caused by stack overflow leading to ‘stack corruption.’

Detailed example::

Taking an embedded program that processes nested JSON data as an example, we will compare the effects of the default stack size and a custom stack size.

1. Default stack size (without using –stack):

arm-none-eabi-gcc -mcpu=cortex-m4 -o json_parser.elf json_parser.c -T stm32h743.ld

The default stack size for the STM32H743 bare-metal program is usually specified by the linker script, assumed to be 16KB (0x4000). When the parsing depth exceeds 8 levels of JSON, the program will suddenly reset, and debugging reveals that it is a stack overflow caused by recursive calls, overwriting the address of the interrupt vector table at the bottom of the stack.

2. Custom stack size (using –stack):

Estimating the required stack space based on recursion depth, we need 32KB of stack space, specified at compile time:

arm-none-eabi-gcc -mcpu=cortex-m4 -o json_parser.elf json_parser.c -T stm32h743.ld -Wl,–stack=0x8000

At the same time, modify the linker script to ensure correct stack space allocation (some compilers require synchronizing adjustments to _estack and _Min_Stack_Size):

/* Confirm stack settings in the linker script */

_estack = 0x20040000; /* Stack top address */

_Min_Stack_Size = 0x8000; /* Consistent with –stack */

After recompiling, the program can successfully parse 16 levels of nested JSON data without stack overflow.

3. Verifying stack size:

Use readelf to check the stack-related information of the program (applicable to programs with an operating system):

arm-none-eabi-readelf -S json_parser.elf | grep “stack”

In bare-metal programs, the stack top (_estack) and the stack bottom (_estack – stack size) addresses can be checked using a debugger to confirm correct space allocation.

Notes:

A larger stack size is not always better: embedded devices have limited memory, and excessive stack allocation can encroach on heap or global variable space, potentially leading to ‘out of memory’ errors; Bare-metal programs need to modify the linker script synchronously: some compilers’ –stack option only affects symbol definitions, and the starting address and size of the stack must be explicitly defined in the linker script to avoid conflicts; Recursive calls need to be combined with stack checks: even if the stack is increased, infinite recursion will still lead to overflow, so it is recommended to add depth limits in recursive functions (e.g., if (depth > 32) return error;).

Practical experience:

I once developed a log parsing program for an industrial device, where a local buffer was defined in a function as char buf[10240] (occupying 10KB of stack space), and combined with the function call stack, the default 8KB stack overflowed directly. After setting the stack size with -Wl,–stack=0x5000 (20KB), the buffer was no longer overwritten by stack overflow, and log parsing resumed normally.

The core of -Wl,–stack is ‘allocation on demand’—setting a reasonable size for stack space based on the actual memory usage of the program. In embedded development, the total of the stack and heap cannot exceed the device’s RAM capacity, so a balance must be struck between the two: if recursion is frequent or local variables are large, set the stack larger; if dynamic memory allocation is frequent, set the heap larger. Tomorrow, we will discuss the compiler’s -fsanitize=address option and see how to accurately locate stack overflow issues during the debugging phase.

-end-

If this article has helped you, please like, share, and follow. Thank you very much!

Leave a Comment