One of the bugs that requires the most painstaking effort to hunt down in an embedded system is when the stack overflows its boundaries and starts to overwrite nearby memory. The symptoms of stack overflow usually appear randomly, when just the perfect storm of interrupts and function calls are occurring. This leads them to be difficult to detect. You can prevent stack overflow through the use of a stack monitor. Here are seven steps that developers can take to create a stack monitor and ensure the stack remains in its allocated memory region.
Step #1 – Perform a worst-case stack analysis
Many compilers and tool chains will automatically set the stack size to 0x400 bytes, which is equivalent to a kilobyte of RAM. A stack size of a kilobyte is usually sufficient for many applications but this is supposed to be computer science and not a guessing game. So how can an engineer be sure that the stack is properly sized? The answer is to perform a worst-case stack analysis.
A worst-case stack analysis can be performed in many different ways and is beyond the scope of this short article. In general, though, a developer needs to fully understand a number of items. First, an understanding of the call depth of their application is necessary. How many functions are calling functions that call functions before returning back up the chain? Each of the return addresses is stored on the stack. Second, the developer needs to understand the number and size of all variables within those functions to estimate how much stack space each function will use. Finally, the developer will need to determine how many interrupts could fire simultaneously along with the size of each interrupt frame.
Step #2 – Set the stack size
The worst-case stack analysis will reveal a size that the stack should be. But calculating stack size can be difficult and hard to do. So despite a careful analysis of the system, it doesn’t hurt to multiply the final number by 1.5 just to make sure that there is a reasonable buffer included for unforeseen circumstances. The stack size can then be changed either through the project properties or through the linker file depending on preference and tool capabilities.
Step #3 – Select a protection method
Properly sizing the stack is good progress towards preventing the stack from overflowing and clobbering nearby memory regions, but it still doesn’t allow for detection of such an overflow event. In an embedded system there are a number of ways to detect such an event. The first is to use a memory protection unit and set the stack boundary. If the stack crosses the boundary the MPU can fire an interrupt and the system can then log the issue and follow procedure to recover the system.
A second approach, if a RTOS is in use, is to enable stack overflow detection in the RTOS. Many RTOSs by default have this detection enabled but I have seen articles recommending turning this feature off to improve performance! It is NOT recommended that developers disable stack overflow detection or else you may feel the cold embrace of a stack overflow bug.
Finally, in a resource-constrained system where an MPU isn’t available or an RTOS in use, a developer can very easily create their own stack monitor.