Code-Coverage undercover: Nutzen und Tücken Trace-basierter Codeabdeckung

Seite: 3/3

Firmen zum Thema

Für das gewählte Beispiel ist aber noch ein weiterer Optimierungsschritt möglich. Da alle Anweisungen innerhalb der case-Blöcke konstante Werte zurückliefern, kann der Compiler die Rückgabewerte in einer Wertetabelle ablegen und die case-Konstante wiederum als Index verwenden (Listing 4). Damit lassen sich die Sprünge komplett eliminieren. Alle case-Blöcke sind nun in einem einzigen Basisblock vereint.

Bildergalerie
Bildergalerie mit 6 Bildern

Um aus den gewonnenen Adressen im Trace ein Statement-Coverage zu ermitteln, reichen normalerweise schon die Standard-Debug-Informationen aus. Für das interessantere Branch-Coverage hingegen fehlen in den im DWARF-Format vorliegenden Debug-Informationen die Verknüpfungen zwischen den Basisblöcken, also die Kanten des Kontrollflussgraphen. Zwar kann aus dem Instruction-Trace ermittelt werden, welche Programmverzweigung genommen bzw. bei direkten Sprüngen auch, welche nicht genommen wurden, aber sobald indirekte Sprünge mit nahezu beliebig vielen Sprungzielen ins Spiel kommen, sind die tatsächlich existierenden Verzweigungen nicht mehr ermittelbar. Folglich kann so ein Branch-Coverage nicht korrekt berechnet werden.

Für unser Beispiel mit der Sprungtabelle wäre in diesem Fall nicht mehr ermittelbar, welche möglichen Verzweigungen ausgeführt wurden und welche nicht. Voraussetzung dafür wäre entweder eine aufwendige statische Code-Analyse, welche die Optimierung erkennt, oder Debug-Information über die Basisblock-Verknüpfungen.

Im Beispiel mit der Wertetabelle wird die Analyse noch aufwendiger, da hier anhand der Speicherzugriffe ermittelt werden müsste, um welchen ausgeführten case-Zweig es sich handelt. Instruction-Trace allein reicht dafür nicht mehr aus, für die Auswertung ist zusätzlich zwingend noch Daten-Trace erforderlich. An dieser Stelle müssen die derzeit verfügbaren Tools noch kapitulieren und es bleibt in der Verantwortung der Entwickler bzw. Tester, ob solche aggressiven Optimierungen tatsächlich zugelassen bzw. wie diese durch die Qualitätssicherung behandelt werden.

Der Compiler hilft, den Kontrollflussplan zu erzeugen

Glücklicherweise gibt es dennoch Mittel und Wege, auch für trace-basierte Code-Coverage von optimiertem Code verlässliche Ergebnisse zu erhalten. Zumindest für die Sprungtabellen-Optimierung liegen die Kontrollflussinformationen, die für die exakte Ermittlung des Branch-Coverages aus Trace-Daten benötigt werden, ja vor. Unter anderem sind das die Identifikationsnummern, Adressen, und Größen der verschiedenen nach Funktion aufgeteilten Basisblöcke sowie die Anzahl und die Identifikationsnummern der Nachfolger eines Basisblockes. Der Compiler selbst benötigt diese Informationen ohnehin für seine Transformationen. Es ist also nur ein kleiner Schritt, daraus den Kontrollflussgraphen zu erzeugen und ihn als Ergänzung zum DWARF-Format auch dem Coverage-Tool verfügbar zu machen.

Bei PLS wurde deshalb exemplarisch für den gcc eine solche Ergänzung vorgenommen. Da sich dabei nur die Debug-Section ändert und diese ja nicht in den Controller geladen wird, beeinflussen die zusätzlichen Informationen weder die Codegenerierung noch den Speicherverbrauch und das Laufzeitverhalten. Seitens des Coverage-Tools, in diesem Falle der Universal Debug Engine (UDE), werden die nun verfügbaren Kontrollflussinformationen mit den gewonnen Trace-Daten verknüpft. Damit ist eine Branch-Coverage-Analyse nichtinvasiv, ohne Instrumentierung und damit ohne Verletzung des Laufzeitverhaltens für optimierten Code möglich.

Literatur

[1] R. Bär, A. Behr, D. Fischer: "Code-Coverage auf Embedded-Systemen"; ESE-Kongress 2012

* Jens Braunes ist Software-Architekt bei PLS Programmierbare Logik & Systeme, Lauta.

Artikelfiles und Artikellinks

(ID:43140991)