Die Anforderungen an Embedded-Devices steigen. Vor allem unter dem Schlagwort IoT (Internet of Things) erobern sich Embedded-Systeme eine wichtige, wenn nicht sogar kritische Position in den Geschäftsprozessen der Unternehmen. Bereits 2019 sollen laut den Marktforschern von Gartner 14,2 Milliarden IoT-Geräte in Betrieb sein, bis zum Jahr 2021 rechnen die Auguren mit einem Anstieg auf 25 Milliarden Devices. Embedded-Geräte werden also für alle Branchen kritisch für den Geschäftsbetrieb, die Frage nach der Sicherheit wird drängend. Denn jedes IoT-Gerät ist zunächst einmal ein Client, der über Standard-Internettechnologien mit anderen Geräten kommuniziert. Damit kommt der Sicherheit der Embedded-Geräte ein extrem hoher Stellenwert zu – sowohl aus der Security-Warte als auch aus Sicht der funktionalen Sicherheit.
Für sichere und zuverlässige Embedded-Geräte ist das Testing ein unverzichtbarer Bestandteil der Qualitätssicherung. Nicht ohne Grund stellen die Normen für die sicherheitskritische Softwareentwicklung genaue Anforderungen an die Testmethoden und die Testabdeckung. Zudem muss in vielen Fällen das Testing nachgewiesen werden, um das Embedded-System vor der Markteinführung zertifizieren zu können. Grundsätzlich gilt dabei: Je höher die Sicherheitsanforderungen an eine Software, desto höher muss die Testabdeckung sein. Nicht alle Code-Coverage-Stufen sind jedoch in jedem Szenario sinnvoll, zudem bringen sie unterschiedliche detaillierte Erkenntnisse (Bild 1):
- Die Aufrufüberdeckung (Function Coverage) ignoriert die inneren Abläufe der Software, ihr Nutzen ist also recht gering.
- Die Anweisungsüberdeckung (Statement Coverage) ermittelt, welche Anweisungen durch die Tests ausgeführt wurden. Damit kann zum Beispiel toter Code erkannt werden. Auch zeigt sich hier, ob Tests für alle Anweisungen vorhanden sind.
- Die Zweigüberdeckung (Branch Coverage) ergibt, ob alle Programmzweige durchlaufen wurden. Die Zweigüberdeckung ist ausführlicher als die Anweisungsüberdeckung und die mit vertretbarem Aufwand umsetzbare Mindestanforderung für das Testing.
- MC/DC (Modified Condition/Decision Coverage) ist die höchste in den Normen geforderte Testabdeckungsstufe. Da die Überprüfung aller Bedingungskombinationen einen enormen Aufwand erfordern würde, versucht MC/DC, diesen Testaufwand zu minimieren. Hierbei werden alle atomaren Bedingungen einer zusammengesetzten Bedingung herangezogen. Für jede der atomaren Bedingungen wird ein Testfallpaar getestet, welches zur Veränderung des Gesamtergebnisses der zusammengesetzten Bedingung führt, wobei sich jedoch nur der Wahrheitswert der betrachteten atomaren Bedingung ändert. Hierbei muss der Wahrheitswert der anderen atomaren Bedingungen konstant bleiben.
Code wird instrumentiert
Zur Ermittlung der Testabdeckung werden sogenannte Code Coverage Analyzer genutzt. Diese ergänzen den Code vor der Übergabe an den Compiler mit Zählern für die gewünschten Testebenen. Diesen Vorgang bezeichnet man als das Instrumentieren des Codes. Ein sehr einfaches Werkzeug dafür ist zum Beispiel das GNU Coverage Testing Tool gcov. Das Tool ist für fast jede Linux-Distribution in den jeweiligen Repositories als Bestandteil des gcc-Compilers verfügbar. Mittels der gcc-Option -ftest-coverage kann der Code instrumentiert werden, um zu ermitteln, wie oft jede Code-Zeile ausgeführt wurde. Die Option -fprofile-arcs instrumentiert die Verzweigungen. Um erste Erfahrungen auf dem Gebiet der Code Coverage zu machen oder für kleine Projekte ist dieses Tool auf jeden Fall geeignet. Sein Nachteil ist – neben dem Fehlen von anspruchsvollen Testabdeckungsstufen, die für größere Testing-Anforderungen benötigt werden – die Aufbereitung und Interpretation der gewonnenen Informationen. Hier sind kommerzielle Tools deutlich überlegen. Zudem lassen sich professionelle Code Coverage Analyzer eng in die Tool-Chain integrieren.
Die Instrumentierung bleibt natürlich nicht ohne Auswirkungen auf den Code, denn die Zähler werden meist als globale Arrays abgelegt. Deutlich wird das an der nachstehenden, in C geschriebenen While-Bedingung:
while (! b == 0 )
{
r = a % b;
a = b;
b = r;
}
result = a;
Durch die Instrumentierung – in diesem Fall mit dem Code-Coverage-Werkzeug Testwell CTC++ – ergibt sich eine signifikant veränderte Struktur:
while ( (( ! b == 0 ) ? (ctc_t[23]++, 1) : (ctc_f[23]++, 0)) )
{
r = a % b ;
a = b ;
b = r ;
}
result = a ;
Zusätzliche Last für Speicher und CPU
Mit der Instrumentierung wächst der Code. Die benötigten Arrays befinden sich im Datenspeicher, sowohl im RAM als auch im ROM sind also zusätzliche Kapazitäten notwendig. Zudem beeinflusst die Instrumentierung die Ausführungszeit des Programms. Bei PC- oder Smartphone-Anwendungen kann dieser Effekt vernachlässigt werden, es ist reichlich Speicherkapazität und Rechenleistung vorhanden. Bei Embedded-Geräten hingegen kann die Instrumentierung durchaus zu massiven Problemen führen, da die Hardware-Ressourcen aus Kostengründen oft sehr knapp kalkuliert sind. Hier ist darauf zu achten, einen Code Coverage Analyzer mit einem vergleichsweise geringen Instrumentierungs-Overhead zu nutzen, da die Zähler sonst schnell die Grenzen des verfügbaren Speichers sprengen. Das gilt insbesondere, wenn sehr anspruchsvolle Testabdeckungsstufen wie MC/DC erforderlich sind.
Für Embedded-Systeme optimierte Code Coverage Analyzer wie Testwell CTC++ erweitern den Code nur minimal. Doch auch das kann in manchen Fällen bereits die Möglichkeiten der Hardware eines Targets übersteigen. Bei Problemen mit dem verfügbaren Speicher kann diese Hürde durch eine partielle Instrumentierung umgangen werden. Dabei werden jeweils nur kleine Ausschnitte des zu testenden Programms instrumentiert und getestet. Der Test wird nacheinander mit allen Programmteilen wiederholt, die daraus gewonnenen Daten lassen sich zu einem Gesamtbild zusammenfügen. So kann die Testabdeckung für das vollständige Programm ermittelt werden.
Kleinere Zähler
Ein anderer Ansatz auf kleinen Targets ist, die Größe der Zähler zu beschränken. Normalerweise arbeiten Code-Coverage-Werkzeuge mit 32-Bit-Zählern. Diese können zumindest theoretisch auf 16 oder 8 Bit reduziert werden. Hierbei sollte man aber Vorsicht walten lassen, denn unter Umständen können die Zähler dann überlaufen. Die gewonnenen Daten müssen also mit großer Sorgfalt interpretiert werden, um Fehler zu vermeiden. In seltenen Ausnahmefällen können die Zähler auch auf einzelne Bits gesenkt werden. Diese Bit-Coverage kann zum Beispiel dann sinnvoll sein, wenn es nicht relevant ist, wie oft ein Programmabschnitt durchlaufen wurde.
Dabei sollte man nicht vergessen, dass auch die gewählte Coverage-Stufe die Anforderungen an den verfügbaren Speicher beeinflusst. Hier gilt: Je höher die geforderte Teststufe, desto höher die benötigten Hardware-Ressourcen auf dem Target. Leistungsfähige Code-Coverage-Tools sind jedoch in der Lage, den Speicherbedarf für die Instrumentierung sowie die Änderungen des Laufzeitverhaltens sehr gering zu halten.
Fazit
Testing und die Ermittlung der Testabdeckung werden im Embedded-Umfeld deutlich an Bedeutung gewinnen. Denn die Software-Qualität wird immer wichtiger. Auch wenn sicher nicht alle Normen und Standards langfristig eine 100-prozentige MC/DC-Coverage für jede Art von Software verlangen: Es ist nur eine Frage der Zeit, bis die Standardisierungsgremien und Branchenverbände die Anforderungen auch abseits der sicherheitskritischen Anwendungen erhöhen. Bessere Tests sind aber auch im Interesse der Hersteller selbst. Denn fehlerhafte Produkte verursachen hohe Folgekosten, vom eigenen Ruf ganz abgesehen. Die vom PC bekannte Bananen-Software, die erst beim Nutzer reift, werden die Kunden im Embedded-Bereich kaum akzeptieren wollen.
Klaus Lambertz
(jj)
Sie möchten gerne weiterlesen?
Unternehmen
Verifysoft Technology GmbH
Technologiepark - In der Spöck 10
77656 Offenburg
Germany