Software-Entwicklung Latente Defekte in bestehender Software aufdecken

Gerade bei sicherheitskritischen Systemen müssen Defekte zuverlässig aufgespürt werden.
Gerade bei sicherheitskritischen Systemen müssen Defekte zuverlässig aufgespürt werden.

Auch eine vielfach getestete und seit Jahren einwandfrei laufende Software muss nicht fehlerfrei sein. Das Tückische dabei: Manche Defekte werden erst sichtbar, wenn die Software auf einem anderen System wiederverwendet wird.

Der Umfang der in modernen Fahrzeugen eingesetzten Software hat in den vergangenen zehn Jahren rapide zugenommen. Neue Software zu schreiben verursacht jedoch hohe Kosten. Aus wirtschaftlichen Gründen ist es daher sinnvoll, vorhandene Software nach Möglichkeit wiederzuverwenden, wenn beispielsweise eine bestehende Fahrzeugkomponente für den Einsatz in einer neuen Modellreihe angepasst werden soll. Der in Frage kommende Code kann allerdings latente Fehler enthalten, auch wenn er gründlich getestet wurde und seine Zuverlässigkeit im Praxiseinsatz auf dem alten System unter Beweis gestellt hat. Dass solche latenten Fehler im früheren System niemals zu Tage getreten sind, liegt an den spezifischen Eigenschaften des Systems, wie etwa an der zum Kompilieren des Codes verwendeten Toolchain, der Prozessorarchitektur oder dem Host-Betriebssystem. Wird die Software allerdings auf ein neues System mit anderen Eigenschaften portiert, können sich die latenten Defekte auf gefährliche Weise manifestieren.

Für eine Migration bestehender Systeme gibt es viele Beweggründe. Zu den wichtigsten gehört der Wunsch, von den Hardware-Weiterentwicklungen zu profitieren, die seit der ersten Implementierung des ursprünglichen Systems hinzugekommen sind. So stellt sich häufig eine Performance-Steigerung ein, wenn ein neuerer und schnellerer Prozessor zum Einsatz kommt. Für den wiederverwendeten Code ist dies jedoch gleichzeitig eine besonders gravierende Änderung. Denn der neue Prozessor kann sich durch seine Bitbreite oder die Zahl der verfügbaren Cores von der alten CPU unterscheiden. Portiert man vorhandenen Code von einer alten Plattform auf eine neue, entsteht ein Großteil des mit der Umprogrammierung einhergehenden Aufwands durch die Anpassung des Codes an derartige Unterschiede.

Subtile Compiler-Unterschiede -können gefährliche Konsequenzen haben

Es gibt jedoch noch eine ganze Reihe weiterer, weniger offensichtlicher Abweichungen, die allzu leicht übersehen werden. Nehmen wir zum Beispiel die Toolchain, mit der der Code kompiliert wird. Eigentlich sollte sie keinen großen Einfluss haben, denn schließlich wurde der Code doch ANSI-C-konform geschrieben. Wenn also die Compiler ANSI C unterstützen, müsste doch der Code immer die gleiche Semantik aufweisen, gleich von welchem Compiler er verarbeitet wurde. Leider trifft dies nicht zu. Der C- und der C++-Standard sind voller Bestimmungen, die compilerabhängig sind. Hier gibt der Standard also nicht genau vor, wie bestimmte Konstrukte zu kompilieren sind, sondern überlässt dies den Compiler-Entwicklern. Einige dieser Bestimmungen - darunter beispielsweise die Reihenfolge, in der die Operanden ausgewertet werden - sind unter Programmierern bekannt. Andere dagegen sind höchst subtiler Natur. Ein latenter Fehler kann auf dem Altsystem harmlos sein, weil er vom Compiler auf eine ganz bestimmte Weise kompiliert wird. Wenn der Compiler jedoch auf dem neuen System eine andere Alternative wählt, kann der Fehler durchaus gefährlich werden.

Die Compiler wiederum sind natürlich auch selbst Software-Programme und somit ebenfalls nicht frei von Defekten. Eine jüngst durchgeführte Untersuchung von C-Compilern etwa deckte in jedem geprüften Compiler Fehler in der Codegenerierung auf [1]. Besonders anfällig für Compiler-Fehler ist die Behandlung des Schlüsselworts volatile , das von tragender Bedeutung für sicherheitskritische Embedded-Software ist. Unter Umständen kann die korrekte Funktion eines Programms von solchen Defekten abhängen. Ein weiterer subtiler Unterschied, durch den latente Defekte zu einer Gefahr werden können, hat mit den Standard-Bibliotheken zu tun, die an das Betriebssystem angebunden sind. Obwohl eigentlich zu hoffen wäre, dass derartige Bibliotheken von Plattform zu Plattform einheitlich sind, ist dies in der Realität nur selten der Fall. Die deutlichsten Unterschiede bestehen hinsichtlich der Fehlerbehandlung. Die neue Plattform kann vollkommen andersartige Ausfallarten aufweisen als die frühere Plattform, so dass der Code möglicherweise umgeschrieben werden muss, um diesen Unterschieden Rechnung zu tragen. Noch schlimmer ist, dass Fehlerfälle offensichtlich sehr unzureichend dokumentiert sind, wie eine kürzlich durchgeführte Studie ergab [2].