Figuring out how to size the stack for an embedded application and the tasks within it can be challenging. In many cases, developers will pick a value that they feel should be enough. These estimates are sometimes a little short, most of the time a gross estimation and rarely spot on. While I always encourage developers to monitor their stack usage throughout their development cycle, there are times when a developer should be performing a worst-case stack analysis such as when:
- They are running dangerously low on RAM
- Need to commit a new code version
- They are finalizing the firmware for production
In this post, I will discuss three different ways that a develop can perform a worst-case stack analysis.
Technique #1 – Calculating by hand
In the days of old, embedded software developers used to have to hand calculate what their stack usage was going to be which can be tricky business. In order to calculate stack usage by hand, developers needed to know:
- How many function calls deep they were going to go
- The local variables that would be stored on the stack in each of those functions
- The size of the return address that will be stored on the stack
- The size of the local variables that will stored on the stack
- How big an interrupt frame will be if an interrupt occurs during execution
- The number of nested interrupts that could occur
As you can imagine, finding all these values and continuing to track them if changes are made can be quite time consuming and error prone. That’s why this method is no longer recommended but it’s still useful to attempt once to gain a deeper appreciation of what the other techniques are doing.
Technique #2 – Use a static code analyzer
Many static code analyzers can be used to estimate what the worst-case stack usage will be. During the code analysis, the tool will determine the function depth along with many of the items that we listed earlier. The nice thing about using a static analyzer is that it isn’t just performing a stack analysis but also checking for potential issues with the code. It runs in a matter of seconds which saves a developer from needing to hand calculate the stack usage.
While using a static code analyzer to get your worst-case stack usage is a good way to go, there are several potential issues that developers need to watch out for. These include:
- Dereferencing a function pointer is not counted as a function call
- Interrupt frames are not considered
It’s important to understand how your tool handles these items. In order to get an accurate result, one must often conditionally compile the function pointers out into function calls during the static code analysis using a macro or compiler symbol. You’ll also have to then add in what you believe the interrupt stack usage will be. Minor issues but important to understand in any analysis.
Technique #3 – Test and measurement
The technique that I often advocate most for worst-case stack analysis is to test and measure the system. Many development environments now have tools to perform OS-Aware debugging which will monitor the RTOS performance closely including the maximum stack usage while the application was running. A great example can be seen in the image below from within e2 Studio for the Renesas Synergy Platform which uses ThreadX.
As you can see, each thread (task) is listed along with the memory location, stack pointer and the maximum stack usage. We can even see how much memory is allocated to the stack. This provides developers not just a great way to keep tabs on their stack usage but also determine what their maximum stack usage is.
Developers do need to be careful with the maximum value presented to them. It’s important that the readings be taken while their system is under the greatest stress. For an RTOS based application, the interrupt frames are often stored on the system stack so that we don’t need to be concerned with sizing each thread to have enough memory for an interrupt storm.
No matter which technique you use to determine your stack usage, it’s still a good practice to oversize the stack a bit. It’s possible that during testing the worst-case was never achieved which could then setup the system for a stack overflow when the system is in the field.
In this article we have examined several techniques that we can use to calculate our worst-case stack usage. We’ve touched on just a few highlights. There are also several additional methods developers can use such as using tools like Percepio Tracealyzer or uC Probe to monitor the stack memory directly. There are even methods that rely on calculations that are performed by the compiler and linker. For today these are beyond our scope but if you are interested in these methods, I recommend reading this article. As we have seen, it’s very easy using modern tools to monitor the stack usage through-out the development cycle. It’s highly recommended that as developers write their software, they monitor and adjust their stack sizes accordingly for the most efficient system possible.