I am writing an Ada program using the Ravenscar subset (thus, I am aware of the number of running tasks at execution time). The code is compiled by gcc with the -fstack-check
switch enabled. This should cause the program raise a STORAGE_ERROR at runtime if any of my tasks exceed their stack.
Ada allows to set the upper limit for those (task-specific) stacks during the specification of the respective task like so:
pragma Storage_Size (Some_Value);
Now I was wondering what options I have to determine Some_Value
. What I have heard of so far:
-fstack-usage
in there.If I understand this correctly all the above techniques are dynamic (i.e. they require the program to run in order to work). Are static approaches also conceivable? E.g. by restricting myself further through some of Ada's high integrity options (such as No_Recursion, what else?).
Perhaps any of you can name some best practices to tackle this problem and/or extend/comment on my (surely incomplete) list.
Bonus question: What is the default size of a task's stack when the above pragma is not specified? GCC's docs only state this value depends on the runtime, without giving any concrete numbers.
The most common way to determine the deepest stack usage is to initialize the stack memory with some known but unusual value, then periodically (or at the end of a big test run) see where that pattern stops. This is exactly how the IAR IDE determines the amount of stack used.
To calculate stack depth, the address of the current stack pointer can be used. This requires simply taking the address of a function's argument or local variable. It should be done at the beginning of the main function and for each of the functions that are potentially using the most stack.
You can query the maximum process and stack sizes using getrlimit . Stack frames don't have a fixed size; it depends on how much local data (i.e., local variables) each frame needs. To do this on the command-line, you can use ulimit.
Methods of reducing stack usage Avoiding the use of large local structures or arrays. Avoiding recursion, for example, by using an alternative algorithm. Minimizing the number of variables that are in use at any given time at each point in a function.
For simple applications, the result of stack usage analysis is plain and easy to understand. In usual, the program entry and interrupt handlers would be regarded as call graph root since they are not called by any other functions.
The stack usage control file is a text file which has *.suc as its suffix. The path of stack usage control file can be set in the Advanced tab of Linker options: There are several types of directive that can be used in the stack usage control file, such as function, exclude, possible calls, call graph root, max recursion depth, no calls from, etc.
IAR Embedded Workbench for RX provides another approach to track the stack usage at runtime, implemented by the C-SPY debugger. C-SPY can fill the entire stack area with a magic data pattern, for example 0xCD, before the application starts to execute.
Upon analysis, functions with a high stack requirement should be examined to determine if the size requirements are required. A method of detecting stack overflows is to create a canary space at the end of each task. This space is filled with some known data. If this data is ever modified, then the application has written past the end of the stack.
You can generally check the stack space required by individual types with the 'Storage_Size
attribute (which counts in bits).
Once you have tabulated this (you may need to round it up to whole words/double words), you can add up how much stack space is used by each declarative region, and then walk through your calls to find the maximum stack usage.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With