Simulation von Fehlersituationen: Fault Injection einfach automatisieren

Autor / Redakteur: Thomas Dirsch * / Sebastian Gerstl

Software für sicherheitskritische Anwendungen erfordert 100%-ige Anweisungsüberdeckung durch dokumentierte Tests. „Fault Injection“ erlaubt hier eine einfache Prüfung auf mögliche Fehlersituationen.

Anbieter zum Thema

Bild 1: 
TESSY Code Coverage-Perspektive.
Bild 1: 
TESSY Code Coverage-Perspektive.
(Bild: Razorcat)

Sicherheitskritischer Code ist in der Regel geprägt durch Programmiertechniken wie Diversität, Defensivität, Selbsttests und ständige Überprüfungen. Das ist auch gut so, denn das System soll sicher sein – doch stellt dies insbesondere den Testingenieur vor Herausforderungen, die auch Rückwirkungen in die Entwicklung haben. Normen wie die ISO 26262, EN/IEC 61508, EN 50128 oder IEC 62304 verlangen Sicherheitsnachweise wie beispielsweise eine 100% Anweisungsüberdeckung (Statement Coverage) durch einen dokumentierten Test.

Die Tests der Funktionalität, basierend auf den Anforderungen, lassen sich zumeist einfach mit etablierten Testwerkzeugen durchführen. Das Dilemma ist, dass dabei die Anweisungsüberdeckung nicht immer zu 100% erfüllt werden kann. In manchen Situationen kann sie das auch nicht sein, da im Funktionstest nicht alle Fehler auftreten und dadurch die Sicherheitsvorkehrungen in der Software nicht vollständig abgearbeitet werden. Darum muss der Testingenieur Fehlersituationen künstlich herbeiführen.

Bildergalerie

Ziel: 100% Anweisungsüberdeckung

In einem System- oder HiL-Test ist es einfach, Fehlersituationen zu erzeugen, da äußere Interfaces durch Stimulation des Testsystems gesteuert und dadurch Fehlersituationen reproduzierbar getestet werden können. Dies erhöht zwar die Anweisungsüberdeckung, aber erfüllt diese immer noch nicht zu 100%, da hier die internen Sicherheitsvorkehrungen in der Software, wie zum Beispiel die Diversität, nicht beeinflusst werden kann. Eine weitere Schwierigkeit stellt die eingesetzte Technik für die Messung der Anweisungsüberdeckung dar. Die Messungen erfolgen durch eine Instrumentierung des Quellcodes, was das dynamische Verhalten des Systems beeinflussen kann.

In der Praxis wird die Anweisungsüberdeckung hauptsächlich in der Testphase „Unit Test“ ermittelt, da beim Test einzelner Funktionen auch die internen Sicherheitsvorkehrungen in der Software getestet werden können. Durch Anwenden von normgerechten Messmethoden wird bereits ein hohes Maß an Testqualität und damit eine hohe Abdeckungsrate der getesteten Funktion erreicht. In einem hoch-sicherheitskritischen Code werden allerdings immer einige Prozentwerte zur vollständigen Anweisungsüberdeckung fehlen. Die Sicherheitsfunktionen wie Diversität und Anwendung von defensiven Programmiertechniken können nur vollständig getestet werden, wenn die entsprechende Fehlersituation hergestellt wird. Eine weitere Schwierigkeit ist, dass bei schweren Fehlersituationen der sichere Zustand des Systems dadurch erreicht wird, dass das Programm in eine Endlosschleife läuft und darauf gewartet wird, dass der Watchdog anschlägt und das System in einen sicheren Zustand versetzt.

Wird der Test auf dem Mikrocontroller ausgeführt, so kann die Hardware nicht in jeden notwendigen „defekten“ Zustand versetzt werden, da die Register der Peripherieeinheiten nur entsprechend ihrer Hardwareimplementierung eingestellt werden können. Als Folge werden nicht alle Fehlerzustände in der Software erreicht. Bei der Verwendung eines Simulators wird je nach Leistungsumfang des Simulators die Peripherie ebenso simuliert und man steht vor der gleichen Schwierigkeit, wie bei einem Test mit dem Mikrocontroller. Die Simulation der Peripherie kann zumeist zwar konfiguriert oder abgestellt werden, dann geht allerdings das Verhalten der Hardware verloren. Z.B. wird nach einem Schreibbefehl an die Peripherie ein Registerbit durch die Hardware gesetzt. Ohne Simulation liegen die Register der Peripherie in einem RAM-Speicher und verhalten sich nur noch wie eine Variable.

Fault Injection – Fehlerinjektion im Quellcode

Deshalb werden Techniken wie die „Fault Injection“ angewendet: Dazu werden im Quellcode, etwa durch den Einsatz von Makros, Fehler in die Anwendung injiziert, um auf diese Weise ihr Verhalten testen zu können. Die Fault Injection wird in der Entwicklung mit Sicht auf das System und die Systemanforderungen durchgeführt, ohne die inneren Sicherheitsvorkehrungen der Software zu berücksichtigen, so dass eine unvollständige Anweisungsüberdeckung erst bei der Entwicklung der Unit Tests auftritt. Der Testingenieur erkennt diesen Mangel und kann die notwendigen Fault Injections implementieren.

Eine manuelle Fehlerinjektion per Makros o.ä. hat aber den Nachteil, dass sie manuell durchgeführt und verwaltet wird und anschließend im produktiven Quellcode verbleibt – dies ist bei hoch-sicherheitskritischen Anforderung oftmals nicht erwünscht. Denn in einem normgerechten Entwicklungsprozess sollte eine Quellcodeveränderung wieder alle Instanzen durchlaufen, d.h. die Änderungen werden freigegeben, in das Versionskontrollsystem übertragen und durchlaufen einen Codereview. Diese zusätzliche Iteration benötigt Zeit und verursacht weitere Kosten.

Bei professionellen Unit Test-Werkzeugen wird die Fault Injection deshalb ohne Quellcodeänderung implementiert. Hier wird die Messung der Anweisungsüberdeckung auch durch Instrumentierung des Quellcodes realisiert, jedoch ist dieser dynamisch für die Testdurchführung und verbleibt nicht im produktiven Quellcode. Durch das Abschalten der Messung der Anweisungsüberdeckung ist bei einem erneuten Testlauf durch das gleiche Testergebnis beider Testdurchführungen zusätzlich gewährleistet, dass diese Instrumentierung keinen Einfluss auf den Ablauf der Funktion nimmt.

Beleuchten wir eine oft genutzte Funktionalität eines Speichertests anhand eines sehr einfachen Beispiels näher. Betrachten wir Codebeispiel 1: In Zeile 6 wird der Speicher beschrieben und in Zeile 7 der geschriebene Wert überprüft. Bei Mikrocontrollern mit Cache steht möglicherweise zwischen beiden Zeilen weiterer Code, um den Cache zu löschen, damit ein erneuter Zugriff auf den Speicher gewährleistet ist. Da davon auszugehen ist, dass der Speicher fehlerfrei ist, ist der Code in Zeile 10 und somit der True-Pfad des if-Ausdruckes nicht erreichbar.

Eine Fehlerinjektion, gesteuert über ein Präprozessordefine FAULTINJECTION, kann in dem zuvor gezeigten Codeabschnitt z.B. ab Zeile 6 wie im darunter abgebildeten Beispiel 2 gezeigt aussehen. Wird das Define FAULTINJECTION gesetzt, so kann auch diese Fehlersituation getestet und die Anweisungsüberdeckung zu 100% erfüllt werden. Die zusätzliche Testvariable tstFaultInjection steuert, ob die Fehlersituation für einen Testfall eintritt oder nicht. Bei der Erzeugung des binären Codes für das Endprodukt ist das Define FAULTINJECTION nicht gesetzt, und die Fault Injection sollte nicht vorhanden sein.

Da nicht immer alle Fault Injections aktiv sein können, werden diese für das System weiter unterteilt und über verschiedene Defines und Testvariable gesteuert. Je nach Umfang des Systems steigt die Anzahl der Fault Injections, was wiederum eine entsprechende Verwaltung notwendig macht.

Automatische Fault Injection und Fehlerinjektion in TESSY

Diese Lücke schließt eine automatische Fault Injection, ähnlich wie die Instrumentierung für die Analyse der Anweisungsüberdeckung. Dafür werden die Fehlerinjektionen in einem Testtool verwaltet. Dieses instrumentiert automatisch den Quellcode und erlaubt es dem Testingenieur individuell zu bestimmen, ob Testfälle mit einer Fault Injection durchgeführt werden oder nicht. Da die Instrumentierung abgeschaltet werden kann, ist die gleiche Sicherheit wie bei der Analyse der Anweisungsüberdeckung über die Testergebnisse gewährleistet. Alle Fault Injections werden von dem Testwerkzeug verwaltet und Testfälle mit einer Fehlerinjektion speziell gekennzeichnet. Automatisierte Testergebnisberichte dokumentieren alle Testeinstellungen und Ergebnisse.

Version 4.1 des Unit- und Integrationstestwerkzeugs TESSY von Razorcat enthält eine derartige neue innovative Funktion für die Verwaltung und Implementierung von automatisierten Fault Injections. Die Positionen der Fault Injection werden von TESSY bei einer Quellcodeänderung berücksichtigt und in einer Analyse der Programmstruktur automatisch an die entsprechenden Stellen im Quellcode dynamisch gesetzt.

Bildergalerie

In der TESSY „Coverage Viewer“-Perspektive wird das Flow Chart der Funktion dargestellt und in den Farben rot und grün die Programmablaufpfade markiert. Codebereiche, die noch nicht durchlaufen wurden, bekommen eine rote Markierung und sind einfach identifizierbar. Gleichzeitig wird der entsprechende Quellcode angezeigt, so dass die Gründe dafür leicht zu analysieren sind. Wurde ein Programmpfad nicht ausgeführt, so hat der Testingenieur am entsprechenden Pfad des Flow Charts die Möglichkeit, einen Code von TESSY automatisch zu instrumentieren, um die Fehlersituation für den Test herbeizuführen.

Folgendes Beispiel illustriert dies anhand des bereits oben diskutierten Codes: Es wurde ein Testfall für einen fehlerfreien Speicherzugriff erzeugt und durchgeführt. Die Analyse der Anweisungsüberdeckung wird in der „Coverage Viewer“-Perspektive von TESSY dargestellt (Bild 1). Bei diesem Speichertest hat nun der Testingenieur im Flow Chart, am linken Pfad des if-Ausdruckes die Möglichkeit, einen benutzerdefinierten Code von TESSY automatisch instrumentieren zu lassen, um z.B. den Speicher zu manipulieren. In einem Dialogfeld wird der zu instrumentierende Code eingegeben. Die Fault Injection ist hier eine Invertierung des Wertes im Speicher, so dass die Überprüfung in Zeile 7 die Fehlersituation bemerkt und die Zeile 9 mit dem Aufruf der Funktion errorHandler() ausgeführt wird.

Im Dialogfenster „Edit Fault Injection“ (Bild 2) wird der zu instrumentierende Code eingegeben. TESSY analysiert, welche vorhandenen und ausgeführten Testfälle den betroffenen if-Ausdruck erreichen und schlägt diese Testfälle für die Anwendung der Fault Injection vor. Im vorliegenden Beispiel wurde Testfall 1 ausgewählt. TESSY erstellt nun mit der eingegeben Fault Injection eine Kopie des Testfall 1 und erzeugt Testfall 2. Der neue Testfall 2 bekommt die Eigenschaft „Fault Injection“ und wird besonders dargestellt. Nachdem der Name und das zu erwartenden Testergebnis (return ist 0) des Testfalls 2 angepasst und der Test erneut durchgeführt wurde, wird die Anweisungsüberdeckung zu 100% erreicht.

Nach erneuter Testdurchführung werden im Flow Chart der „Coverage Viewer“-Perspektive nun alle Pfade grün dargestellt, der Pfad mit einer Fault Injection erhält als Kennzeichen ein blaues Dreieck. In der „Test Items“-Ansicht erhalten die Icons der Testfälle mit der Eigenschaft „Fault Injection“ zusätzlich das gleiche Dreieck. Weitere Fehlersituationen, wie das Erreichen eines default-Pfades einer switch-Anweisung oder das Beenden von Endlosschleifen, sind auf diese Weise nun einfach zu realisieren.

TESSY verwaltet alle Fehlerinjektionen und trägt diese bei einer Testdurchführung automatisiert ein. Liegt eine neue Version des Quellcodes vor, analysiert TESSY diese neue Version und setzt die Fault Injection automatisch an die richtige Stelle. Dabei verbleibt die Instrumentierung nicht im Quellcode und kann bei der Testdurchführung eingestellt werden. Die Testergebnisberichte enthalten zusätzliche Informationen über die Verwendung von Fault Injections. Eine weitere Iteration durch den Entwicklungsprozess ist somit nicht mehr notwendig und die Unit Tests können in einem Arbeitsprozess vollständig erstellt werden, um eine 100% Anweisungsüberdeckung zu erreichen – dies spart Zeit und Kosten.

* Thomas Dirsch ist Senior Software Quality Consultant und Leiter der Niederlassung Waldbronn der Razorcat Development GmbH.

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.

Aufklappen für Details zu Ihrer Einwilligung

(ID:45070688)