Kontinuierliche Überwachung der Stackanalyse während der gesamten Entwicklung

Mit Tausenden bis Millionen von Codezeilen wird Embedded Software immer anspruchsvoller, aber das Gesamtziel, die Entwicklung einer fehlerresistenten, korrekten und schnellen Software, bleibt dasselbe. Leistungsstarke Software muss die verfügbaren CPU- und Speicherressourcen optimal verwalten, was bei Embedded Systemen mit begrenzter Speicherkapazität, insbesondere beim RAM, eine Herausforderung darstellt. Es ist deshalb wichtig, RAM-Nutzung durch eine Stack- und Heap-Analyse zu analysieren. Die manuelle Abschätzung der Stack- und Heap-Last seitens der Entwickler wird zu einer schwierigen Aufgabe, selbst bei kleinen Programmen. Falsche Schätzungen können zu Stapelüberläufen und undefinierten Verhaltensweisen führen. Aus diesem Grund schreiben einige Codierungsstandards Best Practices für die Verwendung von Speicherzuweisungen vor, mit denen unnötiger Verwaltungsaufwand (Overhead) vermieden wird. Der Stack ist jedoch nach wie vor eine erforderliche RAM-Komponente und muss optimal genutzt werden.

Warum wird die Stackanalyse für Embedded Systeme benötigt?

Ein Stapelüberlauf (Stack Overflow) tritt auf, wenn der verfügbare Stapel (Stack) kleiner ist, als der Code erfordert. Es wird jedoch Speicher verschwendet, wenn die Umgebung mit einem größeren als dem erforderlichen Stapel konfiguriert wird. Entwickler müssen also den schlimmsten Fall der Stacknutzung in sicherheitskritischen Anwendungen kontinuierlich und konsequent abschätzen, um zu verhindern, dass die Software zu wenig RAM hat.

Risiko einer falschen Stapelschätzung.

Risiko einer falschen Stapelschätzung.

Wie erreicht man eine Schätzung bei der Stackanalyse?

Manuelle Schätzung des Stapels

Die manuelle Schätzung der Stackanalyse kann zwar gelegentlich hilfreich sein, bei komplexeren Systemen wird sie jedoch schnell zur Herausforderung. Dies erfordert unter anderem ein umfassendes Verständnis der Tiefe der Funktionsaufrufe und der Details aller lokalen Variablen und der Größe der Interrupt-Frames, die zu jedem Zeitpunkt der Ausführung auftreten. Dieses Verfahren ist langwierig und fehleranfällig. Eine manuelle Berechnung ist nicht erforderlich, da statische Code-Analyse-Tools dies schnell berechnen können.

Verwendung statischer Code-Analysatoren

Entwickler können die Stacknutzung mit einem statischen Code-Analysator vorhersagen. Analysewerkzeuge können die Tiefe von Funktionsaufrufen, Stack-Schätzungen für lokale Variablen und Rückgabeparameter, verschachtelte Interrupts und die Größe der während der Ausführung auftretenden Interrupts analysieren. Der Vorteil eines statischen Code-Analyzers besteht darin, dass er neben der Stackanalyse Verletzungen der Codierungsregeln, Laufzeitfehler und die Kodierungskomplexität erfasst. Die Analyse ist in wenigen Minuten abgeschlossen und spart dem Entwickler die Zeit, die er für die manuelle Berechnung des Stapelverbrauchs benötigen würde.

Tests und Messungen am Ziel

Statische Analysatoren können den Stackverbrauch während der Entwicklung abschätzen. Am besten ist es jedoch, die Ergebnisse des tatsächlichen Stackverbrauchs auf der realen Hardware zu ermitteln. Viele Entwicklungsumgebungen verfügen über Funktionen zur Emulation der Hardware und bieten die Möglichkeit, Stackanalysen in Echtzeit durchzuführen. Zum Testen ausfallsicherer Routinen ist es wichtig, eine Stackanalyse auf echter Hardware durchzuführen und Überlaufszenarien zu erstellen. Die große Frage ist nun, wann eine Stackanalyse mit statischen Analysetools und wann auf dem eigentlichen Ziel durchgeführt werden sollte.

Wann sollte eine Stackanalyse durchgeführt werden?

Die Durchführung der Stackanalyse ist ein kontinuierlicher Prozess im Lebenszyklus der Softwareentwicklung. Wird die Stacknutzung erst am Ende des Lebenszyklus der Softwareentwicklung von einem separaten Qualitätsbewertungsteam geschätzt, könnte der gesamte Entwicklungsaufwand gefährdet sein. Außerdem kann sich die Lösung von Problemen zu einem späten Zeitpunkt im Entwicklungszyklus als fehlerhaft und zeitaufwendig herausstellen und Unklarheit darüber entstehen, ob Designänderungen an der Hardware oder der Software vorgenommen werden sollen. Die wichtigsten Meilensteine für die Durchführung der Stackanalyse sind:

  • Wenn neue Funktionen hinzugefügt werden

    Jede neue Funktion, die der Software hinzugefügt wird, erhöht die Nutzung des Stacks. Die Entwickler müssen die Nutzung der Stacks durch die neue Funktion im Auge behalten.
    1. Durchführung von Stackanalysen, Debugging und Korrektur von komplexem Code: Nach jeder größeren Funktionsimplementierung können Entwickler einen statischen Analysator für eine bestimmte Softwarekomponente oder ein Softwaremodul lokal einsetzen, um den Anstieg des Stackverbrauchs durch die neue Software im Vergleich zur Basissoftware zu bewerten.
    2. Überwachen Sie die Stackanalyse während des gesamten Entwicklungsprozesses: Das QA-Team und die Product Owner können mit einem statischen Analysator Stackschätzungen für die Continuous Integration Pipeline (CI-Pipeline) vornehmen und die Ergebnisse auf Dashboards anzeigen. Dieser Prozess unterstützt die Verfolgung der Stackanalyse im Lebenszyklus der Softwareentwicklung.
    3. Durchsetzung bewährter Praktiken, um den Stackverbrauch so gering wie möglich zu halten: Quality Gates (Qualitätsprüfpunkte) können dazu beitragen, Verstöße gegen die MISRA™- und AUTOSAR-Codierrichtlinien zu vermeiden, die die bedingte Verwendung dynamischer Speicherzuweisungen vorschreiben.
  • Vor der Softwarefreigabe

    Die Stackschätzungen eines statischen Analysators weisen deutlich darauf hin, dass der Stackverbrauch unter Kontrolle ist. Führen Sie vor jeder Softwareveröffentlichung eine Stackanalyse an realen Zielen unter Standard-, Minimal- und Maximallasten durch, um ein umfassendes Verständnis der Stacknutzung zu erlangen. Die Überprüfung der Ausfallsicherheitsverfahren für Stapelüberlauf- und -unterlaufereignisse ist ebenfalls entscheidend.

Was leistet Polyspace für die Stackschätzung?

Polyspace Code Prover™ führt konservative und optimistische Schätzungen für höhere und niedrigere Größen von lokalen Variablen in jeder Funktion durch, um die maximale und minimale Stacknutzung sowohl auf Funktions- als auch auf Programmebene zu ermitteln. Die Analyse berücksichtigt die Größe der Funktionsrückgabewerte, der Funktionsparameter, der lokalen Variablen und die zusätzliche Auffüllung für die Speicherausrichtung.

Code-Metriken für die Stackanalyse auf dem Polyspace-Desktop.

Code-Metriken für die Stackanalyse auf dem Polyspace-Desktop.

Um die überschüssige Stacknutzung zu verstehen und zu debuggen, können Entwickler Polyspace® lokal ausführen und die Tiefe der Funktionsaufrufe durchgehen, die genaue Ursache für die überschießende Stack-Nutzung identifizieren und die Stacknutzung durch optimale Nutzung der verfügbaren Ressourcen zu verringern.

Aufrufbaum und höhere Stackschätzung für die Funktion table_loop().

Aufrufbaum und höhere Stackschätzung für die Funktion table_loop().

Überwachung der Stackanalyse während des gesamten Entwicklungsprozesses

Polyspace Access™ ist ein Ergebnisdatenbankserver, der eine grafische Benutzeroberfläche auf Webbrowsern rendert. Der CI-Prozess kann zur Erstellung einer Schätzung der Stacknutzung eine Stackanalyse auf dem Polyspace Server™ auslösen. Dieses Ergebnis kann in die Ergebnisdatenbank hochgeladen werden. QA-Teams und Product Owner können die Stacknutzung kontinuierlich auf dem grafischen Frontend einsehen und bei Übernutzung der verfügbaren Stackressourcen die erforderlichen Maßnahmen ergreifen.

Stackschätzung auf Projektebene mit Polyspace Access.

Stackschätzung auf Projektebene mit Polyspace Access.

Als nächsten Schritt sollten Sie Funktionen mit höherer Stackauslastung überprüfen und die jeweilige Funktion den Entwicklern zur weiteren Untersuchung und Fehlersuche zuweisen. Polyspace ermöglicht, Status, Schweregrad und Kommentare zu Analyseergebnissen festzulegen, bevor sie Entwicklern in Bug-Tracking-Tools wie Jira zugewiesen werden. 

Dashboard zur Stackschätzung und Ergebnisüberprüfung auf Funktionsebene in Polyspace Access.

Dashboard zur Stackschätzung und Ergebnisüberprüfung auf Funktionsebene in Polyspace Access.

Durchsetzung bewährter Praktiken zur Minimierung der Stackauslastung

Der Produktionscode darf in keinem Fall gegen Codierungsstandards wie MISRA C™, MISRA C++, AUTOSAR C++ und andere verstoßen. Diese Codierungsstandards setzen das Verbot der dynamischen Speicherzuweisung durch und empfehlen spezifische Anwendungsfälle zur Optimierung der statischen Speicherzuweisung. Der Polyspace Bug Finder™ kann Verstöße gegen Best Practices identifizieren, die Entwickler lokal und Produkt Owner über Polyspace Access überwachen können. Die nachstehenden Codierungsregeln enthalten Best Practices für die statische Speicherzuweisung, die mit dem Polyspace Bug Finder analysiert werden können.

Codierungsleitfaden

Regel

Beschreibung

MISRA C: 2004

20.4

Heap-Speicher darf nicht dynamisch zugewiesen werden.

MISRA C: 2012

21.3

Die Speicherzuweisungs- und -freigabefunktionen von <stdlib.h> dürfen nicht verwendet werden.

MISRA C++: 2008

18-4-1

Heap-Speicher darf nicht dynamisch zugewiesen werden.

AUTOSAR C++14

A18-5-1

Die Funktionen malloc, calloc, realloc, und free dürfen nicht verwendet werden.

AUTOSAR C++14

A18-5-2

Die Ausdrücke "new" oder "delete" dürfen nicht verwendet werden.

AUTOSAR C++14

A18-5-3

Die Form des delete-Ausdrucks muss mit der Form des new-Ausdrucks übereinstimmen, der für die Zuweisung des Speichers verwendet wurde.

AUTOSAR C++14

A18-5-4

Wenn für ein Projekt eine Version des Operators „delete“ mit und ohne Größenangabe global definiert ist, müssen sowohl eine Version mit als auch eine Version ohne Größenangabe definiert werden.

AUTOSAR C++14

A18-5-5

Die Speicherverwaltungsfunktionen müssen Folgendes gewährleisten: (a) deterministisches Verhalten, das zu einer Worst-Case-Ausführungszeit führt, b) Vermeidung von Speicherfragmentierung, c) Vermeidung von Speichermangel, d) Vermeidung von unpassenden Zuweisungen oder Freigaben und e) Unabhängigkeit von nichtdeterministischen Aufrufen des Kernels.

AUTOSAR C++14

A18-5-7

Wird in dem Projekt eine Nicht-Echtzeit-Implementierung dynamischer Speicherverwaltungsfunktionen verwendet, so darf der Speicher nur in Nicht-Echtzeit-Programmphasen zugewiesen und freigegeben werden.

AUTOSAR C++14

A18-5-8

Für Objekte, die eine Funktion nicht überdauern, gilt eine automatische Speicherdauer.

AUTOSAR C++14

A18-5-9

Benutzerdefinierte Implementierungen von Funktionen zur Zuweisung und Freigabe von dynamischem Speicher müssen die semantischen Anforderungen erfüllen, die in der entsprechenden Klausel „Required behavior “ (Erforderliches Verhalten) des C++-Standards angegeben sind.

AUTOSAR C++14

A18-5-10

Der Ausdruck "Placement new" darf nur mit korrekt ausgerichteten Zeigern auf ausreichende Speicherkapazität verwendet werden.

AUTOSAR C++14

A18-5-11

Die Ausdrücke „Operator new“ und „Operator delete“ werden gemeinsam definiert.

Der Stackverbrauch steigt mit zunehmender zyklomatischer Komplexität des Codes, der Anzahl der verschachtelten Funktionsaufrufe, der Anzahl der Variablen in der Funktion und anderen Faktoren. Polyspace ermöglicht die Kontrolle über zahlreiche Variablen, die die Stacknutzung beeinflussen, sowie die Festlegung eines Schwellenwerts für die Codekomplexität.

Legen Sie Schwellenwerte für die Codekomplexität fest.

Legen Sie Schwellenwerte für die Codekomplexität fest.

Polyspace Bug Finder ermöglicht zahlreiche Laufzeitprüfungen für statische und dynamische Speicherzuweisungen. Die Behebung aller Fehler mit hoher, mittlerer und niedriger Priorität trägt zur Verringerung der Risiken bei, die bei der Speicherzuweisung entstehen.

Statische und dynamische Speicherprüfungen während der Laufzeit.

Statische und dynamische Speicherprüfungen während der Laufzeit.

Es ist ratsam, den Stack etwas zu groß zu dimensionieren, unabhängig von der Methode, die zur Berechnung des Stackverbrauchs verwendet wird. Dieser Ansatz hilft, Schwachstellen im System aufgrund eines Stacküberlaufs zu vermeiden, der beim Testen möglicherweise unentdeckt geblieben wäre.

Die Stack-Overflow-Schwachstelle ist ein wesentlicher Grund dafür, dass viele Embedded Anwendungen in der Praxis ein undefinierbares Verhalten zeigen. Die Verwendung des richtigen Tools zur richtigen Zeit und die Befolgung von Best Practices kann das Vertrauen in die Software gegen Stacküberläufe verbessern.