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
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