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.

Architektur:
Video zu dieser Lektion folgt.

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:

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

FlagNameist 1, wenn das Ergebnis …
ZFZero0 war
SFSignnegativ war (das oberste Bit ist 1)
CFCarryeinen Übertrag hatte: bei einer Subtraktion, wenn von einer kleineren Zahl (ohne Vorzeichen) eine größere abgezogen wurde
OFOverflowals Zahl mit Vorzeichen nicht ins Register passte
FlagNameist 1, wenn das Ergebnis …
ZZero0 war
NNegativenegativ war (das oberste Bit ist 1)
CCarrybei einer Addition einen Übertrag hatte; bei einer Subtraktion ist C 1, wenn kein Borgen nötig war (anders als beim Intel-CF)
VoVerflowals 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).

Sprungspringt, wenn (nach cmp a, b)Flags
je / jza = bZF=1
jne / jnza ≠ bZF=0
jla < b, mit Vorzeichen (less)SF≠OF
jlea ≤ b, mit VorzeichenZF=1 oder SF≠OF
jga > b, mit Vorzeichen (greater)ZF=0 und SF=OF
jgea ≥ b, mit VorzeichenSF=OF
jba < b, ohne Vorzeichen (below)CF=1
jbea ≤ b, ohne VorzeichenCF=1 oder ZF=1
jaa > b, ohne Vorzeichen (above)CF=0 und ZF=0
jaea ≥ b, ohne VorzeichenCF=0
Sprungspringt, wenn (nach cmp a, b)Flags
beqb.eqa = b (equal)Z=1
bneb.nea ≠ bZ=0
bltb.lta < b, mit Vorzeichen (less than)N≠V
bleb.lea ≤ b, mit VorzeichenZ=1 oder N≠V
bgtb.gta > b, mit Vorzeichen (greater than)Z=0 und N=V
bgeb.gea ≥ b, mit VorzeichenN=V
blob.loa < b, ohne Vorzeichen (lower)C=0
blsb.lsa ≤ b, ohne Vorzeichen (lower or same)C=0 oder Z=1
bhib.hia > b, ohne Vorzeichen (higher)C=1 und Z=0
bhsb.hsa ≥ 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

  1. Assembliere das Programm mit Strg+F9 und gehe mit F8 bis hinter den cmp.
  2. 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.
  3. 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.
  4. Mit F8 geht es weiter: Der Sprung wird nicht ausgeführt, die nächste markierte Zeile ist das mov.
  5. Experiment: Gehe das Programm noch einmal bis hinter den cmp. Schalte jetzt per Doppelklick das Flag OF um.Schalte jetzt per Doppelklick das Flag V um. Mit F8 springt das Programm jetzt über das mov: jge fragt nur, ob SF gleich OF istge fragt nur, ob N gleich V ist, 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