Hochwertige Embedded-Software Volle Kontrolle über die RISC-V-Codebasis

Von Rafael Taubinger, Senior Product Marketing Manager, IAR Systems 9 min Lesedauer

Anbieter zum Thema

Die Herausforderung der Embedded-Industrie, immer schneller immer komplexere Software zu liefern, erhöht das Risiko von Softwarefehlern, die die Qualität der Produkte beeinträchtigen und Sicherheitsprobleme verursachen können. Für die RISC-V-Codebasis gilt das umso mehr, da der Quellcode oft wiederverwendet oder von anderen Architekturen migriert wird. Dabei soll das gleiche Ergebnis erzielt werden, obwohl sich die meisten Entwickler erst mit der Architektur, dem neuen Befehlssatz und den umfangreichen Erweiterungen vertraut machen müssen.

Prototyp eines RISC-V Mikroprozessors aus dem Jahr 2013(Bild:   / CC0)
Prototyp eines RISC-V Mikroprozessors aus dem Jahr 2013
(Bild: / CC0)

Wenn wir davon sprechen, die Kontrolle über die RISC-V-Codebasis zu übernehmen, gibt es zwei Aspekte. Zum einen geht es um die Wiederverwendung der Codebasis für zukünftige Projekte. Der zweite Aspekt ist, dass schlechte Code-Qualität tatsächlich ein weitverbreitetes Problem ist. Es gibt eine ganze Reihe von Beweisen dafür, dass schlechte Programmierpraktiken direkt zu Sicherheitslücken führen. Das heißt, dass jeder Entwickler und jedes Unternehmen die Qualität des Codes verbessern muss, damit die Software auf lange Sicht fehlerfrei oder zumindest so fehlerarm wie möglich ist.

Code wiederverwenden

Bild 1: Boehms COCOMO-Methode für nichtlineare Effekte bei der Wiederverwendung. (Bild:  Rose-Hulman Institute of Technology)
Bild 1: Boehms COCOMO-Methode für nichtlineare Effekte bei der Wiederverwendung.
(Bild: Rose-Hulman Institute of Technology)

Die in Bild 1 dargestellte COCOMO-Methode von Boehm schätzt ab, wie stark die relativen Kosten für die Erstellung des Codes davon abhängen, wie viele Änderungen an der wiederverwendeten Software vorgenommen werden. Die x-Achse gibt an, wie viel Prozent des wiederzuverwendenden Codes geändert werden, während die y-Achse die prozentualen Kosten zeigt, die bei einem neu geschriebenen Code entstehen würden. In zwei der drei Codebeispiele müsste nicht viel an dem vermeintlich wiederverwendeten Code geändert werden, um plötzlich 50 % des Aufwands zu erreichen, der beim Neuschreiben des Codes anfällt. Der springende Punkt dabei ist, dass die Wiederverwendung von Code nur dann kosteneffizient ist, wenn dieser von hoher Qualität und gut designt ist.

Fokus auf Code-Qualität

Es gibt mehrere Gründe, warum die Code-Qualität ein wichtiges Thema ist: Erstens kann je nach Reifegrad der Entwicklungsabteilung bis zu 80 % der Zeit beim Debuggen verbracht werden. Wenn Fehler schnell isoliert werden könnten, bevor sie in ein formales Build einfließen, wäre die Fehlerinjektionsrate geringer. Das wiederum bedeutet, dass die Qualitätsanforderungen des Unternehmens viel schneller erfüllt werden können. Das bedeutet aber auch, dass der Code insgesamt weniger Fehler aufweist, was ihn zu einem guten Kandidaten für die Wiederverwendung macht. Denn bei einer erneuten Verwendung des Codes ist die Gefahr geringer, dass ein zuvor unentdeckter Fehler aufgespürt wird. Qualitativ hochwertiger Code ist aufgrund der geringeren Anzahl von Fehlern leichter zu pflegen und – wenn er guten Software-Engineering-Prinzipien folgt – leichter zu erweitern, sodass die Wiederverwendung tatsächlich zu schnelleren Folgeprojekten führt. Es ist auch einfacher, Sicherheitszertifizierungen zu erhalten, wenn die Anwendung das erfordert. Letztlich führt eine höhere Code-Qualität zu einer geringeren „technischen Schuld“ bei der Wiederverwendung von Code.

Gängige Programmierstandards

Es gibt eine ganze Reihe von Programmierstandards, aber nur wenige, die weitverbreitet sind. MISRA C ist ein Softwareentwicklungsstandard für die Programmiersprache C, der von der Motor Industry Software Reliability Association entwickelt wurde. Das Ziel ist die Sicherstellung von Codesicherheit, Portabilität und Zuverlässigkeit im Zusammenhang mit Embedded-Systemen, insbesondere mit Systemen, die in ISO C programmiert sind.

Die erste Ausgabe des MISRA C-Standards, „Guidelines for the use of the C language in vehicle-based software“, wurde 1998 erstellt und ist offiziell als MISRA C:1998 bekannt. Im Jahr 2004 und 2012 wurde der Standard aktualisiert, um weitere Regeln hinzuzufügen. Außerdem gibt es einen MISRA C++ 2008 Standard, der auf C++ 2003 basiert.

Einige gute Standardregeln für die Programmierung finden sich auch in der CWE Common Weakness Enumeration von MITRE . Die Liste wurde erstellt, als die Mitarbeiter von mitre.org eine Umfrage durchführten, welche Arten von Fehlern Entwickler versehentlich in ihren Code einbringen. Überraschenderweise neigen Entwickler aller Couleur – ob Web, App, Desktop oder Embedded – dazu, die gleichen Fehler zu machen. So entstand die CWE, eine Liste dieser häufigen Fallstricke, die Entwickler vermeiden sollten, etwa Allokationen ohne Deallokationen in C++-Code (oder sogar in C-Code). Auch Funktionen, die ohne Prototyping verwendet werden, sind ein interessanter Punkt in Bezug auf gute Programmierpraxis. Wenn die Funktion nicht prototypisiert wird, findet zur Kompilierzeit keine strenge Typüberprüfung statt, aber der Code kann auch weniger effizient sein, da die Regeln der Sprache C besagen, dass ohne Prototyp alle Argumente zu Integerwerten gemacht werden. Dadurch kann es zu Casting und Fließkommaoperationen kommen, wenn die MCU nicht über eine FPU verfügt. Deshalb sollte immer ein Prototyp verwendet werden. Der wichtigste Punkt der CWE ist jedoch, dass sie riskantes und schlechtes Programmierverhalten aufdeckt.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung. Die Einwilligungserklärung bezieht sich u. a. auf die Zusendung von redaktionellen Newslettern per E-Mail und auf den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern (z. B. LinkedIn, Google, Meta).

Aufklappen für Details zu Ihrer Einwilligung

SEI CERT C und C++ definiert ebenfalls häufige Schwachstellen, die sich aus Fallstudien ergeben, wie die Überprüfung von Floats auf Out-of-Bounds-Bedingungen und die Sicherstellung, dass keine Konstante überschrieben wird. Außerdem werden Stilkonventionen vorgeschrieben, um den Code lesbarer und verständlicher zu machen.

Praktische Beispiele für MISRA C 2012

MISRA C 2012 wird häufig zur Sicherung der Code-Qualität in Embedded-Anwendungen verwendet. Ein Blick auf einige Regeln und Richtlinien lohnt sich, um besser zu verstehen, welche Auswirkungen die Programmierstandards auf den Quellcode haben.

Laut Directive 4.6 ist es zum Beispiel nicht erlaubt, einen primitiven Datentyp zu verwenden. Auf den ersten Blick mag das seltsam erscheinen, aber wenn man den Grund dafür versteht, ist es sehr sinnvoll. Verschiedene Compiler behandeln Dinge wie int unterschiedlich, sowohl in Bezug auf die Größe als auch auf die Vorzeichenbehaftung. Das kann auch die Überprüfung von Code erschweren. Der Reviewer wird sich fragen, ob der ursprüngliche Autor des Codes verstanden hat, wie der Compiler diesen Code interpretiert. Wenn auf die Verwendung primitiver Typen verzichtet wird, wird der Code für alle Compiler und Architekturen einheitlich.

Bild 2: MISRA C 2012 – Codebeispiel für Regel 13.(Bild:  IAR System)
Bild 2: MISRA C 2012 – Codebeispiel für Regel 13.
(Bild: IAR System)

In den meisten Fällen werden Entwickler etwas wie uint16_t verwenden, das dem Compiler mitteilt, dass es sich bei der Variablen um eine vorzeichenlose 16-Bit-Zahl handelt, da die Größe und Vorzeichenhaftigkeit ausdrücklich im Variablentyp angegeben sind. Diese sind Teil von stdint.h.

Eine weitere interessante Richtlinie ist Regel 13, die besagt, dass die rechte Seite eines AND- oder OR-Operators keine Zusätze enthalten darf. Der Codeschnipsel aus Bild 2 sieht zwar völlig korrekt aus, ist es aber nicht.

Das Problem ist, dass die rechte Seite nur ausgeführt wird, wenn der Ausdruck auf der linken Seite falsch ist. Nur dann wird der Zeiger p nachinkrementiert. Beim Schreiben von Code kann dieses Verhalten leicht vergessen werden, und jeder, der den Code prüft, testet oder pflegt, muss sich über die Auswirkungen des Codes im Klaren sein. Kommentare in diesem Abschnitt des Codes könnten natürlich helfen, aber in der Realität ist dieser nur selten gut dokumentiert.

Bild 3: MISRA C 2012 – Codebeispiel für Regel 14.(Bild:  IAR Systems)
Bild 3: MISRA C 2012 – Codebeispiel für Regel 14.
(Bild: IAR Systems)

Ein weiteres gutes Beispiel ist Regel 14, die besagt, dass der Inhalt einer if- oder while-Anweisung in geschweifte Klammern eingeschlossen werden muss. Der Codeschnipsel in Bild 3 ist ein Beispiel dafür.

Es ist schwer zu erkennen, ob die Anweisung z=1 Teil des else-Blocks sein soll. Das liegt daran, dass sie genauso eingerückt ist wie die vorherige Anweisung. Sollte dies beabsichtigt sein, handelt es sich um einen Fehler, da die Anweisung eindeutig nicht so in den Codeblock gehört, wie sie geschrieben ist. Mit der Regel wird diese Art von Programmierfehlern vermieden. Und das ist nur ein kleiner Auszug aus den über 200 Regeln, die MISRA C zur Verfügung stellt, um den Code zuverlässiger und portabler zu machen und so das Design zukunftssicher zu gestalten.

Schnell zu besserem Code

Der schnellste Weg zur Verbesserung der Code-Qualität ist die Verwendung von Code-Analyse-Tools. Bei der Erstellung einer für funktionale Sicherheit zertifizierten Anwendung ist der Einsatz einer statischen Analyse sogar vorgeschrieben. Diese Art von Tools hilft, die häufigsten Fehlerquellen im Code ausfindig zu machen. Aber sie hilft auch, Probleme zu finden, über die sich Entwickler in der Regel keine Gedanken machen, wenn sie ihren Code schreiben, insbesondere wenn sie nur ein Code-Gerüst erstellen, um eine Anwendung zum Laufen zu bringen. Diese Art von Tools hilft nachweislich bei der Entwicklung von besserem Code, weil sie Programmierstandards durchsetzen.

Bild 4: Standard- und Regelauswahl.(Bild:  IAR Systems)
Bild 4: Standard- und Regelauswahl.
(Bild: IAR Systems)

Abhängig von der Qualität der statischen Analyselösung kann das Tool viele andere potenzielle Probleme aufdecken, während der Code noch am Schreibtisch geprüft wird. Um zu sehen, wie es funktioniert, sehen wir uns das C-STAT Static Analysis Tool von IAR Systems (das in IAR Embedded Workbench für RISC-V integriert ist) in Aktion an. C-STAT bezieht seine Regeln aus den MISRA C 2004 und 2012 Regelsätzen, dem MISRA C++ 2008 Regelsatz, der Common Weakness Enumeration (CWE) von MITRE und von SEI CERT C. Bild 4 zeigt die verfügbaren Regeln, die aktiviert werden können, um die Einhaltung der Programmierstandards durchzusetzen.

Bild 5: Meldungen aus der Code-Analyse.(Bild:  IAR Systems)
Bild 5: Meldungen aus der Code-Analyse.
(Bild: IAR Systems)

Es kann nach Kategorien aufgeschlüsselt werden, um nur die Regeln auszuwählen, die für das jeweilige Projekt relevant sind. Darüber hinaus ist es möglich, diese Auswahl auf Gruppen-, Datei-, Funktions- oder sogar auf Ebene einer einzelnen Zeile zu überschreiben, um eine vollständige Granularität über das, was geprüft wird, zu erhalten. Sobald die Tools konfiguriert sind, kann das Projekt (oder eine Gruppe oder eine einzelne Quelldatei) analysiert werden. Nach Abschluss der Analyse ist es möglich, die einzelnen Dateien zu untersuchen, um die aufgetretenen Probleme zu überprüfen:

Bild 6: Kontextsensitive Hilfe in C-STAT.(Bild:  IAR Systems)
Bild 6: Kontextsensitive Hilfe in C-STAT.
(Bild: IAR Systems)

Das erkannte Problem CERT-ERR33-C_c in Bild 5 gehört zu den CERT-C-Regeln. Die meisten Regeln sind selbsterklärend, aber weitere Informationen können in den Anwenderhandbüchern oder in der kontextsensitiven Hilfe (F1) gefunden werden, wie in Bild 6 dargestellt.

Bild 7: Codebeispiele für fehlerhafte und bestandene Prüfungen.(Bild:  IAR Systems)
Bild 7: Codebeispiele für fehlerhafte und bestandene Prüfungen.
(Bild: IAR Systems)

Im Hilfefenster erhält der Entwickler eine vollständige Beschreibung des Problems, die Einschätzung, inwieweit es sich um einen echten Fehler handelt, und den Schweregrad, der bei Manifestierung der Fehler eintritt, sowie alle Codierungsstandards, gegen die er verstößt. Am wichtigsten ist jedoch, dass unten, wie in Bild 7 gezeigt, ein bis drei Codebeispiele zu sehen sind. Sie enthalten ein schlechtes Beispiel und zeigen, wie dieses schlechte Beispiel korrigieren werden kann, damit es die Prüfung besteht und der Code robuster wird. Das hilft dabei, die Fehler, die die statische Analyse im Quellcode aufgedeckt hat, schnell zu beseitigen.

Automatisierte Arbeitsabläufe

Bild 8: Automatisierte Arbeitsabläufe.(Bild:  IAR Systems)
Bild 8: Automatisierte Arbeitsabläufe.
(Bild: IAR Systems)

Die Sicherstellung der Code-Qualität ist wichtig für die Entwickler, die tagtäglich am Schreibtisch arbeiten, aber noch wichtiger ist sie in den modernen und skalierbaren Build-Server-Topologien für CI/CD-Pipelines, die virtuelle Maschinen, Container (Docker) und Runner umfassen. Code-Analyse-Tools sollten gut skalierbar sein, damit die automatisierte Aufgabe, die Einhaltung der Programmierstandards zu gewährleisten, für größere Teams und solche, die an verschiedenen Standorten rund um den Globus verteilt sind, leicht zu bewältigen ist. Bild 8 zeigt die Verwendung des statischen Analysetools C-STAT, das über die Kommandozeile in Linux – Ubuntu verwendet wird. Für viele automatisierte Arbeitsabläufe ist die plattformübergreifende Unterstützung ein Standard zur Verbesserung der Effizienz von Entwicklungsteams.

Unterstützung durch Code-Analyse

Einer der größten theoretischen Vorteile der statischen Analyse besteht darin, dass sie sich nicht auf die Leistung eines Systems auswirkt, da das System während der Durchführung der Analyse nicht einmal läuft. Sie ist auch unabhängig von der Qualität der Testsuiten. Um einen bestimmten Fehler im laufenden Code zu finden, muss ein bestimmter Pfad durch das Programm mit einem bestimmten Datensatz ausgeführt werden, während ein Tool für statische Analyse theoretisch alle möglichen Pfade durch den Code untersuchen kann.

Durch die Einführung einer Code-Qualitätskontrolle zu einem frühen Zeitpunkt im Entwicklungszyklus oder bei der Wiederverwendung von Code und der Zukunftssicherung des Quellcodes können die Auswirkungen von Fehlern minimiert werden. Die Bereitstellung einer statischen Analyse direkt in den Händen der Entwickler, die mit RISC-V-Prozessoren mit gut definierten Codierungsstandards arbeiten, kann dabei helfen, Probleme im Quellcode während der Entwicklung zu finden, wo die Kosten für Fehler geringer sind als im freigegebenen Produkt.

 (mbf)

(ID:49084770)