Embedded-Softwareentwicklung

Mit Hyper-Coverage zur sicherheitszertifizierten Software

14. November 2022, 6:00 Uhr | Von Michael Wittner
© Dilok | stock.adobe.com

Für eine Functional-Safety-Zertifizierung müssen Entwickler die Code-Coverage für den gesamten Quellcode inklusive aller Codevarianten nachweisen. Doch wie lässt sich nicht getesteter Code in den originalen C/C++-Quelldateien erkennen? Eine Lösung bietet hier die Ermittlung einer »Hyper-Coverage«.

Als »Code-Coverage« (Testabdeckung) bezeichnet man den Nachweis, dass alle Teile eines C/C++-Quellcodes möglichst vollständig getestet wurden. Die Normen empfehlen dabei, je nach Einstufung einer sicherheitskritischen Anwendung, passende Coverage-Maße wie beispielsweise Statement Coverage (C0) und Branch Coverage (C1) oder Modified Condition/Decision Coverage (MC/DC). Wurden alle verfügbaren Tests durchgeführt, lässt sich erkennen, ob und welche Teile des Codes nicht getestet wurden.

Die Coverage-Messung stützt sich dabei auf den Kontrollfluss der Funktionen/Methoden einer Quelldatei und prüft die erreichten Programmzweige bzw. Bedingungen. Dabei stellt sich die Frage, welcher Code tatsächlich im Kontrollfluss abgebildet wird. Die vielfachen Möglichkeiten des Präprozessors erlauben es, durch den Einsatz von Makros aus ein und derselben Quelldatei mehrere unterschiedliche Programme zu erstellen.

Anbieter zum Thema

zu Matchmaker+

Codevarianten

Aus dem Source-Code einer Quelldatei werden durch den Präprozessor zunächst alle Makros aufgelöst, sodass ein präprozessierter Code entsteht, der letztendlich vom Compiler übersetzt wird. In diesen Prozess fließen die folgenden Informationen ein:

  • Die Quelldatei selbst bildet die Grundlage für die Analyse, in der Teile des originalen Source-Codes über Makros ausgeblendet sein können.
  • Eingebundene Header-Dateien können über Makros kontrollierte Konfigurationen enthalten, die wiederum andere Teile des Source-Codes sichtbar machen oder weitere Makros definieren.
  • Wichtig sind auch die Compiler-Optionen für die jeweilige Quelldatei, wie sie z. B. über eine Entwicklungsumgebung oder ein Makefile einge-steuert und auf der Kommandozeile an den Compiler übergeben werden.

Aus diesen Teilen entsteht der präprozessierte Code, der jeweils eine bestimmte Variante des originalen Source-Codes repräsentiert. Die Präprozessor-Makros werden dabei entweder durch #define/#ifdef-Anweisungen in den Header-Dateien oder durch Compiler-Switches aus der Konfiguration beim Aufruf des Compilers übergeben. Dieser präprozessierte Code stellt auch die technische Basis für Testwerkzeuge dar, auf dem Code wird die Analyse und Instrumentierung für die Coverage-Messung durchgeführt.

Beim Test von Varianten ist es entscheidend zu wissen, welche Codezeilen aus der Originalquelldatei in der jeweiligen Variante tatsächlich existieren. Und auch umgekehrt: Gibt es Code in der Originalquelldatei, der in keiner Variante enthalten ist?

Zunächst muss jede existierende Codezeile einer Variante auch genau in dieser Variante getestet werden. Für jede einzelne Variante muss eine möglichst vollständige Code-Coverage erreicht werden. Über Varianten von Testfällen lassen sich die meist relativ ähnlichen Codestrukturen der Varianten gut testen, sodass der Testaufwand reduziert werden kann.

Anhand des unterschiedlichen Kontrollflusses zweier Codevarianten lässt sich erkennen, dass die Coverage-Ergebnisse von einer Codevariante nicht einfach auf die andere übertragen werden können
Bild 1. Anhand des unterschiedlichen Kontrollflusses zweier Codevarianten lässt sich erkennen, dass die Coverage-Ergebnisse von einer Codevariante nicht einfach auf die andere übertragen werden können.
© Razorcat Development

In Bild 1 ist der Kontrollfluss von zwei Varianten mit der erreichten Code-Coverage dargestellt. Es zeigt, dass Coverage-Ergebnisse aufgrund der unterschiedlichen Kontrollflüsse nicht einfach übertragen werden können, aber die Testfälle aus der Basis-Implementierung genutzt werden könnten, um den in der Variante fehlenden Programmzweig auf der rechten Seite noch abzudecken. Eine Vererbung und Anpassung der Basistestfälle für die Variante spart Aufwand bei der Testerstellung und verhindert redundante Testfälle.

Zusammenführen von Coverage-Testergebnissen

Beim Test mit vielen Varianten stellt sich die Frage, ob sich die Coverage-Ergebnisse zusammenfassen lassen, um den Testaufwand zu reduzieren. Diese Möglichkeit bietet sich zunächst nur für unterschiedliche Tests derselben Variante: Bei identischer Präprozessor-Datei ist die Codestruktur ebenfalls identisch. Damit können die Coverage-Ergebnisse unproblematisch über den Kontrollfluss zusammengeführt werden.

Kombination der Coverage-Ergebnisse aus Unit- und Integrationstest. Der rot unterlegte Programmzweig (links) wird im Integrationstest nicht erreicht, er wird durch einen weiteren Unit-Test geprüft
Bild 2. Kombination der Coverage-Ergebnisse aus Unit- und Integrationstest. Der rot unterlegte Programmzweig (links) wird im Integrationstest nicht erreicht, er wird durch einen weiteren Unit-Test geprüft. Mit beiden Tests lässt sich eine vollständige Coverage für diese Funktion erzielen (rechts).
© Razorcat Development

Beispielsweise lässt sich fehlende Coverage für Statements/Branches oder Bedingungskombinationen einer Funktion durch zusätzliche Unit- und Integrationstests ergänzen. In Bild 2 wird auf der linken Seite die erreichte Coverage aus dem Integrationstest dargestellt. Der rot unterlegte Programmzweig mit der Sonderbehandlung in dieser Funktion wird im Integrationstest nicht erreicht und daher durch einen weiteren Unit-Test ergänzt. In Summe ergibt sich damit eine vollständige Coverage für diese Funktion in dieser Codevariante (rechts im Bild 2).

Hierarchie aus der Source-Codeanalyse

Übersicht der Coverage-Ergebnisse pro Funktion von zwei Quellcodevarianten. Die kombinierte Coverage aus verschiedenen Tests wurde bereits pro Funktion zusammengefasst.
Bild 3. Übersicht der Coverage-Ergebnisse pro Funktion von zwei Quellcodevarianten. Die kombinierte Coverage aus verschiedenen Tests wurde bereits pro Funktion zusammengefasst.
© Razorcat Development

Aus der Analyse des präprozessierten Source-Codes der Varianten ergibt sich eine Hierarchie von Quelldateien mit deren Präprozessor-Varianten und den jeweils darin enthaltenen Funktionen. Bild 3 zeigt eine Quelldatei mit zwei Varianten und den Coverage-Ergebnissen der enthaltenen Funktionen. In dieser Übersicht wurden die kombinierten Coverage-Ergebnisse aus verschiedenen Tests bereits pro Funktion zusammengefasst. Die Übersicht zeigt zwei noch offene Probleme: Obwohl die Funktionen aus derselben Quelldatei hervorgehen, können die Coverage-Ergebnisse aufgrund unterschiedlicher Kontrollflüsse nicht kombiniert werden. Zum anderen fehlt eine Coverage-Aussage auf der Ebene der Originalquelldatei.


  1. Mit Hyper-Coverage zur sicherheitszertifizierten Software
  2. Hyper-Coverage als Lösung

Verwandte Artikel

Razorcat