Assembler-Kurs · Lektion 1
Register und der erste Befehl
Am Ende kennst du Register, den Befehl mov und hast ein vollständiges Programm gestartet und im Debugger Schritt für Schritt beobachtet. Voraussetzung: Asmonite ist entpackt, sonst nichts.
Die Idee: Register
Ein Prozessor rechnet nicht mit Zahlen im Hauptspeicher, sondern mit Zahlen in Registern: wenigen, winzigen und sehr schnellen Speicherplätzen direkt in der CPU. Ein Assemblerprogramm besteht zum großen Teil daraus, Zahlen in Register zu holen, dort etwas mit ihnen zu tun und sie wieder wegzuschreiben. Jedes Register hat einen Namen, den du im Programm schreibst.
Der i386 hat acht 32-Bit-Register für allgemeine Zwecke: eax, ebx, ecx, edx, esi, edi, ebp und esp. (esp ist der Stackzeiger, ebp meist der Rahmenzeiger; beide lässt du vorerst in Ruhe.) Dazu kommen der Befehlszeiger eip (er zeigt auf den nächsten Befehl) und die Flags (ab Lektion 3).
Der x64 hat sechzehn 64-Bit-Register: rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp und r8 bis r15. Die unteren 32 Bit eines Registers haben einen eigenen Namen: eax ist die untere Hälfte von rax, r8d die von r8. (rsp ist der Stackzeiger; lass ihn vorerst in Ruhe.) Dazu kommen der Befehlszeiger rip und die Flags (ab Lektion 3).
ARM 32 hat sechzehn 32-Bit-Register r0 bis r15. Die letzten drei haben Sonderrollen und zweite Namen: r13 ist sp (Stackzeiger), r14 ist lr (Link-Register: hier steht die Rücksprungadresse) und r15 ist pc (der Befehlszeiger). Für eigene Werte nimmst du r0 bis r12. In diesem Kurs steht ARM 32 für den Thumb-2-Befehlssatz (.arch thumb), den Windows on ARM benutzt.
ARM 64 hat 31 allgemeine 64-Bit-Register x0 bis x30. Die unteren 32 Bit eines Registers heißen w0 bis w30. x30 ist auch lr (hier steht die Rücksprungadresse). Dazu kommen der Stackzeiger sp und der Befehlszeiger pc; mit pc kannst du nicht wie mit einem Register rechnen.
Eines der Register ist besonders: Das Programm gibt sein Ergebnis in einem festen Register an den Aufrufer zurück. Bei einem Windows-Programm wird es zum Exit-Code, den du in der Kommandozeile oder im Debugger siehst.
| Architektur | Register für das Ergebnis |
|---|---|
| i386 | eax |
| x64 | rax (eax ist seine untere Hälfte) |
| ARM 32 | r0 |
| ARM 64 | x0 (w0 ist seine untere Hälfte) |
Das kleinste Programm
Zwei Befehle genügen: eine Zahl in das Ergebnisregister schreiben und zurückkehren.
; Lesson 1: the smallest program. EAX = 1, then back to Windows: the exit code is 1. .format pe console .arch i386 .code start: mov eax, 1 ret
; Lesson 1: the smallest program. RAX = 1, then back to Windows: the exit code is 1. .format pe console .arch x64 .code start: mov eax, 1 ret
; Lesson 1: the smallest program. R0 = 1, then back to Windows: the exit code is 1. .format pe console .arch thumb .code start: movs r0, #1 bx lr
; Lesson 1: the smallest program. X0 = 1, then back to Windows: the exit code is 1. .format pe console .arch arm64 .code start: mov x0, #1 ret
.format pe consolesagt dem Assembler, dass er ein Windows-Programm für die Konsole machen soll (Kopf, Abschnitte, alles Nötige schreibt er selbst)..archwählt den Prozessor. Beides sind Direktiven: Anweisungen an den Assembler, keine Befehle für die CPU..codebeginnt den Abschnitt mit Befehlen.start:ist ein Label, ein Name für eine Stelle im Programm. Das Programm beginnt dort, wostartsteht.mov eax, 1kopiert die Zahl 1 ineax. Das Ziel steht immer links, die Quelle rechts:mov ziel, quelle.retkehrt zu dem zurück, der das Programm aufgerufen hat (Windows);eaxwird zum Exit-Code.mov eax, 1kopiert die Zahl 1 ineax, die untere Hälfte vonrax. Das Ziel steht immer links, die Quelle rechts:mov ziel, quelle. Schreibst du in die untere Hälfte, wird die obere Hälfte auf 0 gesetzt,raxist also danach 1.retkehrt zu dem zurück, der das Programm aufgerufen hat (Windows).movs r0, #1kopiert die Zahl 1 nachr0; auch hier steht das Ziel links. Das#kennzeichnet eine Zahl im Befehl. Dassam Ende bedeutet: auch die Flags setzen (Lektion 3); im Thumb-Befehlssatz ist das die kurze Form dieses Befehls.bx lrspringt an die Adresse inlr, also zurück zu dem, der das Programm aufgerufen hat (Windows).mov x0, #1kopiert die Zahl 1 nachx0; auch hier steht das Ziel links. Das#kennzeichnet eine Zahl im Befehl.retspringt an die Adresse inx30(lr), also zurück zu dem, der das Programm aufgerufen hat (Windows).
In Asmonite ausprobieren
- Starte Asmonite, lege eine neue Datei an und füge den Code oben ein (für die Architektur, die du gewählt hast). Speichere sie, zum Beispiel als
erstes.asm. - Drücke Strg+F9: der Assembler übersetzt das Programm. Im Ausgabefenster steht, was er gemacht hat; Fehler stehen dort mit Zeilennummer.
- Drücke F8: das Programm startet und hält am ersten Befehl. Rechts siehst du die Register. In der Quelldatei markiert eine Zeile den nächsten Befehl, der ausgeführt wird.
- Drücke F8 noch einmal: der erste Befehl läuft. Das Register mit dem Ergebnis hat jetzt den Wert 1, und weil es sich geändert hat, ist es rot markiert.
- Drücke F8 ein drittes Mal: der Rücksprung beendet das Programm. Im Ausgabefenster steht der Exit-Code, hier 1.
Dein Rechner ist (vermutlich) kein ARM-Rechner. ARM-Programme laufen auf einem Windows-on-ARM-Gerät; wie du Asmonite mit einem solchen Gerät verbindest, steht auf der Seite Windows on ARM. Bis dahin kannst du die ARM-Programme lesen, assemblieren und mit den Intel-Fassungen vergleichen.
Dein Rechner ist (vermutlich) kein ARM-Rechner. ARM-Programme laufen auf einem Windows-on-ARM-Gerät; wie du Asmonite mit einem solchen Gerät verbindest, steht auf der Seite Windows on ARM. Bis dahin kannst du die ARM-Programme lesen, assemblieren und mit den Intel-Fassungen vergleichen.
Was du siehst
Vor dem ersten F8 hat das Ergebnisregister irgendeinen Wert, den Windows dort hinterlassen hat. Nach dem ersten F8 steht dort die 1 (in rot). Der Befehlszeiger eiprippc ist um die Länge des Befehls weitergerückt. Das ist das ganze Programm: ein Befehl verändert den Zustand der CPU, der nächste ist an der Reihe.
Übungen
1. Anderer Exit-Code
Ändere die Zahl so, dass das Programm mit dem Exit-Code 42 endet. Prüfe es im Debugger.
Lösung
Aus mov eax, 1 wird mov eax, 42.
Aus movs r0, #1 wird movs r0, #42.
Aus mov x0, #1 wird mov x0, #42.
2. Ein Register ins andere
Schreibe erst die Zahl 5 in ein anderes Register als das Ergebnisregister und kopiere sie dann mit mov in das Ergebnisregister. Beobachte beide Register mit F8. Wie viele Befehle brauchst du?
Lösung
mov ebx, 5
mov eax, ebx
mov ecx, 5
mov eax, ecx
movs r1, #5
mov r0, r1
mov x1, #5
mov x0, x1
Drei Befehle mit ret, zwei davon mov. Der zweite kopiert ein Register in ein Register: Das Ziel steht wieder links, die Quelle rechts, und die Quelle behält ihren Wert (kopieren, nicht verschieben).
3. Knobelaufgabe: tauschen
Setze eax auf 3 und ebx auf 4 (bei ARM: die Register mit den Nummern 0 und 1) und vertausche ihre Inhalte, ohne dass ein Wert verloren geht. Am Ende soll das Ergebnisregister die 4 enthalten, das Programm also mit dem Exit-Code 4 enden. Du darfst ein drittes Register benutzen.
Lösung
Wie beim Tauschen zweier Gläser mit Inhalt brauchst du ein leeres dazu: Wert A in das freie Register, B nach A, das freie nach B.
mov eax, 3 mov ebx, 4 mov ecx, eax ; frei = A mov eax, ebx ; A = B mov ebx, ecx ; B = frei
mov eax, 3 mov ebx, 4 mov ecx, eax ; frei = A mov eax, ebx ; A = B mov ebx, ecx ; B = frei
movs r0, #3 movs r1, #4 mov r2, r0 ; frei = A mov r0, r1 ; A = B mov r1, r2 ; B = frei
mov x0, #3 mov x1, #4 mov x2, x0 ; frei = A mov x0, x1 ; A = B mov x1, x2 ; B = frei
Merke
- Register sind winzige, schnelle Speicherplätze in der CPU. Jedes hat einen Namen.
mov ziel, quellekopiert; das Ziel steht links.- Das Ergebnis eines Programms steht im festen Ergebnisregister und wird zum Exit-Code.
- Der Befehlszeiger zeigt auf den nächsten Befehl; ein Befehl nach dem anderen verändert den Zustand der CPU.
- Mit Strg+F9 übersetzt du, mit F8 gehst du Schritt für Schritt durch das Programm.