Über die Fehlersuche hinaus Der Debugger als Test- und Qualitätstool

Auch moderne Debugger dienen nach wie vor in erster Linie der Fehlersuche und -behebung. Gleichzeitig sollten Entwickler solche leistungsfähigen Werkzeuge aber immer auch auf ihre Einsatzmöglichkeiten in Bereichen wie System-Level-Debugging oder Testautomatisierung hin überprüfen. Denn aus der laufenden Systembeobachtung gewonnene Informationen erlauben eine automatische Analyse und Weiterverarbeitung - damit lassen sich sowohl die Leistung optimieren als auch die Softwarequalität sichern.

Früher dienten Debugger Entwicklern vor allem als interaktives Werkzeug zur Fehlersuche gemäß dem Programmiergrundsatz »Arbeite jeden neu geschriebenen Quelltext mindestens einmal im Einzelschritt durch«. Doch inzwischen kommen High-End-Debugger in komplexen System-on-Chip-Applikationen (SoC) immer öfter auch für andere Aufgaben auf Systemebene wie beispielsweise die Software-Integration, die Optimierung der Performance oder zur Sicherung der Softwarequalität zum Einsatz.

Zudem lassen sich moderne Multifunktionswerkzeuge wie die »Universal Debug Engine« (UDE) problemlos in Test- und Serviceumgebungen einbinden, was wiederum den Einsatz eines solchen Werkzeugs über den gesamten Lebenszyklus einer Applikation hinweg erlaubt.

Software integrieren

Bei der Integration wird entweder Software aus unterschiedlichen Quellen zusammen- oder neue Funktionalität einem bestehenden System hinzugefügt. In beiden Fällen kommt es vordergründig nicht auf das Austesten der einzelnen Codezeile an, sondern auf die Beobachtung des Gesamtsystems möglichst ohne Beeinflussung des Laufzeitverhaltens. Hier kann der Debugger Systemparameter aufzeichnen und grafisch darstellen und so der jeweiligen Applikation sozusagen den Puls fühlen (Bild 1).

Stehen die aufgezeichneten Informationen in standardisierten Formaten wie XML auch anderen Tools zur Verfügung, ist sogar eine nachträgliche Auswertung, Archivierung und die Auswertung großer Datenmengen möglich. Die Daten aus dem Target lassen sich auf unterschiedliche Weise gewinnen. Die erste Variante ist das periodische Lesen über die normale Debug-Schnittstelle (in der Regel JTAG) oder deren moderne Weiterentwicklungen bei beispielsweise »Serial Wire Debug« (ARM), »Device Access Port« (Infineon) oder »On Chip Emulation« (Freescale, STMicroelectronics).

Abhängig von der Änderungshäufigkeit der beobachteten Systemparameter sind damit zwar eventuell nur Trends erkennbar, da eine lückenlose Aufzeichnung aller Werte nicht gesichert werden kann. Häufig ist aber schon das eine große Hilfe, zumal hierfür nur die Debug-Schnittstelle als Ressource nötig ist. Die zweite Version inklusive vollständiger Werteaufzeichnung setzt eine zusätzliche externe oder On-Chip-Trace-Einheit voraus.

Der erhöhte Aufwand bis hin zum Einsatz spezieller Emulation-Devices, wie beispielsweise von Infineon favorisiert, garantiert im Gegenzug neben der Lückenlosigkeit exakte Zeitstempel und im Idealfall sogar eine direkte Korrelation zum Programmcode.

Leistungseinbrüche aufspüren

Um die Performance optimieren zu können, müssen Entwickler zuerst die Programmteile aufspüren, welche die Ressourcen stark beanspruchen. Zudem gilt es, Leistungseinbrüche unter bestimmten Betriebsbedingungen zu erkennen (Bild 2).

Anhand dieser Analyse lässt sich dann eine Änderung der Softwarearchitektur oder des lokalen Programmcodes vornehmen. Dabei gibt es einige typische Designfehler, die sich durch entsprechende Leistungs- und Geschwindigkeitsmessungen leicht auffinden lassen. Moderne SoCs verfügen über Speicher mit unterschiedlichen Zugriffszeiten und bevorzugten Zugriffsarten, die entweder für Programmausführung oder Datenzugriff optimiert sind.

In der Praxis bedeutet dies, dass ein und derselbe Speicher an einer Stelle des Adressraums mit Cache hinterlegt ist und an einer anderen nicht. Oder dass eine Speicherverwaltungseinheit (MMU) den Cache ein- und ausschaltet. Als Faustregel gilt: Häufig ausgeführte Teile der Applikation gehören in den schnellsten Programmspeicher; Daten, auf die häufig zugegriffen wird, hingegen in den schnellsten Datenspeicher beziehungsweise in den Speicher, bei dem für den Zugriff vom Programmcode aus die geringste Anzahl an Maschinenbefehlen notwendig ist.

Denn unterschiedliche Adressierungsarten gehören ebenfalls zum Standardrepertoire moderner Befehlssätze. Unterstützung bei der Codegenerierung erfährt der Entwickler hier indes kaum. Die Aufteilung der Applikation in den Adressraum erfolgt bekanntlich in der »Link/Locate«-Phase. Wer hofft, in einem Standard-Embedded-Compiler hierzu auf architekturspezifisches Know-how zu stoßen, wird allerdings meist enttäuscht.

Der Wissenstransfer erfolgt im Regelfall eher durch Referenzdesigns oder Applikationsbeispiele der Halbleiterhersteller. Daten bezüglich der Performance lassen sich zwar auch über die normale Debug-Schnittstelle gewinnen. Sie haben dann aber statistischen Charakter, da in diesem Fall der Befehlszähler periodisch bei laufender Applikation ausgelesen wird (Instruction Pointer Polling).

Bei zeitlich kurzen Messungen erfasst dieses Vorgehen kleinere Funktionen gar nicht oder falsch. Die Genauigkeit nimmt erst mit der Länge der Messung zu. Leider erlaubt beispielsweise »On Chip Emulation« (OnCE) kein Auslesen des Befehlszählers zur Laufzeit. In diesem Fall ist also wieder ein Programm-Trace erforderlich, der zwar Lückenlosigkeit und Zeitstempel bietet, aber eben auch einen höheren Aufwand auf der Werkzeugseite erfordert.

Code-Coverage für mehr SW-Qualität

Für alle heute im Zusammenhang mit der Sicherung der Softwarequalität üblichen Zertifizierungen wird grundsätzlich eine Code-Abdeckungsanalyse (Code Coverage) verlangt. Dabei ist sowohl für eine »C0«-Abdeckung, bei der jeder Maschinenbefehl betrachtet wird, als auch für eine »C1«-Abdeckung - hier werden die Programmverzweigungen untersucht -immer eine vollständige Aufzeichnung erforderlich.

Deshalb kommt hier de facto also nur ein lückenloser Programm-Trace in Frage. »Intelligente« Tools wie die UDE erlauben unter anderem eine grafische Darstellung der Messergebnisse. So lassen sich nicht ausgeführte Programmteile (dead code) schnell erkennen und Testfälle erstellen und manuell ausführen.

Ein Diagramm bietet die C0-Abdeckung auf Funktionsebene. Von diesem kann auf C0-Abdeckung auf Quelltextzeilen-Level verzweigt werden. Im Programm-Fenster ist darüber hinaus eine Anzeige bis auf den einzelnen Maschinenbefehl möglich. Noch wichtiger ist allerdings, dass die Daten einfach für eine Weiterverarbeitung in für die Zertifizierung spezialisierten Tools zur Verfügung stehen.

Das kann beispielsweise eine XML-Datei sein. Damit ist eine automatisierte Auswertung sowie Archivierung von Tests sichergestellt. In Bild 3 sind zwei Zeilen in der Funktion »main« dargestellt. Eine davon mit 100 Prozent Coverage die andere ohne (dead code).

COM vereinfacht Softwaretest

Um einen Debugger für einen zumindest teilautomatisierten Softwaretest nutzen zu können - Voraussetzung hierfür ist, dass unterschiedliche Werkzeuge entweder direkt interagieren oder aber über ein den Gesamtablauf steuerndes Programm zusammenarbeiten können - sind offene Schnittstellen unerlässlich. So gibt es beispielsweise zwischen dem von Razorcat für den automatisierten Test von C/C++-Modulen entwickelten Programm »Tessy« und der Universal Debug Engine (UDE) von PLS eine Kopplung, mittels derer sich Tests auf der realen Hardware ausführen lassen.

Der schnelle Targetzugang mit der »Universal Access Device 2«-Familie sichert hierbei eine kurze Testzeit.

Noch interessanter als eine von zwei Toolherstellern angebotene Kopplung kann für den Anwender unter Umständen ein mit Hilfe eines »Component Object Model« (COM) aus verschiedenen Werkzeuge selbst zusammengefügter Workflow sein.

Das COM (manchmal auch als »ActiveX-Technologie« bezeichnet) erlaubt nicht nur eine programmatische Nutzung von C/C++ oder der aus der .NET-Welt entstammenden Programmiersprache »C#«. COM erschließt Anwendern vor allem auch eine Vielzahl von Scriptsprachen wie »Java Script«, »Python«, »Perl«, »Visual Basic Script« oder »Windows Power Shell«, die alle wiederum die Ansteuerung von COM-Komponenten erlauben (Bild 4).

Die von Anfang an mit COM-Schnittstellen ausgestattete UDE verwendet diese Technologie auch für interne Skripts und anwendungsspezifische Fenster auf Basis von HTML, daher können Anwender die Entwicklungsumgebung uneingeschränkt nutzen, ohne vorher extra eine proprietäre Makrosprache lernen zu müssen. Es reicht vollkommen aus, eine standardisierte Skriptsprache zu beherrschen und das Objektmodell zu kennen.

Als Objektmodell wird bei COM die Gesamtheit aller dokumentierten Objekte, Methoden, Eigenschaften und Datentypen bezeichnet. Damit lassen sich nahezu alle Funktionen der UDE inklusive Add-ins wie »UDE/MemTool« zur FLASH-Programmierung, CAN-Recorder oder Trace-Datenströme von intern oder extern durch Skripte steuern bzw. nutzen. Ein integrierter Skript-Debugger hilft bei der Erstellung korrekt ablaufender Automatisierungen.

Über den Autor:

Heiko Riessland ist Produktmanager bei PLS Programmierbare Logik & Systeme.