Erweiterte Debug-Möglichkeiten bei ARM-Prozessoren mit Standard-Debug-Interface ARM-Debugging – mit den On-Chip-Ressourcen leben

Assistent für bedingte Befehlsausführung

Der Assembler-Befehlssatz der ARM-CPU weist – verglichen mit anderen RISC-Prozessoren – die Besonderheit auf, dass jede Assembler-Instruktion bedingt ausgeführt werden kann. Jede Assembler-Instruktion hat ein Feld „Cond“ in den oberen Bit-Positionen, das dazu verwendet wird, die Prozessor-Status-Flags auszuwerten und die Befehlsausführung zu steuern. Das kann dazu verwendet werden, um Sprungbefehle zu umgehen und eine lineare Programmabarbeitung zu erzeugen. Dies bewirkt z.B. einen kontinuierlichen Füllstand der Prozessor-Pipeline sowie kleinere ausführbare Programme, was z.B. von den ARM-C-Compilern unterstützt wird.

Der mit vielen ARM-C-Compilern erzeugte Programmcode verzichtet in einzelnen Abschnitten ganz auf Sprungbefehle, z.B. beim Übersetzen von „if then else“-Strukturen. Das kann auch zu Überraschungen bei der Verwendung von Breakpoints oder beim Single-Stepping führen. Hier werden u.U. nicht ausgeführte Instruktionen bearbeitet und angezeigt, was zunächst für Verwirrung sorgt und eine genaue Analyse des Assembler-Codes und der Prozessor-Status-Flags erfordert (Bild 6). Diese Analyse kann automatisiert vom Debug-Tool übernommen werden, welches beim Anhalten der CPU untersucht, welche Prozessor-Status-Bits für die nächste Programminstruktion ausgewertet werden müssen. Wird der Befehl nicht ausgeführt werden, so zeigt das Debug-Tool dies durch entsprechende Symbole an. Im Falle von Breakpoints kann das Tool die Programmausführung sofort wieder starten, wenn die Instruktion nicht ausgeführt und damit der Breakpoint nicht aktiv wird.

Jeder 32-bit-ARM-Befehl besteht aus dem 28-bit-Assembler-Opcode und einer 4 bit langen Bedingung „cond“. Damit ergeben sich insgesamt 16 Fälle für Bedingungen zur Befehlsausführung, z.B always, if-zero, if-negativ, if-less etc. Dazu werden die in Bild 6 gezeigten Prozessor-Status-Flags N, Z, C und V verknüpft und ausgewertet [4]. In der mnemonischen Beschreibung der Assembler-Instruktionen werden entsprechende Erweiterungen angehängt, wobei für Befehle mit der Bedingung „always“ keine Erweiterung angefügt wird; z.B. lautet der Additionsbefehl „add“ mit der Bedingung „always“

add r2, r2, #1; inkrementiere Register r2

und im Falle der Bedingung „equal“, d.h. Ausführung wenn das Statusflag Z=1 ist,

addeq r2, r2, #1; inkrementiere Register r2 wenn Z = 1

Wenn Z = 0 ist, wird dieser Befehl nicht ausgeführt und r2 nicht inkrementiert. Die im Debug-Tool verwendeten Symbole sind in Bild 7 gezeigt. Bei einem Breakpoint auf einer bedingten Anweisung wird entsprechend ein roter Kreis mit Fragezeichen angezeigt, und beim Single-Stepping zeigt ein durchgestrichener gelber Pfeil an der Stelle des Programmzählers an, dass der nächste Befehl nicht ausgeführt wird.

Eine weitere gewünschte Zusatzfunktion eines Debug-Tools sind Laufzeitauswertungen von Programmabschnitten oder -funktionen. Bei klassischen Emulatoren mit dem Zugriff auf alle Prozessorbusse kann dies einfach realisiert werden. Die integrierten Debug-Module der ARM-Mikrocontroller erlauben hier nur einen begrenzten Zugriff auf den Prozessorbus und dies auch nicht in Echtzeit. Eine Laufzeitanalyse ist deshalb nur auf Umwegen zu realisieren und erfordert eine Instrumentierung des Programm-Codes, um z.B. einen entsprechenden Controller-Pin zu aktivieren. Unter Zuhilfenahme eines Oszilloskops oder Logik-Analysators lässt sich damit das Laufzeitverhalten untersuchen. Diese Laufzeitmessung ist ebenfalls in das Debug-Tool integrierbar und entsprechend grafisch darstellbar.

Dr. Peter Sauer studierte Elektrotechnik an der Technischen Universität Karlsruhe und promovierte dort über Bildsignalverarbeitung für industrielle Oberflächenanalyse. Er ist seit mehr als 15 Jahren in der Industrie auf den Gebieten Video-Codierung, digitale Signalverarbeitung, ASIC-Systemdesign und -Verifikation sowie Systemdesign für Embedded-Systeme tätig. Heute ist er bei der Hitex Development Tools GmbH im Bereich Produktmanagement und -konzeption beschäftigt.
peter.sauer@hitex.de

<< vorherige  Seite1 | 2 | 3

Die wesentlichen Einschränkungen des Embedded-ICE-Moduls, die die Debug-Möglichkeiten begrenzen, sind damit:

  • Limitierung auf zwei Watchpoint-Register,
  • Zugriff auf die CPU nur im Debug-Mode, d.h. wenn die Programmausführung angehalten wird.

Da beim Debuggen von Programmen im Flash-Speicher nur Hardware-Breakpoints sinnvoll eingesetzt werden können, ist hier die Anzahl auf zwei beschränkt. Würde man Software-Breakpoints einsetzen, so müsste der Flash-Speicher beim Setzen und Loslaufen bzw. Single-Stepping stetig neu programmiert werden. Durch die langen Programmierzeiten würden unzumutbare Ausführungszeiten entstehen. Weiterhin kann ein Zugriff auf die Speicherbereiche nur erfolgen, wenn die Programmausführung gestoppt wird. Dies verhindert z.B. ein kontinuierliches Auslesen von Daten, wie es mit einem Debug-Monitor möglich ist [2]. Durch entsprechende Tool-Unterstützung (siehe [3]) lassen sich einige Beschränkungen aufheben bzw. die Auswirkungen auf das Debuggen einschränken.

Viele ARM-Mikrocontroller haben integrierten Flash- und RAM-Speicher und lassen sich teilweise nicht mit externem Speicher erweitern, d.h., die Programmentwicklung erfolgt direkt im Flash-Speicher. Um die Beschränkung der Breakpoints bzw. Watchpoints auf zwei zumindest teilweise aufzuheben, werden erweiterte (Software-)Breakpoints verwendet. Dabei werden Programminstruktionen durch eine „Undefined Instruction“ wie bei einem (Software-)Breakpoint ersetzt und in den RAM-Speicher verschoben. Das Debug-Tool hat dann dafür zu sorgen, dass diese Instruktionen beim Loslaufen vom Breakpoint ausgeführt werden. Im Einzelnen ist der Ablauf in Bild 4 gezeigt:

  • Die Breakpoints im Flash-Speicher werden wie (Software-)Breakpoints im RAM behandelt, d.h., eine „Undefined Instruction“ ersetzt die aktuelle Instruktion, welche ins RAM kopiert wird. Dies erfordert eine Neuprogrammierung des Flash-Speichers.
  • Beim Anhalten der Programmausführung an dem Breakpoint werden die Status-, Program-Counter- und Link-Register ausgelesen, um die Adresse des Breakpoints zu identifizieren.
  • Beim Loslaufen vom Breakpoint wird die kopierte Programminstruktion im RAM ausgeführt. Dies erspart das erneute Programmieren des Flash-Speichers und ist bei den meisten ARM-Instruktionen möglich. Einige ARM-Instruktionen, die relative Ad-ressierung verwenden oder Co-Prozessor-Befehle, benötigen dafür eine zusätzliche Bearbeitung.

Der Debugger kann den Funktionsumfang der On-Chip-Hardware zwar nicht erweitern, die gewünschten Funktionen aber durch einen „Work-around“ verwirklichen. Dies ist z.B. beim Verfolgen von Speicherinhalten und Variablen während der Programmausführung möglich [2], was eigentlich einen stetigen Zugriff auf den Speicher erfordert. Beim ARM-Prozessor ist der Zugriff auf den Speicher grundsätzlich nur bei angehaltener CPU möglich. Um dem Benutzer die Möglichkeit zu geben, Speicherbereiche oder Variable zu verfolgen, kann über das Debug-Tool ein zyklisches Aktualisieren der sichtbaren Fenster eingeschaltet werden. Dafür gibt man einen festen Update-Zyklus vor, z.B. alle 500 ms, wobei dies natürlich zur Unterbrechung des Programmablaufes und damit zu Echtzeit-Verlusten führt. Aktualisiert werden können alle speicherbezogenen Fenster, d.h. die Watch- und Memory-Fenster.

Der Exception-Assistent dient der Identifizierung von Programmunterbrechungen durch Interrupts und Exceptions. Dies ist vor allem sinnvoll bei unerwarteten Unterbrechungen, wie z.B. einem „Data Abort“, die im Allgemeinen nicht einfach zugeordnet werden können. Aktiviert man den Assistenten für einen bestimmten oder mehrere Exceptions, sei es eine ARM-Exception oder ein Interrupt des Interrupt-Controllers, dann zeigt der Assistent die Exception-Nummer und die Speicheradresse an. Danach kann zu der zuletzt ausgeführten Programmzeile gesprungen werden, um den Grund für diese Exception weiter zu untersuchen. Bild 5 zeigt ein Beispiel für den aufgerufenen Exception-Assistenten.