Assembler-Kurs · Lektion 3
Sprünge, Flags und bedingte Sprünge
Am Ende kannst du den Ablauf eines Programms steuern: überspringen, vergleichen, verzweigen und eine Schleife bauen. Voraussetzung: Lektion 2.
Die Idee: der Befehlszeiger wandert
Der Prozessor führt Befehle der Reihe nach aus: Der Befehlszeiger (eip)(rip)(pc) zeigt auf den nächsten Befehl und rückt nach jedem Befehl weiter. Ein Sprung setzt den Befehlszeiger auf eine andere Adresse – meist die eines Labels. Es gibt zwei Arten:
- der unbedingte Sprung springt immer:
jmp zielb ziel(branch); - der bedingte Sprung springt nur, wenn eine Bedingung erfüllt ist, und läuft sonst einfach mit dem nächsten Befehl weiter.
; Lesson 3: jmp jumps always. The line in between is never executed. .format pe console .arch i386 .code start: mov eax, 1 jmp skip ; the next instruction to run is the one at "skip" mov eax, 99 ; skipped skip: ret ; exit code 1
; Lesson 3: jmp jumps always. The line in between is never executed. .format pe console .arch x64 .code start: mov eax, 1 jmp skip ; the next instruction to run is the one at "skip" mov eax, 99 ; skipped skip: ret ; exit code 1
; Lesson 3: b (branch) jumps always. The line in between is never executed. .format pe console .arch thumb .code start: movs r0, #1 b skip ; the next instruction to run is the one at "skip" movs r0, #99 ; skipped skip: bx lr ; exit code 1
; Lesson 3: b (branch) jumps always. The line in between is never executed. .format pe console .arch arm64 .code start: mov x0, #1 b skip ; the next instruction to run is the one at "skip" mov x0, #99 ; skipped skip: ret ; exit code 1
Das Programm endet mit dem Exit-Code 1: Der Befehl, der das Ergebnisregister auf 99 setzen würde, wird nie ausgeführt. Im Debugger siehst du, wie der Befehlszeiger mit dem Sprung über die Zeile hinwegspringt.
Flags: das Gedächtnis der CPU
Woher weiß ein bedingter Sprung, ob seine Bedingung erfüllt ist? Die CPU hat dafür einzelne Bits, die Flags (Fähnchen). Viele Befehle setzen sie als Nebenwirkung, wenn sie rechnen. Die vier wichtigsten:
| Flag | Name | ist 1, wenn das Ergebnis … |
|---|---|---|
ZF | Zero | 0 war |
SF | Sign | negativ war (das oberste Bit ist 1) |
CF | Carry | einen Übertrag hatte: bei einer Subtraktion, wenn von einer kleineren Zahl (ohne Vorzeichen) eine größere abgezogen wurde |
OF | Overflow | als Zahl mit Vorzeichen nicht ins Register passte |
| Flag | Name | ist 1, wenn das Ergebnis … |
|---|---|---|
Z | Zero | 0 war |
N | Negative | negativ war (das oberste Bit ist 1) |
C | Carry | bei einer Addition einen Übertrag hatte; bei einer Subtraktion ist C 1, wenn kein Borgen nötig war (anders als beim Intel-CF) |
V | oVerflow | als Zahl mit Vorzeichen nicht ins Register passte |
Nicht jeder Befehl setzt Flags: add, sub, dec, cmp tun es, mov nie. (inc und dec lassen CF unverändert.)
Nicht jeder Befehl setzt Flags. cmp tut es immer; Rechenbefehle nur, wenn ein s am Namen steht (adds, subs). Bei Thumb-2 haben die kurzen Formen von mov, add und sub dieses s sowieso (darum movs r0, #1 in Lektion 1).
cmp: vergleichen, ohne zu ändern
cmp a, bcmp a, b rechnet a − b, wirft das Ergebnis weg und behält nur die Flags. Alle Aussagen über „größer“ und „kleiner“ lassen sich aus ihnen ablesen. Für 7 − 12 = −5:
Das Ergebnis ist ungleich 0 (ZF=0), negativ (SF=1), hat bei einer Subtraktion ohne Vorzeichen geborgt (CF=1) und läuft nicht über (OF=0).
Das Ergebnis ist ungleich 0 (Z=0), negativ (N=1), hat ein Borgen gebraucht (C=0) und läuft nicht über (V=0).
Bedingte Sprünge
Ein bedingter Sprung folgt gewöhnlich auf einen cmp und fragt die Flags ab. Weil cmp nicht weiß, ob du deine Zahlen mit oder ohne Vorzeichen meinst, gibt es für „kleiner“ und „größer“ zwei Familien: signed (mit Vorzeichen, −1 ist kleiner als 1) und unsigned (ohne Vorzeichen, die Zahl aus lauter Einsen ist die größte).
| Sprung | springt, wenn (nach cmp a, b) | Flags |
|---|---|---|
je / jz | a = b | ZF=1 |
jne / jnz | a ≠ b | ZF=0 |
jl | a < b, mit Vorzeichen (less) | SF≠OF |
jle | a ≤ b, mit Vorzeichen | ZF=1 oder SF≠OF |
jg | a > b, mit Vorzeichen (greater) | ZF=0 und SF=OF |
jge | a ≥ b, mit Vorzeichen | SF=OF |
jb | a < b, ohne Vorzeichen (below) | CF=1 |
jbe | a ≤ b, ohne Vorzeichen | CF=1 oder ZF=1 |
ja | a > b, ohne Vorzeichen (above) | CF=0 und ZF=0 |
jae | a ≥ b, ohne Vorzeichen | CF=0 |
| Sprung | springt, wenn (nach cmp a, b) | Flags |
|---|---|---|
beqb.eq | a = b (equal) | Z=1 |
bneb.ne | a ≠ b | Z=0 |
bltb.lt | a < b, mit Vorzeichen (less than) | N≠V |
bleb.le | a ≤ b, mit Vorzeichen | Z=1 oder N≠V |
bgtb.gt | a > b, mit Vorzeichen (greater than) | Z=0 und N=V |
bgeb.ge | a ≥ b, mit Vorzeichen | N=V |
blob.lo | a < b, ohne Vorzeichen (lower) | C=0 |
blsb.ls | a ≤ b, ohne Vorzeichen (lower or same) | C=0 oder Z=1 |
bhib.hi | a > b, ohne Vorzeichen (higher) | C=1 und Z=0 |
bhsb.hs | a ≥ b, ohne Vorzeichen (higher or same) | C=1 |
Du musst die Flags nicht auswendig kennen; die Namen der Sprünge sagen, was sie meinen. Wichtig ist nur: Frage dich bei jedem Vergleich, ob deine Zahlen ein Vorzeichen haben.
Das größere von zwei Zahlen
; Lesson 3: cmp sets the flags, a conditional jump looks at them. Prints the larger of two numbers. .format pe console .arch i386 .import msvcrt.dll printf .code start: mov eax, 7 mov ecx, 12 cmp eax, ecx ; computes eax - ecx = -5, throws the result away, keeps the flags jge keep ; jump if greater or equal (signed): here not, 7 < 12 mov eax, ecx ; so the second number is the larger one keep: push eax ; the larger number push dword fmt call [printf] add esp, 8 xor eax, eax ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3: cmp sets the flags, a conditional jump looks at them. Prints the larger of two numbers. .format pe console .arch x64 .import msvcrt.dll printf .code start: sub rsp, 40 mov edx, 7 mov ecx, 12 cmp edx, ecx ; computes edx - ecx = -5, throws the result away, keeps the flags jge keep ; jump if greater or equal (signed): here not, 7 < 12 mov edx, ecx ; so the second number is the larger one keep: lea rcx, [fmt] ; the format (first argument), the number is already in edx (second) call [printf] xor eax, eax add rsp, 40 ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3: cmp sets the flags, a conditional branch looks at them. Prints the larger of two numbers. .format pe console .arch thumb .import msvcrt.dll printf .code start: push {r4, lr} movs r1, #7 movs r2, #12 cmp r1, r2 ; computes r1 - r2 = -5, throws the result away, keeps the flags bge keep ; branch if greater or equal (signed): here not, 7 < 12 mov r1, r2 ; so the second number is the larger one keep: adr r0, fmt ; the format (r0); the number is already in r1 ldr r3, printf blx r3 movs r0, #0 pop {r4, pc} .data align 4 fmt db 'The larger number is %d', 10, 0
; Lesson 3: cmp sets the flags, a conditional branch looks at them. Prints the larger of two numbers. .format pe console .arch arm64 .import msvcrt.dll printf .code start: stp x29, x30, [sp, #-16]! mov w1, #7 mov w2, #12 cmp w1, w2 ; computes w1 - w2 = -5, throws the result away, keeps the flags b.ge keep ; branch if greater or equal (signed): here not, 7 < 12 mov w1, w2 ; so the second number is the larger one keep: adrp x0, fmt add x0, x0, :lo12:fmt ; the format (x0); the number is already in w1 adrp x16, printf ldr x16, [x16, :lo12:printf] blr x16 mov x0, #0 ldp x29, x30, [sp], #16 ret .data fmt db 'The larger number is %d', 10, 0
Das Programm vergleicht 7 und 12. Der Sprung jge springt nur, wenn die erste Zahl größer oder gleich der zweiten istDer Sprung bge springt nur, wenn die erste Zahl größer oder gleich der zweiten istDer Sprung b.ge springt nur, wenn die erste Zahl größer oder gleich der zweiten ist – hier nicht. Dann läuft der nächste Befehl und kopiert die zweite Zahl in das Register, das ausgegeben wird. Ist der Sprung erfüllt, wird dieser Befehl übersprungen, und die erste Zahl bleibt. In beiden Fällen geht es hinter dem Label keep weiter: ein Zweig, der wieder zusammenläuft.
Dasselbe in der Schreibweise von Asmonite
Asmonite kennt für solche Verzweigungen eine kürzere Schreibweise: if … endif. Der Assembler macht daraus genau das cmp und den bedingten Sprung, die du gerade von Hand geschrieben hast: if signed a < b wird zu cmp a, b und dem Sprung mit der gegenteiligen Bedingung um den Block (jgebgeb.ge für „größer oder gleich“). Der Block läuft also, wenn die Bedingung stimmt. Das Wort signed sagt, dass die Zahlen ein Vorzeichen haben; mit unsigned nimmst du die andere Familie.
; Lesson 3: the same program in the notation of Asmonite: if ... endif instead of cmp and the jump. ; "if signed a < b" is cmp a, b and the jump with the opposite condition around the block (jge). .format pe console .arch i386 .import msvcrt.dll printf .code start: mov eax, 7 mov ecx, 12 if signed eax < ecx ; cmp eax, ecx / jge: the block runs when eax < ecx mov eax, ecx ; so the second number is the larger one endif push eax ; the larger number push dword fmt call [printf] add esp, 8 xor eax, eax ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3: the same program in the notation of Asmonite: if ... endif instead of cmp and the jump. ; "if signed a < b" is cmp a, b and the jump with the opposite condition around the block (jge). .format pe console .arch x64 .import msvcrt.dll printf .code start: sub rsp, 40 mov edx, 7 mov ecx, 12 if signed edx < ecx ; cmp edx, ecx / jge: the block runs when edx < ecx mov edx, ecx ; so the second number is the larger one endif lea rcx, [fmt] ; the format (first argument), the number is already in edx (second) call [printf] xor eax, eax add rsp, 40 ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3: the same program in the notation of Asmonite: if ... endif instead of cmp and the jump. ; "if signed a < b" is cmp a, b and the jump with the opposite condition around the block (jge). .format pe console .arch thumb .import msvcrt.dll printf .code start: push {r4, lr} movs r1, #7 movs r2, #12 if signed r1 < r2 ; cmp r1, r2 / bge: the block runs when r1 < r2 mov r1, r2 ; so the second number is the larger one endif adr r0, fmt ; the format (r0); the number is already in r1 ldr r3, printf blx r3 movs r0, #0 pop {r4, pc} .data align 4 fmt db 'The larger number is %d', 10, 0
; Lesson 3: the same program in the notation of Asmonite: if ... endif instead of cmp and the jump. ; "if signed a < b" is cmp a, b and the jump with the opposite condition around the block (jge). .format pe console .arch arm64 .import msvcrt.dll printf .code start: stp x29, x30, [sp, #-16]! mov w1, #7 mov w2, #12 if signed w1 < w2 ; cmp w1, w2 / b.ge: the block runs when w1 < w2 mov w1, w2 ; so the second number is the larger one endif adrp x0, fmt add x0, x0, :lo12:fmt ; the format (x0); the number is already in w1 adrp x16, printf ldr x16, [x16, :lo12:printf] blr x16 mov x0, #0 ldp x29, x30, [sp], #16 ret .data fmt db 'The larger number is %d', 10, 0
Das Programm tut dasselbe wie das obere. Die Rohform solltest du trotzdem kennen, denn im Debugger, in Disassemblern und in fremdem Code siehst du nur sie. Mehr zur Schreibweise (else, if zero und andere) steht auf der Seite if, else, do … while.
In Asmonite ausprobieren
- Assembliere das Programm mit Strg+F9 und gehe mit F8 bis hinter den
cmp. - Sieh dir die Flags rechts an:
ZF=0,SF=1,CF=1,OF=0– wie oben für 7 − 12 berechnet. Geänderte Werte sind rot. - Sieh dir die Flags rechts an:
N=1,Z=0,C=0,V=0– wie oben für 7 − 12 berechnet. Geänderte Werte sind rot. - Mit F8 geht es weiter: Der Sprung wird nicht ausgeführt, die nächste markierte Zeile ist das
mov. - Experiment: Gehe das Programm noch einmal bis hinter den
cmp. Schalte jetzt per Doppelklick das FlagOFum.Schalte jetzt per Doppelklick das FlagVum. Mit F8 springt das Programm jetzt über dasmov:jgefragt nur, obSFgleichOFistgefragt nur, obNgleichVist, und du hast die Antwort geändert. Es ist also wirklich nur das Flag, das entscheidet.
Eine Schleife
Springt ein bedingter Sprung zurück auf ein Label, entsteht eine Schleife: Die Befehle dazwischen laufen so lange, wie die Bedingung gilt. Dieses Programm zählt von 5 bis 1 herunter und gibt jede Zahl aus.
; Lesson 3: a loop. dec sets the zero flag when the result is 0, jnz jumps back while it is not. .format pe console .arch i386 .import msvcrt.dll printf .code start: mov ebx, 5 ; ebx survives the call of printf again: push ebx push dword fmt call [printf] add esp, 8 dec ebx ; ebx = ebx - 1; ZF = 1 when it becomes 0 jnz again ; jump if not zero: back to "again" xor eax, eax ret .data fmt db '%d', 10, 0
; Lesson 3: a loop. dec sets the zero flag when the result is 0, jnz jumps back while it is not. .format pe console .arch x64 .import msvcrt.dll printf .code start: push rbx ; rbx must come back unchanged: we save it ... sub rsp, 32 ; shadow space (push made the stack aligned) mov ebx, 5 ; ... and may use it: it survives the call of printf again: lea rcx, [fmt] mov edx, ebx call [printf] dec ebx ; ebx = ebx - 1; ZF = 1 when it becomes 0 jnz again ; jump if not zero: back to "again" xor eax, eax add rsp, 32 pop rbx ret .data fmt db '%d', 10, 0
; Lesson 3: a loop. subs sets the zero flag when the result is 0, bne branches back while it is not. .format pe console .arch thumb .import msvcrt.dll printf .code start: push {r4, r5, r6, lr} ; r4 to r6 survive a call and must come back unchanged movs r4, #5 ; the counter ldr r5, printf again: adr r0, fmt mov r1, r4 blx r5 subs r4, r4, #1 ; r4 = r4 - 1; the "s" sets the flags: Z = 1 when it becomes 0 bne again ; branch if not equal (Z = 0): back to "again" movs r0, #0 pop {r4, r5, r6, pc} .data align 4 fmt db '%d', 10, 0
; Lesson 3: a loop. subs sets the zero flag when the result is 0, b.ne branches back while it is not. .format pe console .arch arm64 .import msvcrt.dll printf .code start: stp x29, x30, [sp, #-32]! stp x19, x20, [sp, #16] ; x19, x20 survive a call and must come back unchanged mov w19, #5 ; the counter adrp x20, printf ldr x20, [x20, :lo12:printf] again: adrp x0, fmt add x0, x0, :lo12:fmt mov w1, w19 blr x20 subs w19, w19, #1 ; w19 = w19 - 1; the "s" sets the flags: Z = 1 when it becomes 0 b.ne again ; branch if not equal (Z = 0): back to "again" mov x0, #0 ldp x19, x20, [sp, #16] ldp x29, x30, [sp], #32 ret .data fmt db '%d', 10, 0
dec ebx zieht 1 ab und setzt dabei die Flags: Wird das Ergebnis 0, ist ZF=1. jnz again (jump if not zero) springt zurück, solange ZF=0 ist. Ein extra cmp ist nicht nötig, weil dec die Flags schon gesetzt hat. Das Zählregister ebx muss den Aufruf von printf überstehen; Funktionen müssen ebx unverändert zurückgeben, deshalb liegt der Zähler dort und nicht in eax.
subs r4, r4, #1 zieht 1 ab und setzt dabei die Flags (das s): Wird das Ergebnis 0, ist Z=1. bne again springt zurück, solange Z=0 ist. Ein extra cmp ist nicht nötig. Der Zähler liegt in r4, weil printf r4 bis r11 unverändert zurückgeben muss; r0 bis r3 darf es verändern. Aus dem gleichen Grund sichert das Programm r4 bis r6 am Anfang und stellt sie am Ende wieder her.
subs w19, w19, #1 zieht 1 ab und setzt dabei die Flags (das s): Wird das Ergebnis 0, ist Z=1. b.ne again springt zurück, solange Z=0 ist. Ein extra cmp ist nicht nötig. Der Zähler liegt in w19, weil printf x19 bis x28 unverändert zurückgeben muss; x0 bis x17 darf es verändern. Aus dem gleichen Grund sichert das Programm x19 und x20 am Anfang und stellt sie am Ende wieder her.
Was du siehst
The larger number is 12
5 4 3 2 1
Mit einem Haltepunkt auf dem Sprung am Ende der Schleife und F9 siehst du bei jedem Halt das Zählregister um 1 kleiner. Beim letzten Durchlauf wird es 0, das Zero-Flag geht auf 1, und der Sprung bleibt aus.
Übungen
1. Das kleinere von zwei Zahlen
Ändere das Programm so, dass es die kleinere Zahl ausgibt. Du musst nur einen Befehl ändern.
Lösung
Statt jge keep schreibst du jle keep: Ist die erste Zahl kleiner oder gleich der zweiten, bleibt sie.
Statt bge keep schreibst du ble keep: Ist die erste Zahl kleiner oder gleich der zweiten, bleibt sie.
Statt b.ge keep schreibst du b.le keep: Ist die erste Zahl kleiner oder gleich der zweiten, bleibt sie.
2. Aufwärts zählen
Lass die Schleife von 1 bis 5 hoch zählen. Du brauchst jetzt einen cmp, weil das Zählen nicht bei 0 endet.
Lösung
; Lesson 3: exercise 2: counting up. cmp compares with 6, jne jumps back while the counter is not 6. .format pe console .arch i386 .import msvcrt.dll printf .code start: mov ebx, 1 ; ebx survives the call of printf again: push ebx push dword fmt call [printf] add esp, 8 inc ebx cmp ebx, 6 ; zero flag = 1 when ebx is 6 jne again ; jump if not equal: back to "again" xor eax, eax ret .data fmt db '%d', 10, 0
; Lesson 3: exercise 2: counting up. cmp compares with 6, jne jumps back while the counter is not 6. .format pe console .arch x64 .import msvcrt.dll printf .code start: push rbx ; rbx must come back unchanged: we save it ... sub rsp, 32 ; shadow space (push made the stack aligned) mov ebx, 1 ; ... and may use it: it survives the call of printf again: lea rcx, [fmt] mov edx, ebx call [printf] inc ebx cmp ebx, 6 ; zero flag = 1 when ebx is 6 jne again ; jump if not equal: back to "again" xor eax, eax add rsp, 32 pop rbx ret .data fmt db '%d', 10, 0
; Lesson 3: exercise 2: counting up. cmp compares with 6, bne branches back while the counter is not 6. .format pe console .arch thumb .import msvcrt.dll printf .code start: push {r4, r5, r6, lr} ; r4 to r6 survive a call and must come back unchanged movs r4, #1 ; the counter ldr r5, printf again: adr r0, fmt mov r1, r4 blx r5 adds r4, r4, #1 cmp r4, #6 ; Z = 1 when r4 is 6 bne again ; branch if not equal (Z = 0): back to "again" movs r0, #0 pop {r4, r5, r6, pc} .data align 4 fmt db '%d', 10, 0
; Lesson 3: exercise 2: counting up. cmp compares with 6, b.ne branches back while the counter is not 6. .format pe console .arch arm64 .import msvcrt.dll printf .code start: stp x29, x30, [sp, #-32]! stp x19, x20, [sp, #16] ; x19, x20 survive a call and must come back unchanged mov w19, #1 ; the counter adrp x20, printf ldr x20, [x20, :lo12:printf] again: adrp x0, fmt add x0, x0, :lo12:fmt mov w1, w19 blr x20 add w19, w19, #1 cmp w19, #6 ; Z = 1 when w19 is 6 b.ne again ; branch if not equal (Z = 0): back to "again" mov x0, #0 ldp x19, x20, [sp, #16] ldp x29, x30, [sp], #32 ret .data fmt db '%d', 10, 0
Der Zähler beginnt bei 1, wird um 1 erhöht und mit 6 verglichen. Solange er nicht 6 ist, geht es zurück. Die Schleife läuft also für 1, 2, 3, 4 und 5.
3. Knobelaufgabe: mit oder ohne Vorzeichen
Setze im Vergleichsprogramm die erste Zahl auf −1 und die zweite auf 1. Was gibt das Programm mit dem Sprung für Zahlen mit Vorzeichen aus (jgebgeb.ge), und was, wenn du den Sprung für Zahlen ohne Vorzeichen nimmst (jaebhsb.hs)? Sage es vorher voraus und prüfe es im Debugger.
Lösung
Mit Vorzeichen ist −1 kleiner als 1: der Sprung bleibt aus, das Programm gibt die 1 aus. Ohne Vorzeichen ist −1 dieselbe Bitfolge wie 4294967295, und die ist größer als 1: der Sprung wird ausgeführt, das Programm gibt −1 aus (%d liest dieselben Bits wieder mit Vorzeichen). Dieselben Bits, zwei Bedeutungen – welche gilt, bestimmt allein der Sprung, den du wählst. In der Schreibweise von Asmonite ist es nur ein anderes Wort: if signed … gegen if unsigned ….
; Lesson 3, exercise 3: the same with -1 and 1, but an unsigned comparison. .format pe console .arch i386 .import msvcrt.dll printf .code start: mov eax, -1 ; 0xFFFFFFFF: -1 as a signed number, 4294967295 as an unsigned one mov ecx, 1 cmp eax, ecx jae keep ; jump if above or equal (unsigned): 4294967295 >= 1, so it jumps mov eax, ecx keep: push eax ; the larger number push dword fmt call [printf] add esp, 8 xor eax, eax ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3, exercise 3: the same with -1 and 1, but an unsigned comparison. .format pe console .arch x64 .import msvcrt.dll printf .code start: sub rsp, 40 mov edx, -1 ; 0xFFFFFFFF: -1 as a signed number, 4294967295 as an unsigned one mov ecx, 1 cmp edx, ecx jae keep ; jump if above or equal (unsigned): 4294967295 >= 1, so it jumps mov edx, ecx keep: lea rcx, [fmt] ; the format (first argument), the number is already in edx (second) call [printf] xor eax, eax add rsp, 40 ret .data fmt db 'The larger number is %d', 10, 0
; Lesson 3, exercise 3: the same with -1 and 1, but an unsigned comparison. .format pe console .arch thumb .import msvcrt.dll printf .code start: push {r4, lr} movs r1, #-1 ; 0xFFFFFFFF: -1 as a signed number, 4294967295 as an unsigned one movs r2, #1 cmp r1, r2 bhs keep ; branch if higher or same (unsigned): 4294967295 >= 1, so it branches mov r1, r2 keep: adr r0, fmt ; the format (r0); the number is already in r1 ldr r3, printf blx r3 movs r0, #0 pop {r4, pc} .data align 4 fmt db 'The larger number is %d', 10, 0
; Lesson 3, exercise 3: the same with -1 and 1, but an unsigned comparison. .format pe console .arch arm64 .import msvcrt.dll printf .code start: stp x29, x30, [sp, #-16]! mov w1, #-1 ; 0xFFFFFFFF: -1 as a signed number, 4294967295 as an unsigned one mov w2, #1 cmp w1, w2 b.hs keep ; branch if higher or same (unsigned): 4294967295 >= 1, so it branches mov w1, w2 keep: adrp x0, fmt add x0, x0, :lo12:fmt ; the format (x0); the number is already in w1 adrp x16, printf ldr x16, [x16, :lo12:printf] blr x16 mov x0, #0 ldp x29, x30, [sp], #16 ret .data fmt db 'The larger number is %d', 10, 0
Merke
- Ein Sprung ändert den Befehlszeiger. Ein unbedingter Sprung springt immer, ein bedingter nur, wenn die Flags es sagen.
- Die Flags (Zero, Sign/Negative, Carry, Overflow) setzen Rechenbefehle und
cmp;cmp a, brechneta − bund behält nur die Flags. - Für „kleiner“ und „größer“ gibt es zwei Familien: mit Vorzeichen (
l,g) und ohne (b,a; ARM:lo,hi). - Ein bedingter Sprung zurück ist eine Schleife.
- Im Debugger kannst du Flags per Doppelklick umschalten und so sehen, dass sie allein den Sprung entscheiden.