(→aus ihex (lokatiert)) |
(→Compiler dumpt Assembler) |
||
(7 dazwischenliegende Versionen von einem anderen Benutzer werden nicht angezeigt) | |||
Zeile 2: | Zeile 2: | ||
Die Ausgabe von gcc geschieht immer in Form von Assembler-Code als ASCII-Datei. Ohne die Angabe spezieller Optionen ist diese Datei jedoch nur temporär und wird nach Beenden der von gcc aufgerufenen Programme <tt>cc1</tt> ([[Compiler]]), <tt>as</tt> (Assembler) und <tt>ld</tt> (Linker) wieder gelöscht. | Die Ausgabe von gcc geschieht immer in Form von Assembler-Code als ASCII-Datei. Ohne die Angabe spezieller Optionen ist diese Datei jedoch nur temporär und wird nach Beenden der von gcc aufgerufenen Programme <tt>cc1</tt> ([[Compiler]]), <tt>as</tt> (Assembler) und <tt>ld</tt> (Linker) wieder gelöscht. | ||
− | Ein Weg, den zu einer Quelle gehörenden Assembler-Code zu | + | Ein Weg, an den zu einer C-Quelle gehörenden Assembler-Code zu kommen ist also, diese temporäre Datei zu erhalten. Dazu gibt man die gcc-Option <tt>-save-temps</tt> an oder man lässt gcc lediglich assemblieren, was mit der Option <tt>-S</tt> geschieht. |
Alternativ dazu kann man mit objcopy ein Disassemble der erzeugten Objekte erzeugen. | Alternativ dazu kann man mit objcopy ein Disassemble der erzeugten Objekte erzeugen. | ||
− | Ja nach dem, welchen dieser Möglichkeiten man wählt, enthält die Assembler-Ausgabe unterschiedliche Informationen, Darstellungen und Erläuterungen. | + | Ja nach dem, welchen dieser Möglichkeiten man wählt, enthält die Assembler-Ausgabe unterschiedliche Informationen, Darstellungen und Erläuterungen. Während die Assembler-Ausgabe von gcc direkt als Eingabe für den GNU-Assembler verwendet werden kann, sind die Disassembles und Assembler-Dumps des Assemblers für diesen Zweck nicht verwendbar. |
− | + | Zum besseren Verständnis der folgenden Abschnitte ist es hilfreich, den [[avr-gcc/Interna#Ablauf der Codegenerierung|Ablauf der Codegenerierung bei gcc]] zu kennen. | |
==C-Quelle== | ==C-Quelle== | ||
+ | Als C-Beispiel dient eine kleine Funktion, die einem Foren-Beitrag zu selbigem Thema entstammt. Die Assembler-Dumps sind jedoch nur Snips der kleinen Funktion <tt>foo</tt>, um die es hier gehen soll. Für komplexere Funktionen oder andere gcc-Targets (etwa <tt>arm7-gcc</tt>) ist die Vorgehensweise analog. Die <tt>main</tt>-Funktion wird nur gebraucht, damit für das letzte Beispiel ein elf erzeugt werden kann, da hier gegen <tt>main</tt> gelinkt wird. | ||
+ | |||
<pre> | <pre> | ||
unsigned char command_rc5; | unsigned char command_rc5; | ||
Zeile 32: | Zeile 34: | ||
==Compiler dumpt Assembler== | ==Compiler dumpt Assembler== | ||
− | + | ||
− | + | GCC erzeugt seine Ausgabe als Assembler-Datei; allerdings wird diese Assemblerdatei nur temporär angelegt und automatisch wieder gelöscht, nachdem der Assembler diese Datei gelesen und verarbeitet hat. | |
+ | |||
+ | Um ein Löschen dieser Datei zu unterbinden, dient ein Aufruf folgender Form: | ||
'''Kommando:''' | '''Kommando:''' | ||
− | < | + | > gcc -c -fverbose-asm -save-temps ... |
− | > | + | |
− | </ | + | Die Assembler-Datei wird als <tt>*.s</tt>-Datei angelegt. Zusätzlich wird mit <tt>-save-temps</tt> auch die |
+ | präcompilierte Quelle gesichert und zwar mit der Endung <tt>*.i</tt> für C-Quellen bzw. <tt>*.ii</tt> für C++-Quellen. Hier die <tt>.s</tt>-Datei für obige C-Quelle. <tt>-fverbose-asm</tt> hat die Assembler-Datei mit zusätzlichen, hilfreichen Informationen angereichert, wie | ||
+ | * Anzahl der gesicherten Register auf dem Stack. | ||
+ | * Die Größe des Frames, also des Bereichs, in dem lokale Variablen gespeichert werden, die nicht in Registern gehalten werden. | ||
+ | * Variablennamen soweit verfügbar, bzw. automatisch erzeugte Namen für Hilfsvariablen wie <tt>tmp23</tt>. | ||
+ | * Die durch Optimierungsschalter wie <tt>-Os</tt> aktivierten Schalter. | ||
− | '''Ausgabe | + | '''Ausgabe''' (hier für avr-gcc): |
<pre>.global foo | <pre>.global foo | ||
.type foo, @function | .type foo, @function | ||
Zeile 60: | Zeile 69: | ||
</pre> | </pre> | ||
− | + | Bei diesem Ansatz ist es günstig, ohne Debug-Informationen zu übersetzen, da ansonsten die Assembler-Ausgabe unleserlicher und deutlich größer wird. | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
==Assembler dumpt Assembler== | ==Assembler dumpt Assembler== | ||
'''Kommando:''' | '''Kommando:''' | ||
− | + | > gcc -Wa,-alhd=dump.s ... | |
− | + | Die Option <tt>-Wa</tt> bewirkt, daß die Zeichen nach dem Komma mit an die Assembler-Kommandozeile übergeben werden. Der Assembler wird also zusätzlich mit der Option <tt>-alhd=dump.s</tt> gestartet, was das Schreiben der Ausgabe bewirkt. | |
− | </ | + | |
− | + | ||
'''Ausgabe:''' | '''Ausgabe:''' | ||
<pre> 18 .global foo | <pre> 18 .global foo | ||
Zeile 103: | Zeile 101: | ||
==objdump erzeugt Disassemble== | ==objdump erzeugt Disassemble== | ||
− | === aus Object (nicht | + | === aus Object (nicht lokatiert)=== |
+ | Damit die Quellschnippsel im Dump angezeigt werden muss das Object mit Debug-Informationen erzeugt worden sein, und die Quelle muss noch vorhanden sein. | ||
'''Kommando:''' | '''Kommando:''' | ||
− | + | > avr-objdump -d -S ... | |
− | > avr-objdump -d -S ... | + | |
− | + | ||
'''Ausgabe:''' | '''Ausgabe:''' |
Aktuelle Version vom 21. Juli 2012, 10:15 Uhr
Aus den unterschiedlichsten Gründen kann es wünschenswert sein, den Assembler-Code zu sehen, den avr-gcc (oder gcc im Allgemeinen) aus einer C/C++-Quelle erzeugt. Die Ausgabe von gcc geschieht immer in Form von Assembler-Code als ASCII-Datei. Ohne die Angabe spezieller Optionen ist diese Datei jedoch nur temporär und wird nach Beenden der von gcc aufgerufenen Programme cc1 (Compiler), as (Assembler) und ld (Linker) wieder gelöscht.
Ein Weg, an den zu einer C-Quelle gehörenden Assembler-Code zu kommen ist also, diese temporäre Datei zu erhalten. Dazu gibt man die gcc-Option -save-temps an oder man lässt gcc lediglich assemblieren, was mit der Option -S geschieht.
Alternativ dazu kann man mit objcopy ein Disassemble der erzeugten Objekte erzeugen.
Ja nach dem, welchen dieser Möglichkeiten man wählt, enthält die Assembler-Ausgabe unterschiedliche Informationen, Darstellungen und Erläuterungen. Während die Assembler-Ausgabe von gcc direkt als Eingabe für den GNU-Assembler verwendet werden kann, sind die Disassembles und Assembler-Dumps des Assemblers für diesen Zweck nicht verwendbar.
Zum besseren Verständnis der folgenden Abschnitte ist es hilfreich, den Ablauf der Codegenerierung bei gcc zu kennen.
Inhaltsverzeichnis
C-Quelle
Als C-Beispiel dient eine kleine Funktion, die einem Foren-Beitrag zu selbigem Thema entstammt. Die Assembler-Dumps sind jedoch nur Snips der kleinen Funktion foo, um die es hier gehen soll. Für komplexere Funktionen oder andere gcc-Targets (etwa arm7-gcc) ist die Vorgehensweise analog. Die main-Funktion wird nur gebraucht, damit für das letzte Beispiel ein elf erzeugt werden kann, da hier gegen main gelinkt wird.
unsigned char command_rc5; unsigned char Ir_data_tmp; void foo() { uint8_t cmd = command_rc5 & ~(1<<7); if (Ir_data_tmp & (1<<5)) cmd |= (1<<7); command_rc5 = cmd; } int main main() { return 0; }
Compiler dumpt Assembler
GCC erzeugt seine Ausgabe als Assembler-Datei; allerdings wird diese Assemblerdatei nur temporär angelegt und automatisch wieder gelöscht, nachdem der Assembler diese Datei gelesen und verarbeitet hat.
Um ein Löschen dieser Datei zu unterbinden, dient ein Aufruf folgender Form:
Kommando:
> gcc -c -fverbose-asm -save-temps ...
Die Assembler-Datei wird als *.s-Datei angelegt. Zusätzlich wird mit -save-temps auch die präcompilierte Quelle gesichert und zwar mit der Endung *.i für C-Quellen bzw. *.ii für C++-Quellen. Hier die .s-Datei für obige C-Quelle. -fverbose-asm hat die Assembler-Datei mit zusätzlichen, hilfreichen Informationen angereichert, wie
- Anzahl der gesicherten Register auf dem Stack.
- Die Größe des Frames, also des Bereichs, in dem lokale Variablen gespeichert werden, die nicht in Registern gehalten werden.
- Variablennamen soweit verfügbar, bzw. automatisch erzeugte Namen für Hilfsvariablen wie tmp23.
- Die durch Optimierungsschalter wie -Os aktivierten Schalter.
Ausgabe (hier für avr-gcc):
.global foo .type foo, @function foo: /* prologue: frame size=0 */ /* prologue end (size=0) */ lds r25,command_rc5 ; cmd, command_rc5 andi r25,lo8(127) ; cmd, lds r24,Ir_data_tmp ; Ir_data_tmp, Ir_data_tmp sbrc r24,5 ; Ir_data_tmp, ori r25,lo8(-128) ; cmd, .L2: sts command_rc5,r25 ; command_rc5, cmd /* epilogue: frame size=0 */ ret /* epilogue end (size=1) */ /* function foo size 11 (10) */ .size foo, .-foo
Bei diesem Ansatz ist es günstig, ohne Debug-Informationen zu übersetzen, da ansonsten die Assembler-Ausgabe unleserlicher und deutlich größer wird.
Assembler dumpt Assembler
Kommando:
> gcc -Wa,-alhd=dump.s ...
Die Option -Wa bewirkt, daß die Zeichen nach dem Komma mit an die Assembler-Kommandozeile übergeben werden. Der Assembler wird also zusätzlich mit der Option -alhd=dump.s gestartet, was das Schreiben der Ausgabe bewirkt.
Ausgabe:
18 .global foo 20 foo: 21 .LFB2: 22 .LM1: 23 /* prologue: frame size=0 */ 24 /* prologue end (size=0) */ 25 .LM2: 26 0000 9091 0000 lds r25,command_rc5 27 0004 9F77 andi r25,lo8(127) 28 .LM3: 29 0006 8091 0000 lds r24,Ir_data_tmp 30 000a 85FD sbrc r24,5 31 .LM4: 32 000c 9068 ori r25,lo8(-128) 33 .L2: 34 .LM5: 35 000e 9093 0000 sts command_rc5,r25 36 /* epilogue: frame size=0 */ 37 0012 0895 ret 38 /* epilogue end (size=1) */ 39 /* function foo size 11 (10) */
objdump erzeugt Disassemble
aus Object (nicht lokatiert)
Damit die Quellschnippsel im Dump angezeigt werden muss das Object mit Debug-Informationen erzeugt worden sein, und die Quelle muss noch vorhanden sein.
Kommando:
> avr-objdump -d -S ...
Ausgabe:
00000000 <foo>: uint8_t command_rc5, Ir_data_tmp; void foo() { uint8_t cmd = command_rc5 & ~(1<<7); 0: 90 91 00 00 lds r25, 0x0000 4: 9f 77 andi r25, 0x7F ; 127 if (Ir_data_tmp & (1<<5)) 6: 80 91 00 00 lds r24, 0x0000 a: 85 fd sbrc r24, 5 cmd |= (1<<7); c: 90 68 ori r25, 0x80 ; 128 command_rc5 = cmd; e: 90 93 00 00 sts 0x0000, r25 12: 08 95 ret
aus elf (lokatiert)
Kommando:
> avr-objdump -h -S ...
Ausgabe:
0000005c <foo>: uint8_t command_rc5, Ir_data_tmp; void foo() { uint8_t cmd = command_rc5 & ~(1<<7); 5c: 90 91 60 00 lds r25, 0x0060 60: 9f 77 andi r25, 0x7F ; 127 if (Ir_data_tmp & (1<<5)) 62: 80 91 61 00 lds r24, 0x0061 66: 85 fd sbrc r24, 5 cmd |= (1<<7); 68: 90 68 ori r25, 0x80 ; 128 command_rc5 = cmd; 6a: 90 93 60 00 sts 0x0060, r25 6e: 08 95 ret
aus ihex (lokatiert)
Intel-HEX ist ein recht "dummes" Format, es speichert weder Section-Informationen noch Symbole, Debug-Informationen oder Quellbezüge. Dementsprechend muss man wissen, wo man nach was zu suchen hat im disassemblierten Spaghetti-Code. Zudem kann objdump nicht wissen, ob eine Sequqnz als Befehl oder als Datum zu interpretieren ist und hat keine Alignment-Info, was zu verwirrenden Disassembles führen kann, wenn objcopy z.B. mitten in einem 4-Byte-Befehl aufsetzt.
Da dieses Format auch nicht weiß, für welche Maschine es codiert, muss man dies wissen und angeben. Hier wurde der Code für einen ATmega8 erzeugt, welcher zur Gruppe avr4 gehört.
Kommando:
> avr-objdump -D -mavr4 ...
Ausgabe:
file format ihex Disassembly of section .sec1: 00000000 <.sec1>: ... 5c: 90 91 60 00 lds r25, 0x0060 60: 9f 77 andi r25, 0x7F ; 127 62: 80 91 61 00 lds r24, 0x0061 66: 85 fd sbrc r24, 5 68: 90 68 ori r25, 0x80 ; 128 6a: 90 93 60 00 sts 0x0060, r25 6e: 08 95 ret ...