19.06.2014 Aufrufe

Debugging mit bedingten Befehlen auf ARM-CPUs - EuE24.net

Debugging mit bedingten Befehlen auf ARM-CPUs - EuE24.net

Debugging mit bedingten Befehlen auf ARM-CPUs - EuE24.net

MEHR ANZEIGEN
WENIGER ANZEIGEN

Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.

YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.

MIKROCONTROLLER & PROZESSOREN<br />

E&E PRODUCTS & SOLUTIONS<br />

<strong>Debugging</strong> <strong>mit</strong> <strong>bedingten</strong><br />

<strong>Befehlen</strong> <strong>auf</strong> <strong>ARM</strong>-<strong>CPUs</strong><br />

Bedingte Befehle eröffnen Entwicklern neue, weit reichende<br />

Möglichkeiten beim Software-<strong>Debugging</strong><br />

B.01<br />

Die typischen Debug-Funktionen<br />

beim Entwickeln und Testen von<br />

Software heißen Breakpoint und<br />

Single-Step. Beim Debuggen von<br />

<strong>ARM</strong>-Prozessoren macht sich die<br />

spezielle Eigenschaft des Befehlssatzes<br />

bemerkbar: Jeder Befehl<br />

kann bedingt ausgeführt werden.<br />

Dies kann zu einem für den<br />

Entwickler zunächst unerwarteten<br />

Programmabl<strong>auf</strong> führen, der<br />

jedoch durch Software-Debug-<br />

Tools automatisch erkannt und<br />

angezeigt werden kann.<br />

PETER SAUER<br />

D<br />

ie Programmabl<strong>auf</strong>-Prüfung <strong>mit</strong><br />

den Funktionen Breakpoint und<br />

Single-Step kann bei <strong>ARM</strong>-Prozessoren<br />

zu unerwarteten Ergebnissen<br />

führen. Die Eigenschaft, dass alle Befehle<br />

bedingt ausgeführt werden können, wird von<br />

den üblichen C-Compilern zur Code-Optimierung<br />

benutzt. Dabei zeigt sich ein Verhalten,<br />

dass z.B. bei „if-then-else“-Konstrukten<br />

scheinbar immer beide Pfade durchl<strong>auf</strong>en<br />

A U T O R<br />

Dr. PETER SAUER<br />

Manager Product Marketing<br />

peter.sauer@hitex.de<br />

Hitex Development Tools GmbH<br />

Greschbachstr. 12<br />

76229 Karlsruhe<br />

T +49/721/9628-0<br />

F +49/721/9628-149<br />

Dem 28-Bit-Assembler opcode ist ein vier<br />

Bit langer Code für die Bedingung cond anwerden,<br />

unabhängig von der „if“-Bedingung.<br />

Bei näherer Betrachtung des generierten Assembler-Codes<br />

wird das Verhalten jedoch verständlich<br />

und der vermeintliche Fehler entpuppt<br />

sich als korrekter Vorgang.<br />

Die Software-Debug-Tools können dieses<br />

Verhalten automatisch berücksichtigen und<br />

dem Entwickler beim Debuggen den aktuellen<br />

Status <strong>mit</strong> einfachen Symbolen anzeigen.<br />

Der <strong>ARM</strong>-Befehlssatz<br />

Die typische RISC-Architektur eines <strong>ARM</strong>-<br />

Prozessors [1] zeichnet sich aus durch<br />

eine große Zahl einheitlicher Register,<br />

eine Load-Store-Architektur, nur unter<br />

Verwendung der Register-Inhalte,<br />

einfache Adressierungsverfahren und<br />

einheitliche und gleichlange Befehlsworte.<br />

Daneben weist die <strong>ARM</strong>-Architektur die<br />

spezielle Eigenschaft <strong>auf</strong>, dass die Befehlsausführung<br />

bedingt erfolgen kann und zwar<br />

abhängig vom Zustand der Flags im Statusregister.<br />

Die gängigen Compiler, u.a. der GNU<br />

Compiler, nutzen dies aus und erzeugen<br />

u.U. einen optimierten Code. Besonders bei<br />

Anweisungen <strong>mit</strong> einfachen „if-then-else“-<br />

Konstrukten werden die <strong>bedingten</strong> Befehle<br />

anstelle von Programmverzweigungen <strong>mit</strong><br />

Sprungbefehlen verwendet.<br />

Der <strong>ARM</strong>-Befehlssatzes ist aus gleichlangen<br />

und gleich <strong>auf</strong>gebauten Feldern zusammengesetzt,<br />

die eine effiziente Dekodierung des<br />

Befehls ermöglichen. Der Aufbau eines 32-<br />

Bit-Befehlswortes ist wie folgt:<br />

31 27 0<br />

cond<br />

opcode<br />

E&E KOMPENDIUM 2005/2006<br />

91


E&E PRODUCTS & SOLUTIONS<br />

MIKROCONTROLLER & PROZESSOREN<br />

B.01<br />

gefügt. Das Feld cond bestimmt hierbei, abhängig<br />

von den Flags N, Z, C und V im Statusregister<br />

CPSR, ob der Befehl ausgeführt<br />

wird oder nicht. Für die Bedingungen ergeben<br />

sich da<strong>mit</strong> insgesamt 16 Fälle:<br />

Die Mnemonic-Erweiterung wird hierbei an<br />

den Assembler-Befehl angehängt, wobei für<br />

Befehle ohne Bedingung die Erweiterung al<br />

in der Regel entfällt. Für den Befehl<br />

add r2, r2, #1 ; inkrementiere Register r2<br />

ergibt sich im Falle der Bedingung „Ausführen<br />

bei Gleichheit“ Z=1 der Befehl<br />

addeq r2, r2, #1 ;<br />

inkrementiere Register r2 wenn Z=1.<br />

Im Falle von Z=0 wird r2 nicht inkrementiert.<br />

Im folgenden Abschnitt werden Beispiele<br />

angeführt.<br />

C-Code versus Assembler-Code<br />

Beispiel 1<br />

# “C” Code <strong>ARM</strong> Assembler<br />

Code<br />

1 if (index < counter) ldrh r2, [r5]<br />

2 { ldrh r3, [r4, #00h]<br />

cmp r2, r3<br />

3 buffer1 = index; movcc r7, r2<br />

4 }<br />

5 else<br />

6 {<br />

7 buffer2 = index+1; ldrcs r3, [pc, #1ch]<br />

ldrcsb r6, [r3]<br />

addcs r6, r6, #1h<br />

9 }<br />

Abb. 1:<br />

Den „<strong>bedingten</strong>“<br />

Breakpoint, erkennt<br />

man am Fragezeichen<br />

im Punkt, den un<strong>bedingten</strong><br />

Breakpoint<br />

am Punkt<br />

Hier werden drei Beispiele für bedingte Anweisungen<br />

in C- und Assembler-Code gezeigt.<br />

Der C-Code wurde dabei <strong>mit</strong> einem<br />

<strong>ARM</strong>-GNU-Compiler übersetzt. Die den C-<br />

Anweisungen entsprechenden Assembler-<br />

Instruktionen sind zur Verdeutlichung in<br />

Gruppen zusammengefasst. Ein einfaches<br />

Beispiel für eine „if-then-else“-Bedingung in<br />

C und Assembler zeigt Beispiel 1.<br />

Diese in C absolut übliche Konstruktion<br />

einer Abfrage erzeugt im Assembler-Code<br />

eine lineare Abfolge von <strong>Befehlen</strong> ohne<br />

Sprünge. Abhängig vom Carry-Flag werden<br />

die Befehle ausgeführt oder nicht (ersichtlich<br />

an den Mnemonic-Erweiterungen cc<br />

und cs, siehe vorherige Tabelle). Der Vorteil<br />

ist eine konstante L<strong>auf</strong>zeit des Codes, unabhängig<br />

von der Gültigkeit der Abfrage-<br />

Bedingung in Zeile #1. Es werden immer<br />

sowohl der „if“-Zweig als auch der „else“-<br />

Zweig durchl<strong>auf</strong>en.<br />

Für Prozessoren ohne bedingte Befehle werden<br />

Assembler-Anweisungen generiert, die<br />

die „if-then-else“-Pfade über Sprungbefehle<br />

erreichen. Dazu wird nach der Abfrage der<br />

Bedingung in Zeile #1 ein bedingter Sprung<br />

eingefügt, und im „if“-Zweig der „else“-<br />

Zweig übersprungen und umgekehrt. Dies<br />

ist in Beispiel 2 zu sehen. Hier wird der Programmabl<strong>auf</strong><br />

durch Sprünge gesteuert,<br />

wo<strong>mit</strong> die L<strong>auf</strong>zeit vom durchl<strong>auf</strong>enen Pfad<br />

abhängt. Dies ist an der Anzahl der ausgeführten<br />

Befehle leicht zu erkennen (Vergleich<br />

der Zeilen #3 und #7).<br />

Beispiel 3 zeigt, wie durch eine zusätzliche<br />

Abfragebedingung im Beispiel 1 ein <strong>ARM</strong><br />

Assembler-Code wie bei Beispiel 2 erzeugt<br />

wird. Hier wird der Programmabl<strong>auf</strong> ebenfalls<br />

durch Sprünge gesteuert.<br />

Die Zeilen #7 aller Beispiele erzeugen dieselbe<br />

Folge von Assemblerbefehlen, <strong>mit</strong> dem<br />

Unterschied, dass sie in Beispiel 1 bedingt<br />

ausgeführt werden, während sie in Beispiel 2<br />

und 3 immer (Bedingung „Always“, siehe<br />

vorherige Tabelle) ausgeführt werden. Die<br />

Mnemonic-Erweiterung wird hierbei, wie<br />

bereits erwähnt, nicht angezeigt.<br />

Für den Software-Entwickler ist da<strong>mit</strong> nicht<br />

unbedingt zu erkennen, welcher Programmcode<br />

erzeugt wird und wie das L<strong>auf</strong>zeitverhalten<br />

sein wird. Der generierte <strong>ARM</strong>-Assembler-Code<br />

hängt im Wesentlichen davon<br />

ab, wie komplex die Anweisungen in der<br />

„if“-Bedingung und innerhalb der beiden<br />

Pfade sind und welche Register zur Verfügung<br />

stehen.<br />

Debug-Problematik<br />

Das Debuggen solcher Programmabläufe<br />

<strong>mit</strong> Breakpoints führt zu unterschiedlichen<br />

Resultaten:<br />

Bei einem Breakpoint in Zeile #7 des C-Codes<br />

wird eine Programmunterbrechung erwartet,<br />

falls die Bedingung in Zeile #1 nicht erfüllt ist.<br />

Für die Beispiele 2 und 3 trifft dies zu. In Beispiel<br />

1 jedoch wird die Programmausführung<br />

in jedem Fall in Zeile #7 angehalten, da immer<br />

beide Zweige durchl<strong>auf</strong>en werden. Was<br />

Beispiel 2<br />

# “C” Code <strong>ARM</strong> Assembler<br />

Code<br />

1 if (index < counter) ldrh r2, [r5]<br />

2 { ldrh r3, [r4, #00h]<br />

cmp r2, r3<br />

bcs #else<br />

3 buffer1 = index; mov r7, r2<br />

ble #end<br />

4 }<br />

5 else #else<br />

6 {<br />

7 buffer2 = index+1; ldr r3, [pc, #1ch]<br />

ldrb r6, [r3]<br />

add r6, r6, #1h<br />

9 } #end<br />

Beispiel 3<br />

# “C” Code <strong>ARM</strong> Assembler<br />

Code<br />

1 if ((index < counter) && ldrh r2, [r5]<br />

(buffer1+buffer2 < 25)) ldrh r3, [r4, #00h]<br />

2 { cmp r2, r3<br />

bcs #else<br />

add r3, r7, r6<br />

cmp r3, #18h<br />

movle r7, r2<br />

ble #end<br />

3 buffer1 = index;<br />

4 }<br />

5 else #else<br />

6 {<br />

7 buffer2 = index+1; ldr r3, [pc, #1ch]<br />

ldrb r6, [r3]<br />

add r6, r6, #1h<br />

9 } #end<br />

92<br />

www.<strong>EuE24.net</strong>


MIKROCONTROLLER & PROZESSOREN<br />

E&E PRODUCTS & SOLUTIONS<br />

aus Sicht des C-Codes als fehlerhaftes Verhalten<br />

erscheint, wird nur durch einen Blick <strong>auf</strong><br />

den Assembler Code verständlich. Der Software-Entwickler<br />

muss sich hier versichern,<br />

ob der Befehl ausgeführt wird oder nicht, d.h.<br />

er muss den Zustand der Flags für den jeweiligen<br />

Assembler-Befehl prüfen (siehe vorherige<br />

Tabelle). Er muss sich also dessen bewusst<br />

sein, dass dieses Stück Programmcode immer<br />

durchl<strong>auf</strong>en wird.<br />

Software-Debug-Tools können hier sehr hilfreich<br />

sein. Durch Prüfen der Status-Flags, die<br />

für den Assemblerbefehl an der Stelle des<br />

Breakpoints relevant sind, kann das Debug-<br />

Tool entscheiden, ob der Befehl ausgeführt<br />

wird oder nicht. Wenn der Befehl ausgeführt<br />

wird, bleibt der Programmabl<strong>auf</strong> gestoppt.<br />

Andernfalls wird das Programm wieder gestartet.<br />

Die Programmausführung erfolgt<br />

dann <strong>mit</strong> einem kleinen zeitlichen Versatz, der<br />

für die Prüfung der Status-Flags benötigt wird.<br />

Im Single-Step-Betrieb sind Debug-Tools<br />

ähnlich hilfreich, da die manuelle Auswertung<br />

der Status Flags entfällt. Gelbe und rote<br />

Pfeile zeigen dem Entwickler un<strong>mit</strong>telbar<br />

an, ob der nächste Befehl ausgeführt wird<br />

oder nicht.<br />

Abbildung 1 zeigt das HiTOP5 Software-<br />

Debug-Tool <strong>mit</strong> einem so genannten „<strong>bedingten</strong>“<br />

Breakpoint, erkenntlich am Fragezeichen<br />

im Punkt, und <strong>mit</strong> einem un<strong>bedingten</strong> Breakpoint,<br />

erkenntlich am Punkt. Im <strong>mit</strong>tleren<br />

Fenster zeigt ein Pfeil die aktuelle Position des<br />

Programmzählers pc für den nächsten auszuführenden<br />

Befehl<br />

nicht ausgeführt werden wird, da das zugehörige<br />

Carry-Flag C=1 gesetzt sein wird<br />

(<strong>mit</strong> r2=r3). Da<strong>mit</strong> wird der Programmabl<strong>auf</strong><br />

fortgesetzt und der Breakpoint ignoriert.<br />

Fazit<br />

Tabelle: <strong>ARM</strong> Befehlserweiterungen<br />

Cond Mnemonic Beschreibung Status Flags<br />

[31:27] Erweiterung<br />

0000 eq Equal Z=1<br />

0001 ne Not Equal Z=0<br />

0010 cs/hs Carry Set / Unsigned C=1<br />

Higher or Same<br />

0011 cc/lo Carry Clear / Unsigned Lower C=0<br />

0100 mi Minus / Negative N=1<br />

0101 pl Plus / Positive or Zero N=0<br />

0110 vs Overflow V=1<br />

0111 vc No Overflow V=0<br />

1000 hi Unsigned Higher C=1 und Z=0<br />

1001 ls Unsigned Lower or Same C=0 und Z=1<br />

1010 ge Signed Greater Than or Equal N=1 und V=1 oder<br />

N=0 und V=0<br />

1011 lt Signed Less Than N=1 und V=0 oder<br />

N=0 und V=1<br />

1100 gt Signed Greater Than Z=0 und (N=1 und =1)<br />

oder N=0 und V=0<br />

1101 le Signed Less Than or Equal Z=1 oder N=1 und V=0<br />

oder N=0 und V=1<br />

1110 al Always --<br />

1111 nv Never --<br />

die Optimierungen der C-Compiler lässt<br />

sich so<strong>mit</strong> sinnvoll in die Debug-Tools integrieren.<br />

Literatur<br />

[1] Advanced RISC Machines. Architectural Reference<br />

Manual. <strong>ARM</strong>, DDI 0100B, 1996<br />

cmp<br />

r2,r3<br />

Eine automatische Analyse der Status Flags<br />

erleichtert dem Software-Entwickler das Debuggen<br />

Dieser Beitrag als PDF und weiterführende<br />

an. Weiterhin erkennt man, dass der Befehl<br />

an der Stelle des <strong>bedingten</strong> Breakpoints<br />

seiner Anwendung und elimi-<br />

niert falsch interpretierte „Fehler“ im Programmabl<strong>auf</strong>.<br />

Dies ist vor allem beim Debuggen<br />

Informationen (ähnliche Beiträge, technische<br />

Daten, Direktlinks zum Hersteller etc.)<br />

sind online verfügbar <strong>auf</strong> www.<strong>EuE24.net</strong>.<br />

<strong>auf</strong> C-Ebene wichtig, da das Verhal-<br />

ldrcc r3, [pc, #11ch]<br />

ten nicht un<strong>mit</strong>telbar erkannt werden kann.<br />

Das Wissen um die Befehlsarchitektur und<br />

more @ click EE5B0102 ><br />

B.01<br />

Das Knowledge-Portal für<br />

Elektronik & Entwicklung<br />

E&E KOMPENDIUM 2005/2006<br />

93

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!