Simplexity prides itself on simplifying product design. The simplest designs are often the most robust. This includes the design of embedded firmware.
A major issue in developing firmware for an embedded system is determining the stack size for each of the various threads within the system. Stack memory is used to store all the local variables within a function, plus some function call parameters. If insufficiently sized, a function will overrun its stack and can affect the memory of other threads, likely causing erroneous behavior and potentially causing a system crash.
Basic methods for gauging stack sizes
One way to size a thread’s stack is to take a wild guess at the stack sizes, run the system, and see if it is functional and stable. If you like to gamble, this method might be for you. If you want to be a little more thorough, however, a common practice is to mark the stack memory with a predefined pattern, run the system for a while, and dump each thread’s stack areas and examine how much of the stack space was used. This is certainly better than the first option, but it does not ensure you’ll have enough stack space for the worst-case scenario.
So how do you determine the worst-case scenario for stack usage? Running the system and examining the stack space that was used only gives you a snapshot of stack usage under the limited set of input conditions you exposed your system to. There are likely far too many combinations of inputs — not to mention the timing variations between those inputs — to exhaustively test even a relatively simple embedded system.
Robust methods for gauging stack size
Luckily, some tools and processes can be combined to determine a theoretical worst-case stack usage for each thread in your system. We frequently use the GNU ARM build tools for our projects. The tools support a wide range of ARM processors, the dominant processor choice these days for embedded systems. The compiler can be configured to generate the stack usage for each function in the system. Another tool in the family can be used to dump the assembly language instructions for each function. By examining this output, we can construct a call tree which represents all possible function call chains through the code. By combining this with the stack usage information, a worst-case stack usage can be calculated for each thread in the firmware.
If your code is extremely simple, this technique should work fine as described. However, most embedded systems rely on at least one C library, if not two or three different ones. If your code calls into these libraries, then you will also need to know the stack usage of the library function. This involves getting the source code for each library and recompiling it to get the stack usage information.
But are you sure you got all those function calls in your system? Function calls are frequently made indirectly through pointers. For instance, if you open a file handle, you are given a pointer which you then use to perform various operations on the file. These operations do not translate as direct function calls in the code. Rather, they are calls through a function pointer. Resolving all these within a system can be a time-consuming and error-prone process.
Simplexity has developed a method to analyze an embedded system which parses all the function calls, both direct and indirect, including library calls. Using this information, we can determine the worst-case stack size for each thread. This eliminates the guesswork when determining the correct stack size, as well as eliminating the resulting system crash that occurs when the stack is sized too small. It also eliminates wasting valuable memory resources on stacks that are sized too conservatively. Developing a system with the proper stack sizes is one of the many pieces required for designing the most cost-efficient and highest-quality embedded system.
For more on engineering and design, follow Simplexity’s product development blog.