Suchen

Analysewerkzeuge Moderne statische Codeanalyse für das Internet der Dinge

| Autor / Redakteur: Paul Anderson * / Franz Graser

Die Vernetzung der Geräte und Schwächen der Programmiersprachen sind zentrale Risiken im Zusammenhang mit IoT-Software. Einige dieser Risiken lassen sich mit statischer Analyse aufdecken und beheben.

Firmen zum Thema

Bild 1: Beispiel einer Warnung eines statischen Analysetools
Bild 1: Beispiel einer Warnung eines statischen Analysetools
(Bilder: Grammatech)

Obwohl C in vielerlei Hinsicht eine problematische Programmiersprache ist, kann sie ihre dominierende Stellung bei der Implementierung vieler IoT-Geräte behaupten. C++ ist in einigen Aspekten schon deutlich besser, doch hat diese Sprache einige fundamentale Schwächen geerbt. Zudem sind viele der Konstrukte, die das Programmieren in C++ so komfortabel machen (etwa STL, Exceptions), in Embedded-Systemen ohnehin unzulässig.

Was die statische Typisierung betrifft, besteht eine entscheidende Schwäche darin, dass sich Programme schreiben lassen, die vom Compiler akzeptiert werden, aber keinen Sinn ergeben. Ohne dass der Compiler eine Warnung erzeugt, kann häufig ein Wert eines Typs so behandelt werden, als sei er von einem anderen Typ.

Sorgfältige Programmierer wissen einige dieser Probleme zwar zu umgehen, aber beim Einsatz bestimmter Standardbibliotheken ist das schwierig. So können selbst sehr erfahrenen Programmierern leicht Fehler unterlaufen, die sich einer frühzeitigen Aufdeckung entziehen.

Das zweite wichtige Problem besteht in der mangelnden Speichersicherheit, da jede Speicherstelle als beliebiger Typ behandelt werden kann. Am deutlichsten wird dies beim Zugriff auf Arrays mit Indizes, die außerhalb des zulässigen Bereichs liegen. Solange die dabei referenzierte Speicherstelle innerhalb des virtuellen Speicherbereichs des Prozesses liegt, generiert ein solcher Zugriff keinen umgehend sichtbaren Fehler.

Stattdessen werden Daten im Verborgenen verfälscht, was mysteriöse Probleme in ganz anderen Teilen des Programms hervorrufen kann. Hier liegt auch die Grundursache dafür, dass Pufferüberläufe in C und C++ so gefährlich sind. Würden die Sprachen die Einhaltung von Array-Grenzen überwachen, ließen sich entsprechende Fehler umgehend erkennen und mit definierten Exceptions behandeln.

Aufgrund ihres Alters und ihrer Herkunft hat die C-Sprache mittlerweile ein Stadium erreicht, in dem viele überkommene Verhaltensweisen auch in moderne Versionen der Spezifikation übernommen werden müssen, auch wenn sie alles andere als wünschenswert sind. Der Standard definiert drei Verhaltensweisen:

  • Unspezifiziertes Verhalten: Hier legt sich der Standard nicht fest, wie der Compiler etwas implementieren soll. Ein Beispiel ist die Auswertung von Operanden, die jeder Compiler in einer anderen Reihenfolge vornehmen kann. Dies führt eventuell zu gravierenden Problemen für die Portierbarkeit, da dasselbe Programm viele verschiedene legale Semantiken haben kann.
  • Undefiniertes Verhalten: Hier kann der Compiler beliebige Dinge tun, etwa Schreibzugriffe außerhalb der Grenzen eines Arrays vornehmen. Eine gültige Implementierung kann die Verarbeitung entweder auf unnormale Weise abbrechen oder gar nichts tun.
  • Von der Implementierung definiertes Verhalten: Hier muss die Compiler-Implementierung spezifizieren, was geschieht. Ein gutes Beispiel ist die Größe eines int-Wertes: sie kann je nach Zielplattform als 16 oder 32 Bit angesetzt werden.

Im C99-Standard gibt es zweieinhalb Seiten mit unspezifiziertem Verhalten, 13 Seiten mit undefiniertem Verhalten und sechseinhalb Seiten mit implementierungsdefiniertem Verhalten. Es sind also keine selten vorkommenden Phänomene. Die Achillesferse von C-Programmen ist eindeutig undefiniertes Verhalten, das für die meisten gravierenden Fehler verantwortlich ist (etwa Division durch Null, ungültige Zeigerindirektion, Verwendung von nicht initialisiertem Speicher, Data Races und viele mehr).

Artikelfiles und Artikellinks

(ID:44230359)