Die Embedded-Industrie muss schnell komplexe Software liefern, was das Risiko von Fehlern und Sicherheitsproblemen erhöht. Besonders bei der RISC-V-Codebasis, da der Quellcode oft wiederverwendet oder migriert wird. Als Schlüssel für eine effiziente Software-Entwicklung gilt die Qualität.
Wenn wir davon sprechen, die RISC-V-Codebasis im Griff zu haben, 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 müssen, damit die Software auf lange Sicht fehlerfrei oder zumindest so fehlerarm wie möglich ist.
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. Die y-Achse zeigt die prozentualen Kosten, 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 effizient ist, wenn dieser von hoher Qualität und gut entwickelt ist.
Es gibt mehrere Gründe, warum die Code-Qualität ein wichtiges Thema ist:
Es gibt eine ganze Reihe von Programmierstandards, aber nur wenige sind weitverbreitet. MISRA C [2] 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 in Embedded-Systemen, insbesondere in denen, 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. In den Jahren 2004, 2012 und 2023 wurde der Standard aktualisiert, um weitere Regeln hinzuzufügen. Außerdem gibt es einen MISRA-C++-2008-Standard, der auf C++ 2003 basiert, sowie eine aktuelle Version für C++ aus dem Jahr 2023.
Einige gute Standardregeln für die Programmierung finden sich auch in der CWE Common Weakness Enumeration von MITRE [3]. 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 Compilierzeit keine strenge Typüberprüfung statt. Dennoch kann der Code 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 der Prozessor 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.
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.
Trotz der zunehmenden Akzeptanz von MISRA C 2023 wird MISRA C 2012 weiterhin 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.
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 folgende Codeschnipsel (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 helfen, aber in der Realität ist dieser nur selten gut dokumentiert.
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 es beabsichtigt sein, handelt es sich um einen Fehler, da die Anweisung, so wie sie geschrieben ist, eindeutig nicht in den Codeblock gehört. 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 die Software-Entwicklung zukunftssicher zu gestalten.
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.
Abhängig von der Qualität der statischen Analyse 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 in Aktion an, das in IAR Embedded Workbench und IAR Build Tools für RISC-V integriert ist. C-STAT bezieht seine Regeln aus den MISRA C- und MISRA C++-Regelsätzen, der Common Weakness Enumeration (CWE) von MITRE und aus SEI CERT C.
Bild 4 zeigt die verfügbaren Regeln, die aktiviert werden können, um die Einhaltung der Programmierstandards durchzusetzen.
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.
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.
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 korrigiert 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.
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-Systemen 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.
Der Autor
Rafael Taubinger
ist Global Product Marketing Manager bei IAR mit Hauptsitz in Schweden. Zuvor war er für IAR als Global FAE-Manager und Senior FAE tätig. Taubinger verfügt über mehr als 20 Jahre Erfahrung in der Embedded-Branche und hat einen Bachelor-Abschluss in Elektrotechnik sowie einen Master of Business Administration (MBA).
Rafael.Taubinger@iar.com