Gemeinsam zum Erfolg

Statische Codeanalyse und dynamische Tests

6. Dezember 2021, 9:10 Uhr | Royd Lüdtke
Handschake vor Programmcode
© Elektronik – shutterstock.com

Softwareentwicklern stehen zwei Techniken zum Testen von Software zur Verfügung: dynamische Tests und die statische Codeanalyse. Erst wenn beide Methoden kombiniert werden, lassen sich Fehler in der Software weitgehend ausschließen

Vermehrte Rückrufaktionen, verzögerte Auslieferungen, Schwierigkeiten, die versprochenen Funktionen rechtzeitig auszuliefern: Softwarequalität ist nichts, was vom Himmel fällt. Gute Software entsteht nur durch konsequentes Handeln, Einhalten von Normen und den Einsatz ausgereifter und funktionsreicher Qualitätssicherungstools. Schlechte Software führt zu monetären Verlusten und Imageschäden. Besonders kritisch ist Software für Embedded-Systeme, da diese oft in sicherheitskritischen Anwendungen eingesetzt wird. Hier können Softwarefehler Menschenleben gefährden und müssen daher auf jeden Fall vermieden werden.

Branchennormen wie die ISO 26262, IEC 61508 oder DO178-C schreiben deshalb strikte Anforderungen hinsichtlich der Qualität von Entwicklung und Test von Software vor. Um die Qualität sicherzustellen, werden zwei Methoden unterschieden: statische Codeanalyseverfahren und das Testen lauffähiger Software während der dynamischen Analyse – unter anderem mit Unit-Tests. Beide Verfahren sind komplementär – jeder der beiden Ansätze deckt verfahrensbedingt jeweils nur einen Teil der vorhandenen Fehler auf. Größte Fehlerfreiheit kann erst im Zusammenspiel von statischer und dynamischer Analyse erreicht werden.

Anbieter zum Thema

zu Matchmaker+

Einsatz statischer Analysetools früh im Entwicklungszyklus

Muss bei der dynamischen Analyse der Code ausgeführt werden, so ist dies bei der statischen Analyse nicht der Fall. Daher können statische Analysetools schon früh im Entwicklungsprozess in der Implementierungsphase genutzt werden und tragen deshalb massiv zum Projekterfolg bei – denn je früher ge­testet wird, desto günstiger ist die
Fehlerbehebung.

Statische Codeanalysetools überprüfen den Code auf Syntax, Semantik, Kontrollfluss- und Datenflussanomalien, Nebenläufigkeitsprobleme und Einhaltung von Codier-Richtlinien/Standards. Ohne das Schreiben von Testfällen wird eine Vielzahl an Bugs und sicherheitsgefährdender Schwachstellen frühzeitig aufgedeckt.

Idealerweise wird der Code bereits vom Beginn der Entwicklung an regelmäßig – am besten bereits durch den einzelnen Entwickler vor dem Einchecken und dann mit zunehmender Integration von Modulen – statisch analysiert. Hierbei ist es sinnvoll, den Code erst dann einem weiteren Verifikationsschritt wie Code-Reviews, Unit-Tests oder Integrationstests zuzuführen, wenn die statische Codeanalyse keine Fehler mehr anzeigt. Bei diesem Vorgehen kann die Anzahl der Fehlermeldungen bei der Abschlussprüfung vor Auslieferung entscheidend reduziert werden.

Der Einsatz von statischen Analysetools ist vor allem bei der Entwicklung von Steuerungs- und Embedded-Systemen sehr sinnvoll. Da hier oft sehr hardwarenah programmiert wird, kommen Sprachen wie C und C++ zum Einsatz, die den Entwicklern viele Freiheiten geben – leider auch beim Schreiben von fehlerhaftem Quellcode. So fügt ein C/C++-Compiler keinen Code ein, der testet, ob angeforderter Speicher auch zugeteilt wurde oder eine zu kopierende Zeichenkette in das Zielarray passt. Dies passiert beispielsweise im folgenden Programm, in welchem mit der Funktion »malloc()« durch das Betriebssystem Speicher angefordert wird:

int hallofehlerwelt ()
{   char *p;
    p = (char *)malloc (20);
    strcpy(p, “Hallo Fehlerwelt“);
    printf(“%s\n“,p);
}

Der Fehler liegt hier darin, dass davon ausgegangen wird, dass die 20 angeforderten Bytes auch bereitgestellt wurden und der Zeiger »p« auf ein Feld mit zwanzig Zeichen zeigt. Schlug die Speicheranforderung fehl, schreibt die String-Copy-Funktion »strcpy()« in die Speicherstelle 0, was in der Regel zum Programmabsturz führt, weil jedes moderne Betriebssystem diese Speicherstelle schützt. Die Funktion »strcpy()« prüft nicht, ob an der Stelle p genügend Platz für den String vorhanden ist, den es dann kopiert. Fügt man also noch ein »schöne« zwischen »hallo« und »fehlerwelt« ein, schreibt strcpy  mehr als zwanzig Zeichen in den Speicher und damit in Bereiche, die andere Daten oder Programmcode enthalten können. Das Resultat ist ein Speicherüberlauf (Buffer-Overflow).

Die Verwendung globaler Variablen kann ebenfalls fehlerträchtig sein, weil Compiler nicht prüfen, ob auf eine globale Variable lesend zugegriffen wurde, bevor sie initialisiert wurde. Zudem lassen sich Variablen-Typen in C und C++ relativ leicht verändern, was zu impliziten Wertveränderungen führen kann. Die korrekte Vorgehensweise zu prüfen, wird umso schwieriger, wenn viele Entwickler an unterschiedlichen Modulen gleichzeitig arbeiten, die voneinander abhängig sind und niemand mehr alle Programmentwicklungen im Blick hat.

Solche Fehler können durch die statische Codeanalyse vermieden werden. Sie erledigt die Arbeit, die sich gängige Compiler sparen und erzeugt bei einem Analyselauf Abbilder der komplexen Abläufe und Datenzugriffe. Das Werkzeug für die statische Analyse durchläuft den Source-Code wie ein Compiler, erzeugt aber statt Code ein Analyseabbild, das vom Tool umfangreich ausgewertet wird.

Besonders fortschrittlich ist hierfür das Werkzeug CodeSonar von GrammaTech. CodeSonar erstellt aufgrund des Quellcodes Modelle der Datenströme und Zugriffe, analysiert diese und listet alle potenziellen Schwachstellen auf. Es wird angezeigt, wo Funktionen und globale Variablen verwendet werden. Über eine grafische Ausgabe sind
die Zusammenhänge leicht erkennbar. Jedes einzelne Problem wird mit ausführ­lichen Fehlerbeschreibungen angezeigt.

Gute Werkzeuge für die statische Analyse bieten im Vergleich zu einfachen Analysewerkzeugen einen weitaus größeren Komfort, indem sie Fehlermeldungen nach Kritikalität sortieren. Das Entwicklerteam kann sich somit zunächst der Behebung der schwerwiegenden Fehler widmen und weniger kritische Meldungen dann bearbeiten, wenn hierzu noch Zeit bleibt. Warnungen, die vom Team als nicht relevant angesehen werden, können sogar ganz unterdrückt werden.

Der Einsatz von »Advanced Static Analysis Tools« führt daher bei Entwicklern und Managern zu höherer Akzeptanz. Die etwas höheren Kosten für die Anschaffung  des Tools werden in der Regel schnell wieder hereingeholt.

Dynamische Tests ergänzen die statische Codeanalyse

Sobald die Software im fortgeschrittenen Projektstadium ablauffähig ist, sollte die statische Analyse durch dynamische Tests ergänzt werden. Sie dienen vor allem dazu, die funktio­­nale Korrektheit eines Systems nachzuweisen.

Üblicherweise werden dynamische Tests ausgeführt, sobald erste Bestandteile des Codes lauffähig sind, und begleiten dann die Entwicklung bis zum fertigen Produkt. Ein wichtiger Bestandteil dieser Tests ist die Code-Coverage-Analyse. Hiermit wird sicher­gestellt, dass jeglicher Code einer Anwendung auch durchlaufen – d. h. getestet – wurde. Programmteile, die offensichtlich für einen gewissen Zweck entwickelt wurden und damit auch eine Funktion haben sollten, die aber nie aufgerufen werden, lassen vermuten, dass ein Programmier- oder Verfahrensfehler vorliegt. Im einfachsten Fall ist es Code, den niemand gelöscht hat, nachdem er nicht mehr gebraucht wurde, und der »nur« Speicherplatz belegt. Im schlimmsten Fall sind es Initialisierungs- oder andere Funktionen, die selten zum Einsatz kommen, im Bedarfsfall aber wichtige Aufgaben haben.

Code Coverage Analyser wie Testwell CTC++ von Verifysoft ergänzen zur Messung der Testabdeckung die Programme durch passende Zähler, die an allen relevanten Stellen im Source-Code platziert werden – Instrumentierung des Codes – und analysieren, ob der entsprechende Codeteil durchlaufen wurde. Wichtig bei solchen Tools ist, dass die Instrumentierung möglichst wenig Speicherplatz belegt, da in Embedded-Systemen häufig nur limitierter Hauptspeicher zur Verfügung steht. Außerdem dürfen solche Tools die Leistungsfähigkeit nur minimal beeinflussen, um in zeitkritischen Regelungssystemen keine Fehlfunktionen zu generieren, die sonst nicht auftreten würden. In der Regel sind Coverage-Tools in die Entwicklungsumgebungen integriert, sodass kein manuelles Platzieren von Testcode nötig ist. Nach den Testläufen erzeugt der Coverage-Analyser Berichte, über die Entwickler sich bis ins Detail anschauen können, welche Funktionen ausgeführt wurden und welche nicht. Ein nützlicher Nebeneffekt der Code-Coverage-Analyse ist die Angabe, wie häufig einzelnen Codeteile aufgerufen wurden.

Bei sicherheitskritischer Software sind dynamische Testverfahren und der Nachweis der Code-Coverage aus gutem Grund verpflichtend. So fordern die DO-178C in der Luftfahrt, die ISO 26262 im Automobilbereich, die EN 50128 im Schienenverkehr sowie die allgemeine Norm IEC 61508 für Software mit hoher Kritikalität hohe Code-Coverage-Stufen wie die Modified Condition Decision Coverage (MC/DC), um den Test aller Bedingungen bzw. Entscheidungen in einer Software nachzuweisen.

Dies ist beispielsweise für If-Then-Abfragen mit mehreren logischen
Und- oder Oder-Verknüpfungen wichtig. So wird bei einer Abfrage wie:

if (sensorActivity == TRUE || sensorB > grenzwert(…))
{  …  }

die Funktion »grenzwert()« im zweiten Teil der Abfrage hinter dem logischen Oder »||« nicht ausgeführt, wenn sensorActivity den Wert True hat. Das ist auch sinnvoll, weil unabhängig vom Ergebnis des zweiten Vergleichs der Gesamtausdruck True sein wird, wenn sensorActivity schon True ist. Ist die Funktion »grenzwert()« auch für die Initialisierung von globalen Variablen oder I/O-Registern verantwortlich, kann die Nichtausführung zu schwer auffindbaren Fehlern führen, weil die Entwickler fälschlicherweise davon ausgehen, dass diese Funktion bei dieser Abfrage immer ausgeführt wird.

Oft sind logische Abfragen noch wesentlich verschachtelter – bei logischen Und-Abfragen mit False-Prüfun­gen gilt das Gleiche. Auf den ersten Blick ist nicht erkenntlich, ob hier ein Fall vorliegt, bei dem Codeteile nie ausgeführt werden.


  1. Statische Codeanalyse und dynamische Tests
  2. Kombination von statischer Codeanalyse und dynamischem Testen

Verwandte Artikel

Verifysoft Technology