MISRA-Konformität So erstellen Sie sicherheitskritische, MISRA-konforme C- und C++-Systeme
Anbieter zum Thema
Sicherheitskritische Anwendungen werden oft in C oder C++ implementiert. Doch wie lässt sich die Konformität mit MISRA-C- und C++-Richtlinien erzielen?

Bei der Implementierung sicherheitskritischer Anwendungen wird in der Regel entweder C oder C++ als Programmiersprache verwendet. Aber in vielen Fällen, in denen das sicherheitskritische System in C++ geschrieben wurde, sind auch einige C-Komponenten beteiligt.
Dies kann die Implementierung der Standardbibliothek sein, das RTOS oder ein Utility-Modul, auf das mehrere Projekte gemeinsam zugreifen. Weder in den MISRA-Richtlinien noch im MISRA-Compliance-Dokument ist explizit definiert, welche Maßnahmen in einer solchen Situation zu ergreifen sind, um das erwartete Sicherheitsniveau des Gesamtsystems zu erreichen.
Konformitätsnachweis mit MISRA C und MISRA C++
Dieser Beitrag beschreibt einen möglichen Ansatz zum Nachweis der Konformität mit MISRA C und MISRA C++. Der Fokus liegt dabei auf den möglichen Nuancen und Herausforderungen, die bei dem Versuch auftreten, die Konformität mit den MISRA C- und C++-Richtlinien zu erzielen.
Die Sicherheitsnormen IEC 61508 und ISO 26262
Nach Sicherheitsnormen wie IEC 61508 [1] oder ISO 26262 [2] muss Software, die Teil sicherheitskritischer Systeme ist, angemessen verifiziert werden. Dazu empfehlen sie verschiedene Methoden, wie die statische Analyse des Quellcodes. Techniken wie Kontrollflussanalyse, Datenflussanalyse, statische Codeanalyse und statische Analyse auf der Grundlage einer abstrakten Interpretation des Codes werden sowohl auf der Ebene der Softwareeinheit als auch auf der Ebene der Softwareintegration (dringend) angeraten.
Die Programmierung eines komplexen Systems verlangt manchmal die Verwendung mehrerer Programmiersprachen. Die Kombination von C und C++ in einem System ist nicht ungewöhnlich, da diese Sprachen so konzipiert wurden, dass sie ein gewisses Maß an Kompatibilität und Interoperabilität gewährleisten. Heutzutage bietet jeder größere Compilerhersteller kompatible C- und C++-Compiler an, die es ermöglichen, diese Sprachen in einem System zu mischen – sofern bestimmte Regeln eingehalten werden [3].
Sicherheitskritische Systeme, die sowohl C als auch C++ verwenden, müssen – wie alle anderen sicherheitskritischen Systeme – die Anforderungen der Sicherheitsnormen erfüllen. Daher sollten die Methoden der statischen Codeanalyse sowohl auf der Ebene der Softwareeinheit als auch auf der Integrationsebene angewendet werden. Da MISRA C und MISRA C++ die Richtlinien der ersten Wahl für den Einsatz dieser Sprachen in kritischen Systemen sind, liegt es nahe, diese auch für die Verifikation der gemischten C- und C++-Codebasis zu verwenden. Leider definieren weder die MISRA-C- und C++-Richtlinien noch das MISRA-Compliance-Dokument explizit die Maßnahmen, die in einer solchen Situation zu ergreifen sind, um das erwartete Sicherheitsniveau des Gesamtsystems zu erzielen.
Szenario: C und C++ in einem einzigen System
Das häufigste Szenario für die gleichzeitige Verwendung von C und C++ in einem einzigen System besteht darin, dass einige Module (Subsysteme) in C und andere Module (Subsysteme) in C++ geschrieben sind. Letztere können mit den ersteren über eine wohldefinierte API interagieren, die als ein Satz von Funktionen implementiert ist.
Betrachten wir das Beispielsystem, das aus dem in C geschriebenen Modul der Sensorbibliothek (Bild 1) und der in C++ geschriebenen GUI- und Controller-Anwendung (Bild 2) besteht. Wir erörtern nun die möglichen Ansätze, um die Einhaltung der MISRA-C- und MISRA-C++-Richtlinien für ein solches Projekt zu erreichen.
Wir gehen davon aus, dass das für die statische Codeanalyse gewählte Werkzeug Folgendes analysieren kann:
- Sowohl C- als auch C++-Code,
- Code gegen die MISRA-C-Richtlinien,
- Code gegen die MISRA-C++-Richtlinien.
Ein System als eine Summe von Subsystemen
Im ersten Ansatz versuchen wir, die gesamte MISRA-Konformität unseres Systems zu beanspruchen, indem wir behaupten, dass sie gegeben ist:
- MISRA-C-Konformität des C-Moduls (Bild1),
- MISRA-C++-Konformität des C++-Moduls (Bild 2).
Wir versuchen, diesen Ansatz zu implementieren, indem wir C- und C++-Module als getrennte Subsysteme betrachten und die statische Codeanalyse so konfigurieren, dass sie durchgeführt werden kann:
- MISRA-C-Prüfung der Quell- und Headerdateien, aus denen das C-Modul besteht (Bild 1)
- MISRA-C++-Prüfung der Quell- und Headerdateien, aus denen das C++-Modul besteht (Bild 2).
Allerdings berücksichtigt dieser Ansatz nicht, dass die Headerdateien der Sensorbibliothek (Bild 1) als C++-Code kompiliert werden, wenn sie in die C++-Quelldateien der GUI- und Controller-Anwendung (Bild 2) enthalten sind. Das bedeutet: Unser ursprünglicher Ansatz ist unvollständig, da er die MISRA-C++-Konformität dieser Headerdateien nicht gewährleistet.
Gemeinsame Headerdateien sind sowohl C als auch C++
Im nächsten Ansatz modifizieren wir unsere ursprüngliche Lösung, indem wir gemeinsam genutzte Header als Elemente sowohl des C-Moduls (Bild 1) als auch des C++-Moduls (Bild 2) analysieren. Dies führt dazu, dass die gemeinsam genutzten Header sowohl nach den MISRA-C- als auch nach den MISRA-C++-Richtlinien untersucht werden.
Bei diesem Ansatz muss man darauf achten, dass der Code sowohl mit MISRA C als auch mit MISRA C++ konform ist. Dateien, die sowohl als C- als auch als C++-Code kompiliert werden, müssen entsprechend geschrieben werden, z. B. durch bedingte Kompilierung, um sicherzustellen, dass die externe C-Deklaration zur Benennung von Funktionen zum Einsatz kommt, wenn die Datei als C++ kompiliert wird, oder um das C-Schlüsselwort _Noreturn vom C++-Attribut [[noreturn]] zu unterscheiden. Außerdem müssen semantische Unterschiede zwischen den Sprachen C und C++ berücksichtigt werden, da derselbe Quellcode je nach Sprache eine unterschiedliche Bedeutung haben kann, was wiederum zu unvorhergesehenem Verhalten des Codes führen kann. Beispiele für solche Situationen sind:
- Typ eines Zeichens (Literal),
- Typ einer Aufzählungskonstanten,
- Verkettung von const-Objekten.
Um solche Probleme zu vermeiden, ist es sinnvoll, die gemeinsamen Header-Dateien so einfach wie möglich zu halten und im Idealfall nur die API-Definition aufzunehmen, die für die Interoperabilität der Module erforderlich ist. Dazu gehören Typdefinition und nicht-definierende Erklärungen von Funktionen. Andere Definitionen, insbesondere solche von Funktionen (einschließlich Funktionen mit interner Verknüpfung), sollten nicht in den gemeinsamen Headerdateien enthalten sein, damit es nicht zu einem unerwarteten Verhalten kommt.
Werden die gemeinsamen Headerdateien so einfach wie möglich gehalten, trägt dies auch zur Konformität mit MISRA C und MISRA C++ bei, da einige der Richtlinien, die im Kontext einer bestimmten Sprache definiert wurden, sich nicht strikt auf Code anwenden lassen, der sowohl in C als auch in C++ kompiliert wurde. Die MISRA-C:2012-Regel 11.9 schreibt vor, dass „NULL" die einzig zulässige Form der Null-Zeiger-Konstante ist, während MISRA C++:202x in solchen Fällen die Verwendung von „nullptr" vorschreibt. Die MISRA-C:2012-Regel 10.x enthält mehrere Anforderungen an den Gebrauch von Ausdrücken arithmetischer Typen unter Verwendung des Konzepts des essentiellen Typmodells – wobei diese Anforderungen nicht direkt auf C++-Code anwendbar sind, da das essentielle Typmodell nicht für die Verwendung im Kontext der Sprache C++ vorgesehen ist.
Dieser Ansatz ist vollständiger als der vorherige, da er die gemeinsam genutzten Header als C- oder C++-Code betrachtet, je nachdem, in welcher Sprache die Übersetzungseinheit, zu der sie gehören, kompiliert wurde. Sowohl MISRA C als auch MISRA C++ enthalten eine Reihe von Richtlinien, die auf das gesamte System angewendet werden müssen. In MISRA Compliance:2020 wird erwähnt, dass diese Regeln bei der Analyse der gesamten Codebasis zu überprüfen sind, was in unserem Fall C- und C++-Code einschließt. Leider erfüllt unser derzeitiger Ansatz diese Kriterien nicht, da er keine Analyse auf Gesamtsystemebene beinhaltet, d. h. es gibt keine Analyse, die die Integration des C-Moduls (Bild 1) und des C++-Moduls (Bild 2) verifiziert.
Überprüfung der Konformität mit Regeln auf Systemebene
Im nächsten Schritt betrachten wir die Anwendbarkeit der MISRA-C- und MISRA-C++-Regeln auf Systemebene auf die gesamte Codebasis, die sowohl C-Module als auch C++-Module enthält. Die aktuelle Version von MISRA C:2012 schließt 53 Richtlinien auf Systemebene ein, MISRA C++:2008 beinhaltet keine explizite Klassifizierung des Umfangs, und die für das öffentliche Review veröffentlichte Version von MISRA C++:202x umfasst 35 Richtlinien auf Systemebene. Unter den Richtlinien auf Systemebene gibt es einige, die nicht auf gemischte C- und C++-Codebasen anwendbar sind.
Die MISRA-C:2012-Regel 5.8 verlangt, dass alle Bezeichner von Funktionen mit externer Verknüpfung eindeutig sein müssen. Während dies im Kontext der Sprache C sinnvoll ist, sollte es nicht auf C++-Code angewendet werden, denn dadurch würde die Verwendung von Funktionsüberladungen völlig unmöglich.
Darüber hinaus gibt es die MISRA-C:2012-Regel 17.11, welche die Verwendung des Funktionsspezifizierers "_Noreturn" für Funktionen vorschreibt, die niemals zurückkehren. Diese Regel kann nicht wörtlich auf C++ angewendet werden, da in dieser Sprache kein solches Schlüsselwort existiert. Um dem Geist der MISRA-C:2012-Regel 17.11 zu folgen, sollte das Attribut "[[noreturn]]" in C++-Code zum Einsatz kommen. Solche Richtlinien dürfen nicht wörtlich auf eine gemischte Codebasis aus C und C++ angewendet werden.
Es gibt einige Richtlinien auf Systemebene, die nur in MISRA C oder MISRA C++ existieren, aber sie können sicher zur Überprüfung der gesamten Codebasis herangezogen werden, da sie entweder nie für eine andere Sprache gelten oder ihre Anwendung sicher und ohne unerwünschte Folgen möglich ist. MISRA C++:202x enthält eine Regel, die verlangt, dass bei der Deklaration von Mitgliedsfunktionen die Funktionsspezifikationen "virtual", "override" und "final" angemessen verwendet werden. Es ist klar, dass die Regel niemals einen nicht konformen Code in der Sprache C auslösen wird, in der es diese Mitgliedsfunktionen nicht gibt. Die MISRA-C:2012-Regel 2.5 verbietet unbenutzte Makrodefinitionen. Obwohl diese Regel nicht in den MISRA-C++-Richtlinien enthalten ist, wird ihre Anwendung auf den C++-Code keinen Schaden anrichten. Solche Richtlinien lassen sich ohne Bedenken auf die gemischte C- und C++-Codebasis anwenden.
Einige Richtlinien auf Systemebene verlangen eine Datenfluss- und Kontrollflussanalyse des Quellcodes, um festzustellen, ob der Code konform ist oder nicht. In vielen Fällen enthalten sowohl MISRA C als auch MISRA C++ übereinstimmende Versionen der Richtlinie, z. B:
- Der Wert eines Objekts darf nicht gelesen werden, bevor er gesetzt wurde (MISRA-C:2012-Regel 9.1, MISRA-C++:2008-Regel 8-5-1, MISRA-C++:202x-Regel),
- Funktionen dürfen sich weder direkt noch indirekt selbst aufrufen (MISRA-C:2012-Regel 17.2, MISRA-C++:2008 Regel 7-5-4, MISRA-C++:202x-Regel),
- Ein Objekt darf keinem überlappenden Objekt zugewiesen werden (MISRA-C:2012-Regel 19.1, MISRA-C++:2008-Regel 0-2-1, MISRA-C++:202x-Regel).
Um zu überprüfen, ob der Code diesen Regeln entspricht, muss eine statische Codeanalyse für die gesamte Codebasis mit einem statischen Codeanalysetool erfolgen, das den Daten- und Kontrollfluss zwischen C- und C++-Code verfolgen kann. Dies ist notwendig, um eine Nichtkonformität zu melden, z. B. wenn ein nicht initialisiertes Objekt vom C-Code an den C++-Code übergeben wird, der seinen Wert liest.
Die Anwendung von MISRA C und MISRA C++ auf der Systemebene auf eine gemischte C- und C++-Codebasis erfordert eine gründliche Analyse dieser Richtlinien, um zu bestimmen, welche anzuwenden sind und welche nicht. Eine solche Analyse würde den Rahmen dieser Arbeit sprengen – hier gehen wir davon aus, dass ein Satz von auf Systemebene anwendbaren Richtlinien auf die gesamte Codebasis angewendet wird.
Weder die MISRA-C- und C++-Richtlinien noch das MISRA-Konformitätsdokument definieren explizit, was es bedeutet, dass ein Projekt, das gemischten C und C++ Code enthält, mit MISRA C und MISRA C++ konform ist. Es ist daher fraglich, ob ein solches Projekt offiziell MISRA-Konformität beanspruchen kann. Dennoch sollte man versuchen, die MISRA-Richtlinien auf ein solches Projekt anzuwenden. Die Umsetzung des vorgeschlagenen Ansatzes, bei dem der gesamte in C kompilierte Code gegen die MISRA-C-Richtlinien und der gesamte in C++ kompilierte Code gegen die MISRA-C++-Richtlinien und zusätzlich die gesamte Codebasis gegen die auf Systemebene anwendbaren MISRA-C- und C++-Richtlinien geprüft wird, kann ein hohes Maß an Vertrauen in das Sicherheitsniveau des Gesamtsystems schaffen. Er eignet sich dazu, um die Konformität mit den relevanten Normen zur funktionalen Sicherheit zu behaupten. (mk)
(ID:49760835)