How to Continually Monitor Stack Analysis Throughout Development

With thousands to millions of lines of code, embedded software is becoming increasingly sophisticated, but the overall goal of achieving robust, correct, and fast-performing software remains the same. Fast-performing software needs to optimally manage the available CPU and memory resources, which is a challenge in embedded systems with restricted memory capacity, especially the RAM. It is important to analyze RAM usage by performing stack and heap analysis. Estimating the stack and heap load manually by developers becomes a difficult task, even for small programs. Incorrect estimations can lead to stack overflows and undefined behaviors. That is the reason why some coding standards enforce best practices on the use of memory allocation to avoid unnecessary overheads. However, stack is still a required RAM component and needs to be optimally used.

Why Is Stack Analysis Needed for Embedded Systems?

Stack overflow happens when the available stack is smaller than the code demands. Yet, memory is wasted when the environment is configured with a larger-than-required stack. It is imperative for developers to estimate the worst-case stack usage in safety-critical applications continuously and consistently to prevent running the software short on RAM.

Risk of incorrect stack estimate.

Risk of incorrect stack estimate.

How to Achieve Estimation on Stack Analysis?

Estimating stack manually

While manually estimating stack analysis may occasionally be helpful, it can be challenging for more complex systems. It requires a thorough understanding of the depth of function calls and details on all local variables and the size of interrupt frames that happen at any moment during execution, among others. This procedure takes a long time and is prone to mistakes. Considering static code analysis tools can quickly compute this, doing it manually is unnecessary.

Using static code analyzers

Developers can predict the stack use with a static code analyzer. Analysis tools can analyze function call depths, stack estimations on local variables and returns parameters, nested interrupts, and the size of interrupts occurring during execution. The benefit of using a static code analyzer is that it takes care of coding rule violations, run-time defects, and coding complexity along with the estimation of stack analysis. It completes in minutes, saving the developer the time it would take to manually compute the stack consumption.

Test and measurements on Target

Static analyzers can estimate the consumption of stack during development. However, it is best to get results on actual stack consumption on the real hardware. Many development environments come with emulating capabilities of the hardware and offer capabilities to perform real-time stack analysis. It is important to perform stack analysis on actual hardware and create overflow scenarios to test fail-safe routines. Now the big question is, when to perform stack analysis with static analysis tools and when on the actual target?

When to Perform Stack Analysis?

Performing stack analysis is a continuous process in the software development life cycle. Estimating the stack usage only at the end of the software development life cycle by a separate quality assessing team could keep the entire development effort at risk. Also, resolving issues late in the development cycle could be erroneous and time-consuming, and it could cause confusion in determining whether to deal with design changes on hardware or software. The most important milestones to perform stack analysis are:

  • When new features are added

    Every new feature added to the software accounts for increased stack use. Developers must keep an eye on the stack use of the new feature.
    1. Perform stack analysis, debug, and fix complex code: After every major feature implementation, developers can employ a static analyzer on specific software component or software module locally to assess the increase in stack consumption between the base software and the implemented software.
    2. Monitor stack analysis throughout the development process: The QA team and product owners can use a static analyzer to make stack estimations on the continuous integration (CI) pipeline to display the results on dashboards. This process helps to track the stack analysis during the software development life cycle.
    3. Enforce good practices to keep minimum stack use: Quality gates can help avoid violations of MISRA™ and AUTOSAR coding guidelines that enforce the conditional use of dynamic memory allocations.
  • Before software release

    Static analyzer stack estimates provide strong evidence that stack consumption is in control. Before every software release, run stack analysis on real targets under standard operational loads, minimum loads, and maximum loads to acquire a comprehensive understanding of stack use. Verifying the fail-safe procedures for stack overflow and underflow events is also crucial.

What Does Polyspace do for Stack Estimation?

Polyspace Code Prover™ performs conservative and optimistic estimates on higher and lower sizes of local variables in each function to come up with maximum and minimum stack usage both on the functional and program level. The analysis considers the size of function return values, size of function parameters, size of local variables, and additional padding introduced for memory alignment.

Stack analysis code metrics on Polyspace desktop.

Stack analysis code metrics on Polyspace desktop.

To understand and debug the overshooting stack utilization, developers can run Polyspace® locally and go through the function call depths to identify the exact cause of the stack overshoot and bring down the stack usage by optimally utilizing the available resources.

Call tree and higher stack estimate for function table_loop().

Call tree and higher stack estimate for function table_loop().

Monitor stack analysis throughout the development process

Polyspace Access™ is a result database server that renders a graphical user interface on web browsers. CI process can trigger stack analysis on the Polyspace Server™ to generate a stack usage estimate. This result can be uploaded to the result database. QA teams and product owners can continuously view stack usage on the graphical frontend and take the required action in case of overuse of available stack resources.

Project level stack estimate on Polyspace Access.

Project level stack estimate on Polyspace Access.

As a next step, review functions with higher stack use and assign the specific function to developers for further investigation and debugging. Polyspace allows to appoint status, severity, and comments on analysis findings before it is assigned to developers on bug tracking tools like Jira. 

Function level stack estimate and result review dashboard on Polyspace Access.

Function level stack estimate and result review dashboard on Polyspace Access.

Enforce good practices to keep minimum stack usage

For production code, it is obligatory to have zero violations of coding standards like MISRA C™, MISRA C++, AUTOSAR C++, and more. These coding standards enforce the prohibition of dynamic memory allocation and recommend specific use cases to optimize static memory allocation. Polyspace Bug Finder™ can identify any violations of best practices, which developers can monitor locally, and product owners via Polyspace Access. The coding rules below specify best practices for static memory allocation that can be analyzed with Polyspace Bug Finder.

Coding Guideline

Rule

Description

MISRA C: 2004

20.4

Dynamic heap memory allocation shall not be used.

MISRA C: 2012

21.3

The memory allocation and deallocation functions of <stdlib.h> shall not be used.

MISRA C++: 2008

18-4-1

Dynamic heap memory allocation shall not be used.

AUTOSAR C++14

A18-5-1

Functions malloc, calloc, realloc, and free shall not be used.

AUTOSAR C++14

A18-5-2

Non-placement new or delete expressions shall not be used.

AUTOSAR C++14

A18-5-3

The form of the delete expression shall match the form of the new expression used to allocate the memory.

AUTOSAR C++14

A18-5-4

If a project has sized or unsized version of operator “delete” globally defined, then both sized and unsized versions shall be defined.

AUTOSAR C++14

A18-5-5

Memory management functions shall ensure the following: (a) deterministic behavior resulting with the existence of worst-case execution time, (b) avoiding memory fragmentation, (c) avoid running out of memory, (d) avoiding mismatched allocations or deallocations, and (e) no dependence on non-deterministic calls to kernel.

AUTOSAR C++14

A18-5-7

If non-real-time implementation of dynamic memory management functions is used in the project, then memory shall only be allocated and deallocated during non-real-time program phases.

AUTOSAR C++14

A18-5-8

Objects that do not outlive a function shall have automatic storage duration.

AUTOSAR C++14

A18-5-9

Custom implementations of dynamic memory allocation and deallocation functions shall meet the semantic requirements specified in the corresponding “Required behavior” clause from the C++ Standard.

AUTOSAR C++14

A18-5-10

Placement new shall be used only with properly aligned pointers to sufficient storage capacity.

AUTOSAR C++14

A18-5-11

“operator new” and “operator delete” shall be defined together.

Stack usage grows with increased cyclomatic complexities of the code, number of nested function calls, number of variables in the function, and others. Polyspace offers control over numerous variables that influences stack use and allows setting a threshold for code complexity.

Set thresholds for code complexity.

Set thresholds for code complexity.

Polyspace Bug Finder offers many run-time checks for static and dynamic memory allocations. Resolving all high-, medium-, and low-priority defects help to reduce risks that arise with memory allocations.

Run time static and dynamic memory checks.

Run time static and dynamic memory checks.

It is a good idea to slightly oversize the stack, regardless of the method used to calculate your stack use. This approach helps to avoid system vulnerabilities due to a stack overflow that may have gone undetected during testing.

Stack overflow vulnerability is a significant reason why many embedded applications exhibit undefinable behavior in the field. Using the right tool at the right time and following best practices can improve confidence in the software against stack overflows.