Der im F-35 verwendete C++-Standard – warum Kampfjets 90 % der C++-Funktionen verbieten
(youtube.com)- Ein Video erklärt, warum Kampfjet- und Raketen-Software, bei der ein einzelner Bug bei Mach 1 fatale Folgen haben kann, den Großteil der C++-Funktionen entfernt und nur vorhersagbaren Code übriglässt
- Es fasst die Geschichte zusammen, wie der mechanische Bombencomputer der F-4, der lange geheime Mikroprozessor der F-14 und die militärischen Sprachkämpfe rund um Jovial, CMS-2 und Ada sowie die Code-Explosion zur Forderung nach einer einheitlichen sicheren Sprache und strengen Standards führten
- Es demonstriert anhand echten Codes, wie der im Zuge der F-35-Entwicklung entstandene JSF-C++-Standard, mit dem Lockheed den Einsatz von C++ statt Ada durchsetzte, Ausnahmen, Rekursion und dynamische Speicherallokation verbietet und sie durch Rückgabecodes, Schleifen und vorab reservierten Speicher ersetzt
- Außerdem wird gezeigt, dass die frühen Missionscomputer der F-35 eine PowerPC-basierte Architektur nutzten, und mithilfe einer Verbindung von X-Plane 12 mit einem selbstgebauten MFD verglichen, wie sich Code, der die JSF-Regeln verletzt, und regelkonformer Code tatsächlich im Flug unterschiedlich verhalten
- Abschließend wird dargestellt, dass der JSF-Standard später zu Sicherheitsstandards wie NASA F-Prime, MISRA und AutoSAR führte und dass heute ein Ansatz auf Basis dieses Erbes mit C++ Core Guidelines und modernem C++ passender ist
Vorstellung des Vortragenden
- Ein Entwickler, der C++-Code für Luft- und Raumfahrtsysteme geschrieben und Demos für die Luftwaffe durchgeführt hat
- Er erklärt das Thema auf Grundlage realer Erfahrung mit C++ in Systemen mit hohen Sicherheitsanforderungen
- Als Demo-Umgebung nutzt er ein selbstgebautes MFD (Multifunction Display), bestehend aus X-Plane 12, einer Web-API, einer Python-UI und einem C++-Backend
Flugsoftware und Umgebungen, in denen Fehler nicht toleriert werden
- Bei gewöhnlichen Anwendungen endet ein Crash oft mit einem Neustart, aber bei Mach-1-Kampfjets und Raketen bedeutet ein einziger Fehler unmittelbar eine Katastrophe
- Mit dem Satz „Bei Mach 1 gibt es keine Zeit, auf den Garbage Collector zu warten“ wird die Echtzeitanforderung betont
- Wenn eine einzige fehlerhafte Zeile zu tödlichen Folgen führen kann, muss bereits die Auswahl der Sprachfeatures selbst als Sicherheitsmechanismus dienen
- Als typisches Beispiel wird die Explosion der Ariane 5 im Jahr 1996 genannt
- Beim Umwandeln eines 64-Bit-Gleitkommawerts für den horizontalen Bias in eine 16-Bit-Ganzzahl trat eine Ausnahme auf, die nicht behandelt wurde
- Die Sprache erzeugte den Fehler normgerecht, aber das System konnte die Ausnahme nicht verarbeiten, was zur sofortigen Zerstörung einer 500-Millionen-Dollar-Rakete führte
- Ausgehend von diesem Fall entschied sich das F-35-Designteam für den Ansatz, Sprachfeatures selbst abzuschneiden, um denselben Fehler nicht zu wiederholen
Geschichte militärischer Software und der Sprachenkrieg
- In der Frühzeit von Kampfflugzeugen wie der F-4 Phantom gab es praktisch noch keine Software
- Der Bombencomputer war eher eine präzise mechanische Vorrichtung aus Zahnrädern und Nocken, und der „Code“ war die Form der Metallnocken selbst
- Beim geheimen Marine-Luftüberlegenheitsprojekt VFX (F-14 Tomcat) änderte sich die Lage
- Erst später wurde bekannt, dass Garrett AiResearch einen Mikroprozessor für die F-14 entwickelt hatte, der der als „erster Mikroprozessor“ bekannten Intel 4004 aus Lehrbüchern zeitlich vorausging
- Um den Schwenkflügel der F-14 optimal zu steuern, war ein leistungsfähiger Mikroprozessor nötig, auf dem rund 2500 Zeilen Microcode für Polynom-Berechnungen liefen
- Danach begann durch die Einführung unterschiedlicher Sprachen bei den Teilstreitkräften ein Wildwuchs an Programmiersprachen
- Die Luftwaffe nutzte Jovial (Jules Own Version of the International Algorithmic Language), eine Sprache aus der ALGOL-Familie
- Die Marine wollte die Sprache der Luftwaffe nicht verwenden und setzte in der F-18 auf CMS-2
- Durch unterschiedliche Sprachen und Hardware-Architekturen wurde Code-Wiederverwendung und Verifikation nahezu unmöglich
- Gleichzeitig wuchs die Softwaregröße pro Flugzeug exponentiell
- Als Zahlenbeispiele nennt der Vortrag etwa 125.000 Zeilen für die F-16A, etwa 1 Million für die B-1 und rund 9 Millionen für die moderne F-35
- Eine Untersuchung des Verteidigungsministeriums ergab, dass mehr als 450 Programmiersprachen im Einsatz waren und kaum eine davon über einen vernünftigen Standard verfügte
Die Durchsetzung von Ada und ihre Grenzen
- Um dieses Chaos zu beheben, entwickelte das Verteidigungsministerium mit Ada eine einheitliche Hochsprache und verpflichtete Projekte stark zu ihrer Nutzung
- Wer in neuen Projekten kein Ada einsetzen wollte, musste nachweisen, warum es mit Ada unmöglich war; andernfalls war kein Vertragszuschlag zu bekommen
- Ada wird als Sprache vorgestellt, die für Bereiche mit hohen Anforderungen an Sicherheit und Zuverlässigkeit sehr gut geeignet ist
- Besonders hervorgehoben wird das Design zur Gewährleistung von Speicher- und Typsicherheit in sicherheitskritischen Systemen wie der Luft- und Raumfahrt
- In den 1990er-Jahren zeigte sich jedoch eine zunehmende Entkopplung von der Realität
- Gleichzeitig dominierten Internet, Windows 95 und kommerzielle Spiele auf Basis von C++ den Mainstream
- Studierende und Entwickler wandten sich statt teurer Ada-Compiler ganz natürlich dem kostenlosen GCC und C++ zu
- Ada-Compiler kosteten mehrere tausend Dollar und waren für Einzelpersonen schwer zugänglich, wodurch auch der Pool an Ada-Fachkräften schrumpfte
F-35 und die Entstehung des JSF-C++-Standards
- Die F-35 (Joint Strike Fighter) wurde von Anfang an als Flugzeug mit sehr hohem Softwareanteil konzipiert
- Komplexe Berechnungen wie Sensor Fusion wurden zu einem Kernbestandteil des Betriebs
- Lockheed Martin schlug dem Verteidigungsministerium vor, für diese Anforderungen den Einsatz von C++ zu erlauben
- Das lief faktisch auf die Bitte hinaus, die bisherige Ada-Pflicht zu lockern, und dafür musste ein Weg gefunden werden, die Risiken von C++ zu beherrschen
- Dabei wirkte auch der C++-Erfinder Bjarne Stroustrup als Berater an der Gestaltung der JSF-C++-Regeln mit
- Er erklärte, direkt an der Ausarbeitung beteiligt gewesen zu sein, und sagte selbst, dass er diesem Standard gegenüber daher „voreingenommen sein könnte“
- Die Kernidee ähnelt dem Gedanken hinter einem „remove before flight“-Tag
- Wie ein Tag, der vor dem Start entfernt werden muss, werden gefährliche C++-Funktionen auf Sprachebene entfernt, sodass nur eine vorhersagbare Teilmenge verwendet wird
- So sollte Ada-ähnliche Sicherheit erreicht werden, während Entwickler dennoch einen Teil der Ausdrucksstärke von C++ nutzen können
Reale Hardware und die GameCube-Analogie
- Die frühen F-35-Blöcke verwendeten Missionscomputer auf Basis von Motorola-G4-PowerPC-Prozessoren
- Diese Information wurde unter anderem in einem Artikel von Aviation Today aus dem Jahr 2003 veröffentlicht
- Auch der GameCube nutzt einen Prozessor aus der PowerPC-Familie, was als interessante hardwareseitige Verwandtschaft auf Befehlssatzebene angeführt wird
- Für eine exaktere Generationseinordnung wäre eine andere Konsole näherliegend, aber zum Verständnis des Prinzips reicht der GameCube-Vergleich aus
JSF-C++-Demo-Umgebung: X-Plane 12 + MFD
- Auf Basis von X-Plane 12 und dem F-35B-Addon von AOA Simulations wird eine Flugsimulator-Umgebung aufgebaut
- Über die neue Web-API von X-Plane werden Flugdatendaten in Echtzeit abonniert und im MFD angezeigt
- Das Frontend besteht aus Python, während das Backend als C++-Plugin umgesetzt ist; so werden regelkonformer und regelverletzender Code direkt verglichen
- Angezeigt werden unter anderem Höhe, Geschwindigkeit, Wind, Flight Envelope und Navigationsdaten als Ergebnisse von C++-Berechnungen
- Im Flug wird absichtlich nicht standardkonformer C++-Code, der Ausnahmen wirft, ausgeführt, sodass das MFD abstürzt und Probleme im Flug entstehen; anschließend wird schrittweise gezeigt, wie die Anwendung der JSF-Regeln dies behebt
Die drei zentralen Einschränkungen von JSF: Ausnahmen, Rekursion, dynamischer Speicher
- Der JSF-C++-Standard betont drei zentrale Einschränkungen: Ausnahmen (Exceptions), Rekursion und dynamische Speicherallokation
- Zusätzlich wird auch eine Obergrenze für die Cyclomatic Complexity von Funktionen festgelegt
1) Verbot von Ausnahmen – AV Rule 208
- JSF AV Rule 208: „Exceptions shall not be used“
- Schlüsselwörter wie
try,catchundthrowwerden vollständig verboten
- Schlüsselwörter wie
- Der wichtigste Grund ist die Nichtdeterministik des Kontrollflusses
- Wie bei Ariane 5 kann das gesamte System unvorhersehbar zusammenbrechen, wenn bei einer Ausnahme das Handling fehlt oder das Timing nicht stimmt
- Bjarne steht moderner C++-Exception-Handling grundsätzlich positiv gegenüber, erklärt aber, dass zur Zeit der JSF-Entwicklung Werkzeugreife und garantierte Echtzeitfähigkeit für eine Unterstützung noch nicht ausreichten
„JSF++ ist für Hard-Real-Time- und sicherheitskritische Anwendungen (Flugsteuerung) gedacht. Wenn Berechnungen zu lange dauern, können Menschen sterben, und mit Ausnahmen lassen sich Antwortzeiten nicht garantieren.“
- Statt Ausnahmen verwendet JSF Rückgabecodes
- Im Beispiel einer Berechnung der Druckhöhe werden für verschiedene Fehlersituationen unterschiedliche Rückgabecodes gesetzt, die der Aufrufer auswertet
- So kann die aufrufende Seite auch bei Berechnungsfehlern oder Zeitüberschreitungen einen sicheren „Fehlerzustand“ darstellen, ohne dass das gesamte Programm abstürzt
2) Verbot von Rekursion – AV Rule 119
- AV Rule 119 verbietet, dass eine Funktion direkt oder indirekt sich selbst aufruft, also Rekursion
- Da sich bei jedem rekursiven Aufruf ein neuer Stack-Frame aufbaut und die maximale Tiefe schwer zu begrenzen ist, steigt das Risiko eines Stack Overflow
- Als Beispiel dient der Binomialkoeffizient, der bei der Berechnung eines Ausweichflughafens im Flugplan verwendet wird
- In der nicht standardkonformen Version wird eine rekursive Implementierung in der Form
C(n, k) = C(n-1, k-1) + C(n-1, k)verwendet - Dadurch hängt die Aufruftiefe von der Eingabe ab, was die Vorhersage der Speicherobergrenze erschwert
- In der nicht standardkonformen Version wird eine rekursive Implementierung in der Form
- In der JSF-konformen Version wird dieselbe Berechnung auf eine iterative Implementierung mit Schleifen umgestellt
- Der Code ist länger und weniger elegant, liefert aber ohne Selbstaufrufe dasselbe Ergebnis
- Dadurch lassen sich maximale Aufruftiefe und Speicherverbrauch statisch deutlich leichter abschätzen
3) Verbot dynamischer Speicherallokation – AV Rule 206
- AV Rule 206: Nach der Initialisierung werden weder Speicher angefordert noch freigegeben
- Heap-Allokationen zur Laufzeit über
new,deleteoder internesnewüber Smart Pointer sind verboten
- Heap-Allokationen zur Laufzeit über
- Der Grund sind zwei zentrale Probleme
- Heap-Allokation bringt eine zeitliche Nichtdeterministik mit sich, weil unklar ist, wie lange sie dauert
- Wird der Heap fragmentiert, kann trotz noch freier Gesamtkapazität die Zuweisung scheitern, weil kein großer zusammenhängender Block mehr gefunden wird
- Als Beispiel wird nicht standardkonformer Code gezeigt, der für die Berechnung von Böen eine Verlaufspuffer für IAS (Indicated Airspeed) mit
std::unique_ptrund einem dynamischen Array anlegt- Dabei wird zur Laufzeit ein neues Array angefordert und damit gegen die JSF-Regeln verstoßen
- Die JSF-konforme Version verwendet stattdessen ein Array fester Größe mit konstant definierter Maximalgröße
- Mit Konstanten wie
MAX_IAS_HISTORYwird die Größe festgelegt, der Speicher einmalig bei der Initialisierung reserviert und anschließend nur der Index rotiert - Dadurch entstehen im laufenden Betrieb keine weiteren Allokationen mehr, was Vorhersagbarkeit bei Zeit und Speicher sicherstellt
- Mit Konstanten wie
4) Obergrenze für Cyclomatic Complexity – AV Rule 3
- AV Rule 3 schreibt vor, dass die Cyclomatic Complexity einer Funktion 20 nicht überschreiten darf
- Die Funktionsdeklaration zählt selbst mit 1 Punkt, und
if,while,for,switchsowie logische AND/OR erhöhen die Komplexität jeweils um 1
- Die Funktionsdeklaration zählt selbst mit 1 Punkt, und
- An der iterativen Implementierung des Binomialkoeffizienten wird gezeigt, wie jedes
if, jede logische Operation und jedefor-Schleife den Komplexitätswert erhöht- Da mit steigender Komplexität Test, Verifikation und Analyse schwieriger werden, ist die Einhaltung einer Obergrenze ein zentrales Ziel des Standards
Das JSF-Erbe: NASA F-Prime, MISRA, AutoSAR
- Die Ideen des JSF-C++-Standards verbreiteten sich später in andere sicherheitskritische Bereiche
- Das Flugsoftware-Framework F-Prime der NASA wurde 2017 veröffentlicht und teilt Regeln wie Verbot dynamischer Speicherallokation, Verbot von Ausnahmen und Verbot von Rekursion
- Auch in der Automobilindustrie setzte sich ein ähnlicher Trend fort
- Standards wie MISRA C++ und AutoSAR (Automotive Open System Architecture) entstanden und legten Sicherheitsregeln für Fahrzeugsoftware fest
- Es wird erwähnt, dass die AutoSAR-C++14-Guidelines ausdrücklich auf JSF Bezug nehmen, was zeigt, dass der Einfluss von JSF bis in die Automotive-Software reicht
- Moderne Autos sind faktisch „Computer auf Rädern“, sodass solche Sprach-Teilmengen und Coding-Regeln eine zentrale Grundlage für Sicherheit bilden
Fazit: Woran sollte man sich heute orientieren, wenn man C++ verwendet?
- Der JSF-C++-Standard wird als technische Leistung dargestellt, die für ihre Zeit eine komplexe Sprache auf eine vorhersagbare Teilmenge reduzierte und so Sicherheit auf dem Niveau von Flugsteuerung in Kampfjets ermöglichte
- Gleichzeitig empfiehlt Bjarne Stroustrup heutigen Entwicklern, sich an den C++ Core Guidelines und modernem C++ zu orientieren
- Denn Sprache und Toolchain von C++ haben sich in den vergangenen Jahrzehnten weiterentwickelt, und für Features wie Ausnahmen und Smart Pointer gibt es heute oft sichere Einsatzumgebungen
- Trotzdem bleibt JSF ein wichtiges Beispiel für die Denkweise, eine Sprache nicht durch Ergänzen, sondern durch „Entfernen“ zu kontrollieren
- Die abschließende Botschaft lautet: Nicht was man hinzufügt, sondern was auf die „remove before flight“-Liste gehört, ist der Kern des Entwurfs sicherheitskritischer Systeme
8 Kommentare
Da es für militärische Zwecke gedacht ist, könnte das Ergebnis der Abwägung zwischen niedrigen Hardware-Spezifikationen und fortgeschrittenen Funktionen eben C++ gewesen sein.
gnat-llvmauf LLVM-Basis. Eine Open-Source-IDE wird ebenfalls bereitgestellt.Studierende und Entwickler haben es wohl nicht gelernt, weil es die Sprache des Verteidigungsministeriums ist und die Nachfrage gering war, nicht weil die Tools teuer wären.
Insgesamt geht es offenbar um Techniken, mit denen man insgesamt besser vorhersagbaren Code schreibt.
C++ dient hier nur als Beispiel; dieselben Punkte sind in anderen Sprachen, etwa bei GC, teils sogar noch problematischer. Schade ist, dass es dadurch so verstanden werden kann, als würde über die Grenzen von C++ an sich gesprochen.
Wenn man ohnehin weder dynamische Speicherzuweisung noch Exception-Handling in C++ verwenden will, bleibt bei mir die Frage, ob es nicht viel einfacher und schneller wäre, stattdessen C zu verwenden und nach diesen Prinzipien zu schreiben.
Stimmt, ich frage mich auch, warum es C++ und nicht einfach C ist.
Modernes C++ ist zwar wirklich stabil, aber wenn es nicht unbedingt C++ sein muss, könnte man vielleicht auch überlegen, eine andere Sprache zu wählen, die noch stabiler ist.
Lasst uns die RUST-Syntax STRICT verwenden!
Hacker-News-Kommentar
Es umfasst 142 Seiten und zeigt, welche Einschränkungen es in der tatsächlichen Entwicklung von Flugzeugsoftware gibt.
Ich frage mich allerdings, wie die Ausnahmen bei den „shall“-Regeln gehandhabt werden. In solchen Großprojekten zeigen gerade die Ausnahmefälle, wie wirksam ein Standard wirklich ist.
2005 war das vielleicht in Ordnung, aber ich frage mich, wie das heute in Umgebungen wie KI-basierten Drohnen angewendet wird.
Besonders Dinge wie das Verbot von
stdio.hwirken erst einmal fremd, aber je weiter man liest, desto eher denkt man: „Ja, ergibt Sinn.“a = a;, die ich in echtem MISRA-Code gesehen habe.Das ist ein Trick, um ungenutzte Parameter nicht zu entfernen, aber ich bin nicht sicher, ob solche Regeln wirklich gute Codequalität garantieren.
Die JSF-Richtlinien sind ein Dokument von 2005 und haben damit ihre zeitbedingten Grenzen.
Interessant ist auch, dass Bjarne Stroustrup kürzlich das Konzept der „C++-Profile“ vorgestellt hat.
Letztlich könnten solche Styleguides auch eine Frage des ästhetischen Geschmacks sein.
(void)a;zu verwenden.Wirkliche Sicherheit entsteht durch Designqualität und Fail-Safe-Architektur.
_ = a;behandelt.Dazu gibt es auch ein relevantes Issue.
Sie dienen eher als gemeinsamer Maßstab für Reviews.
Der Kernpunkt ist Mission Assurance.
Wenn man Stack oder Heap verwendet, ändern sich die Speicheradressen von Variablen, und wenn eine bestimmte Zelle ausfällt, entstehen Probleme.
Wenn alle Variablen feste Adressen haben, kann man nur die defekte Zelle per Patch verlagern und die Mission fortsetzen.
Lokale Variablen, Parameter, Rücksprungadressen usw. benötigen letztlich den Stack.
Rekursion wäre ebenfalls unmöglich, und temporäre Variablen wären eingeschränkt.
Deshalb ist diese Behauptung nicht realistisch.
Die Adressbereiche von Stack und Heap kann man zwar festlegen, aber ich bezweifle, dass das eine wirklich überzeugende Begründung ist.
Ich hinterlasse ähnlich auch Logs wie „Dieser Fall sollte niemals eintreten“.
In großen Systemen ist ein solches Logging von Ausnahmefällen beim Debugging sehr nützlich.
Man muss alle Fälle explizit behandeln, und wenn ein enum-Wert hinzugefügt wird, warnt der Compiler.
_STOPoder_CRASHhinzu, um sofort einen Debug-Abbruch auszulösen.Das zwingt dazu, Probleme direkt zu beheben.
Zusammengefasst lautete der Grund: „Es gab zu wenige Ada-Entwickler und Toolchains.“
Heute ist die Offenheit gegenüber sprachlicher Vielfalt jedoch größer, sodass Ada eher akzeptiert werden könnte.
Dass NVidia SPARK übernommen hat, wirkt ebenfalls wie ein Anzeichen einer Ada-Renaissance.
Wenn das DoD Ada vorgeschrieben hätte, wären Universitäten und Unternehmen gefolgt.
Sprachen kann man lernen, und mit Ada wäre die Zuverlässigkeit der F-35 womöglich höher gewesen.
Dazu gehören AdaCore, GHS, PTC ApexAda, DDC-I, Irvine, OC Systems und RR Software.
Früher waren Ada-Compiler eine kostenpflichtige Option, während C/C++ standardmäßig mitgeliefert wurden, weshalb Schulen und Unternehmen sich für C++ entschieden.
AdaCore hat viel zur ISO-Standardisierung und zur Verbreitung von Open Source beigetragen.
Früher wurde es übermäßig kritisiert, heute wirkt es auf mich eher attraktiv.
Dateistruktur und Build-Flags waren kompliziert, und im RedMonk-Sprachranking tauchte Ada nicht auf.
Manche Funktionen waren interessant, aber es fühlte sich nicht nach einer modernen Sprach-Erfahrung wie Rust an.
Zertifizierungsrahmen wie DO-178C sind wichtig.
Vielleicht ist das sogar der richtige Weg, um hochzuverlässigen Code zu schreiben.
Sie behandeln Themen wie Reverse Engineering, Obfuskation und Compiler.
Muss man so etwas wirklich extra festschreiben?
Ob LM selbst einen gebaut hat oder ein kommerzielles Produkt verwendet, würde mich interessieren.