Die Programmstruktur, d.h. die Basisblöcke, ihre Programmadresse und -größe sowie ihre Verknüpfung, lässt sich sowohl statisch als auch dynamisch ermitteln. Dabei können auch im DWARF-Format vorliegende Debug-Informationen aus der Programmdatei genutzt werden, welche u.a. die Zuordnungen von Zeilennummern zu Programmadressen und die Programmadressen der Basisblöcke enthalten.
Allerdings sind im regulären DWARF-Format bislang weder die Größe der Basisblöcke noch ihre Verknüpfung enthalten. Letztere lassen sich nur durch Disassemblierung der Maschinencode-Anweisungen und anschließender Interpretation, Emulation oder Simulation ermitteln. Diese statische Code-Analyse ist sehr aufwendig und zeitintensiv und wird daher auch nur selten durchgeführt.
Die dynamischen Trace-Informationen enthalten die Adressen der tatsächlich ausgeführten Anweisungen und die Reihenfolge der Ausführung. Aus diesen Informationen lässt sich zwar eine Sicht auf mögliche Basisblöcke erzeugen, aber der Nachteil hier ist, dass man leider nur die tatsächlich ausgeführten Basisblöcke sieht. Ohne zu wissen, welche Basisblöcke nicht ausgeführt werden, kann jedoch keine verlässliche Überdeckungsanalyse durchgeführt werden. Was also tun in so einem Fall?
Wege zur sicheren Erkennung der Programmüberdeckung
Normalerweise reichen die Standard-Debug-Informationen aus, um die Überdeckung der Quellcode-Zeilen (C0-Coverage) zu ermitteln. Um jedoch die Zweigüberdeckung (C1-Coverage) aus Trace-Daten ermitteln zu können, werden zusätzlich die Verknüpfungen zwischen den Basisblöcken benötigt. Diese Verknüpfungen sind, wie schon angesprochen, in den DWARF-Informationen nicht vorhanden. Aus dem Anweisungs-Trace kann ermittelt werden, welche Programmverzweigungen genommen wurden, bei direkten Sprüngen – mit einigem Aufwand – zudem auch, welche Verzweigungen nicht genommen wurden. Anders sieht es bei indirekten Sprüngen aus. Da diese nahezu beliebig viele Sprungziele haben können, ist in der Regel nicht mehr ermittelbar, wie viele Verzweigungen tatsächlich existieren und wohin diese Verzweigungen führen.
Für das vorliegende Beispiel mit der Sprungtabelle bedeutet dies, dass in diesem Fall nicht mehr ermittelbar ist, welche möglichen Verzweigungen ausgeführt wurden und welche nicht. Voraussetzung dafür wäre entweder eine aufwendige statische Code-Analyse, welche die Sprungtabellen-Optimierung erkennt, oder eine Debug-Information über die Basisblock-Verknüpfungen.
Im Beispiel mit der Wertetabelle wird die statische Code-Analyse noch aufwendiger, da hier anhand der Speicherzugriffe ermittelt werden müsste, um welchen ausgeführten case-Zweig es sich handelt. Anweisungs-Trace allein reicht dafür nicht mehr aus; für die Auswertung ist zusätzlich zwingend auch noch Daten-Trace erforderlich.
Anreicherung der Debug-Informationen
Um eine aufwendige und damit auch fehleranfällige statische Code-Analyse zu vermeiden, bietet es sich an, die Basisblock-Informationen zu nutzen, die dem Compiler nach allen Programmtransformationen ohnehin zur Verfügung stehen. Dies sind unter anderem die Identifikationsnummern, Adressen und Größen der verschiedenen nach Funktion aufgeteilten Basisblöcke sowie Anzahl und Identifikationsnummern der Nachfolger eines Basisblockes. Diese Informationen reichen aus, um einen Graphen von Basisblöcken zu ermitteln und aus den Verknüpfungen zwischen den Basisblöcken und dem Anweisungs-Trace eine Zweigüberdeckung (Branch Coverage) zu ermitteln.
Grundvoraussetzung für diese Vorgehensweise ist, dass die eingesetzten Mikrocontroller vollständigen internen oder externen Programm-Trace unterstützen, der direkt zur Statement-Coverage-Analyse verwendet werden kann. Um damit auch Branch Coverage realisieren zu können, bedarf es zusätzlich nur noch eines effizienten Ersatzes für die Instrumentierung des Programmcodes.
Da die benötigten Informationen über die Basisblöcke, also Anfangsadresse, Länge und ihre Verbindungen, dem in den Beispielen verwendeten Compiler intern bei der Codegenerierung schon zur Verfügung stehen, bestand die Aufgabe von PLS vor allem darin, diese Informationen auch von außen zugänglich zu machen. Gelöst werden konnte die Aufgabe mittels Ergänzung des DWARF-Debug-Formats und Erweiterung des verwendeten Compilers. Durch geschickte Verknüpfung mit den Trace-Daten innerhalb des Debug- und Testwerkzeuges können jetzt erstmals Branch-Coverage-Analysen auch nichtinvasiv, ohne Instrumentierung des Codes durchgeführt werden. Darüber hinaus ist dieses von PLS in Zusammenarbeit mit der Fa. Hightec entwickelte und im Feldeinsatz bereits auf seine Praxistauglichkeit hin überprüfte Verfahren auch mit optimiertem Code anwendbar.