Software-Entwicklung Fallstricke im Code

Die im Quellcode dargestellte Codeüberdeckung „sieht“ nicht sämtlichen Code, der tatsächlich auf dem Prozessor abläuft. Denn bevor aus dem Quellcode Maschinencode wird, finden Umwandlungsprozesse statt, auf die der Software-Entwickler keinen direkten Einfluss hat. Umso wichtiger ist es, diese Prozesse und ihre Auswirkungen zu kennen

Codeüberdeckung bzw. Code Coverage ist eine Software-Metrik, mit der sich die Güte von Testläufen auswerten lässt. Auf Basis dieser Auswertung werden neue Testfälle abgeleitet, redundante Testfälle eliminiert und ineffiziente Testfälle modifiziert oder ersetzt. Mittels Codeüberdeckung lässt sich zudem Code identifizieren, der nie ausgeführt wird. Dieser „tote“ Code verursacht unerwünschten Overhead bei der Zuteilung von Speicherressourcen und stellt darüber hinaus eine potenzielle Gefahrenquelle dar, wenn dieser während des Tests nicht ausgeführt wurde. In den Bereichen Luftfahrt, Medizin, Automobil und Kerntechnik werden Codeüberdeckungsmessungen (meist auf Quellcode-Ebene) für die Zertifizierung nach Normen wie DO-178B, DO248D, IEC 62304, ISO 26262 und SC45A empfohlen. Nach Beizer [1] deckt eine hundertprozentige Anweisungsüberdeckung auf Quellcode-Ebene nur etwa 75 % oder weniger Anweisungen auf Objektcode-Ebene ab [2].

Vom Quellcode zum Objektcode

In einer Hochsprache wie C, C++, ADA etc. geschriebene Programme werden, bevor sie auf einem Prozessor oder Controller ablaufen, in Objektcode umgewandelt. Ausgehend vom Quellcode finden zahlreiche Umwandlungsvorgänge statt, aus denen der Objektcode entsteht, der dann auf der Zielplattform abläuft. Bei C-Programmen bestehen diese Schritte für gewöhnlich aus Preprocessing, Compilieren, Optimieren, Assemblieren, Linken und Konvertieren; mit kleineren, systemabhängigen Unterschieden (Compiler, Assembler, Linker, Converter). Diese Schritte sind nicht umkehrbar - in jedem Schritt werden Daten hinzugefügt und entfernt. Beim Linken werden beispielsweise Adressen für Variablen erzeugt und symbolische Namen entfernt. Diese Änderungen haben einen direkten Einfluss auf die Codeüberdeckungsanalyse auf Quellcode-Ebene, denn

  • oft fügt ein Compiler- oder Bibliotheksaufruf Objektcode hinzu,
  • es ist schwierig zu erkennen, wann Objektcode durch einen Compiler- oder Bibliotheksaufruf hinzugefügt wird,
  • Ihre Tests decken u.U. den hinzugefügten Objektcode nicht zu 100 % ab,
  • es ist sehr aufwändig festzustellen, welcher Objektcode welcher Quellcodezeile entspricht (die Ergebnisse der Codeüberdeckungsanalyse werden meist in Quellcode dargestellt)

Die nachfolgenden Beispiele zeigen, dass bei jeder Verwendung von komplexen Anweisungen (z.B. für eine Schleife), Bedingungen (z.B. if-Anweisung) oder Bibliotheks-/Betriebs- systemfunktionen Objektcode hinzugefügt wird, der sich nicht direkt auf Quellcode-Anweisungen zurückführen lässt. Manchmal erzeugt auch der Compiler Objektcode. Absatz 6.4.4.2b der Norm DO-178/ED-12B besagt daher: „… wenn der Compiler Objektcode generiert, der sich nicht direkt auf Quellcode-Anweisungen zurückführen lässt, ist eine zusätzliche Verifizierung des Objektcodes erforderlich …“. Wenn also eine Applikation eine der o.a. Normen erfüllen muss, so muss auch der Objektcode abgedeckt werden. Für die Zulassung einer Applikation sind die folgenden drei Schritte zu beachten (aus CAST-12 [3]):

  • Erkennen und Identifizieren von hinzugefügtem Objektcode,
  • Festlegen der Funktion dieses Codes,
  • Verifizieren der Richtigkeit der hinzugefügten, nicht rückführbaren Codeabschnitte.

Die On-Chip-Debug-Tools und Analyzer von iSystem bieten eine einfache Möglichkeit zur Verifizierung und Überdeckungsanalyse von Objektcode. Die iSystem-Tools messen die Codeüberdeckung durch Aufzeichnen aller auf dem Prozessor angesprungenen Adressen; die Messung erfolgt direkt auf Ebene des Maschinencodes. Dies erfolgt ohne Code-Instrumentierung. Die Ergebnisse der Codeüberdeckung werden auf Quellcode-, Objektcode- und Assembler-Ebene dargestellt und sind direkte Abbildungen des Maschinencodes auf dem High-Level-Quell-/Objekt-/Assemblercode. Eingefügter Objektcode wird im Codeüberdeckungs-Analysefenster von iSystem gut sichtbar dargestellt.

Voraussetzung für diese Messung der Codeüberdeckung auf Objektcode-Ebene: Die Objektcode-Analyse findet im Code statt, der auf die reale Ziel-Hardware geladen wird. Zum Messen der Codeüberdeckung zeichnet ein mit dem Target verbundenes Hardware-Tool die Programm-ausführung auf (Befehle und Daten, externe Signale einschließlich Zeitstempel). Anhand dieser Daten lässt sich im Objektcode nachweisen, ob Code ausgeführt wurde oder nicht. Diese Funktion bezeichnet man als Tracen. In-Circuit-Emulatoren oder On-Chip-Debugger (OCD) bieten diese Trace-Funktion an, wobei die OCD-Schnittstelle über einen sogenannten Code Trace Port für die Rekonstruktion und Messung der Codeausführung verfügen muss. Hier ist die Verwendung von Mikrocontrollern mit integrierten On-Chip-Trace-Debug-Zellen vorzuziehen, z.B. NEXUS oder ETM.

Was beim Umwandeln in Objektcode passiert

In den folgenden Beispielen sind reguläre Anweisungen mit einer farbigen Box gekennzeichnet. Die Box ist grün bei ausgeführten Anweisungen und rot bei nicht ausgeführten Anweisungen. Rote und grüne Pfeile zeigen Entscheidungsüberdeckungsdaten an, falls vorhanden. Der Pfeil ist entsprechend der ausgeführten Pfade gefärbt.