if, else, endif, do … while

Ein Vergleich, ein Block – und die Schleife dazu: ohne eigenes Label und ohne den Sprung von Hand.

Statt cmp, eines bedingten Sprungs und einer Marke schreibst du, was du meinst: if ebx == [0x04+eax], den Block und endif. Der Assembler schreibt den Vergleich und den Sprung der Gegenbedingung selbst. Es ist kein Ausdruck und keine Makro-Sprache: ein direkter Vergleich zweier Operanden, und bei < und > sagst du, ob mit Vorzeichen oder ohne.

Für .arch i386 (32 Bit), überall im Code und auch in einer func. Die Bytes stammen aus dem echten Assembler.

Die Idee

Der Block läuft nur, wenn der Vergleich stimmt. Dazu überspringt der Assembler ihn mit dem Sprung der Gegenbedingung: bei == ein jne.

Du schreibst
        if ebx == [0x04+eax]
          mov dword [0x04+eax], 0xFFFFFFFF
        endif
        mov eax, [eax]
Gleichwertig von Hand, und die Bytes
        cmp ebx, [0x04+eax]
        jne skip              ; the opposite condition
        mov dword [0x04+eax], 0xFFFFFFFF
skip:
        mov eax, [eax]
3B 58 04 75 07 C7 40 04 FF FF FF FF
8B 00
vom Assembler ergänzt (if, else, do, while) deine Zeilen, unverändert

Die Marke macht der Assembler selbst (asmogen_if…, sie ist im Programm nicht zu sehen). Der Debugger führt die if-Zeile, also den Vergleich und den Sprung, als einen Schritt aus.

Die Form

if [signed|unsigned] Operand Vergleich Operand, der Block, optional else mit einem zweiten Block, und endif. Die Operanden sind alles, was cmp nimmt: Register, Zahlen, Labels, Speicher in eckigen Klammern.

Du schreibstErzeugt und BytesAnmerkung
if eax == ebx
  nop
endif
  cmp eax, ebx
  jne skip
  nop
skip:
3B C3 75 01 90
gleich: cmp, dann jne über den Block (75 01 überspringt das eine Byte des Blocks)
if eax != 5
  nop
endif
  cmp eax, 5
  je skip
  nop
skip:
83 F8 05 74 01 90
ungleich: je über den Block; die rechte Seite darf eine Zahl sein
if dword [0x1000] == 5
  nop
endif
  cmp dword [0x1000], 5
  jne skip
  nop
skip:
83 3D 00 10 00 00 05 75 01 90
Speicher gegen eine Zahl braucht die Größe (byte, word, dword), wie bei cmp
if signed eax < ebx
  nop
endif
  cmp eax, ebx
  jge skip
  nop
skip:
3B C3 7D 01 90
ein Ordnungsvergleich sagt signed oder unsigned; signed < springt über den Block mit jge
if unsigned eax >= [ebx]
  nop
endif
  cmp eax, [ebx]
  jb skip
  nop
skip:
3B 03 72 01 90
Speicher darf auf einer Seite stehen; unsigned >= springt mit jb über den Block
vom Assembler ergänzt (if, endif) deine Zeilen, unverändert

Die Vergleiche sind ==, !=, <, <=, > und >=. In einer func gelten die Namen der Parameter und Lokalen auch im Vergleich: if signed eax < b.

Mit oder ohne Vorzeichen

Dieselben Bits sind je nach Sicht verschieden groß: 0xFFFFFFFF ist mit Vorzeichen -1, ohne Vorzeichen 4294967295. -1 < 1 stimmt also mit Vorzeichen und nicht ohne. Adressen, Längen und Zähler sind meist vorzeichenlos, Zahlen mit Vorzeichen eben nicht – deshalb verlangt Asmonite die Antwort in jeder Zeile (oder mit .compare, siehe unten). == und != brauchen sie nicht.

VergleichvorzeichenbehaftetSprung über den BlockvorzeichenlosSprung über den Block
<if signed eax < ebxjge 7D 01if unsigned eax < ebxjae 73 01
<=if signed eax <= ebxjg 7F 01if unsigned eax <= ebxja 77 01
>if signed eax > ebxjle 7E 01if unsigned eax > ebxjbe 76 01
>=if signed eax >= ebxjl 7C 01if unsigned eax >= ebxjb 72 01

Die Sprünge der rechten Spalten sind die der Gegenbedingung: sie überspringen den Block, wenn der Vergleich nicht stimmt. Mit Vorzeichen sind es jl, jle, jg, jge, ohne Vorzeichen jb, jbe, ja, jae.

Bits prüfen: &

Mit & statt eines Vergleichs wird der if zu test: der Block läuft, wenn mindestens ein Bit in beiden Operanden gesetzt ist. & ist hier kein Rechenausdruck, sondern die ganze Bedingung – ein &, danach höchstens == 0 oder != 0. Ein Wort signed oder unsigned gibt es dabei nicht: ob etwas null ist, hängt nicht vom Vorzeichen ab.

Du schreibstDer Block läuft, wenn …Erzeugt
if eax & ebxein Bit in beiden gesetzt isttest eax, ebx + jz 85 D8 74 01
if eax & ebx != 0wie oben (andere Schreibweise)test eax, ebx + jz 85 D8 74 01
if eax & ebx == 0kein Bit in beiden gesetzt isttest eax, ebx + jnz 85 D8 75 01
if al & 0x80das oberste Bit von al gesetzt isttest al, 0x80 + jz A8 80 74 01
if byte [ebx] & 1das unterste Bit des Bytes in [ebx] gesetzt isttest byte [ebx], 1 + jz F6 03 01 74 01

Wie bei cmp braucht Speicher gegen eine Zahl die Größe (byte, word, dword). test kennt nicht „Register gegen Speicher“, sondern nur „Speicher gegen Register“; bei if eax & [ebx] vertauscht Asmonite die Operanden von selbst (für test macht das keinen Unterschied). else, endif, Verschachteln und die Namen einer func gehen wie bei jedem if.

Die Flags: if zero

Stehen die Flags schon da (nach dec, sub, add, and ...), braucht es keinen Vergleich: if nennt die Bedingung mit einem Wort und schreibt nur den Sprung um den Block. Die Wörter sind die der Sprungbefehle: zero (auch equal), carry, sign, overflow, parity, dazu above / below (ohne Vorzeichen) und less / greater (mit Vorzeichen). Davor darf not stehen, dahinter bei den letzten vier or equal.

Du schreibstDer Block läuft, wenn ...Erzeugt (mit dem Befehl davor)
dec ecx
if zero
das Ergebnis 0 istdec ecx + jnz 49 75 01
sub eax, 5
if not zero
das Ergebnis nicht 0 istsub eax, 5 + jz 83 E8 05 74 01
add eax, ecx
if carry
ein Übertrag entstanden istadd eax, ecx + jnc 03 C1 73 01
cmp eax, ebx
if below or equal
eax <= ebx, ohne Vorzeichencmp + ja 3B C3 77 01
cmp eax, ebx
if not less
eax >= ebx, mit Vorzeichencmp + jl 3B C3 7C 01

Welche Flags gelten, entscheidet der Befehl davor, nicht if: if zero nach einem mov prüft das Flag eines früheren Befehls. Ein Wort signed oder unsigned gibt es hier nicht, above / below und less / greater sagen es selbst. else, endif und das Verschachteln gehen wie bei jedem if.

Schleifen: do ... while

Der Block zwischen do und while läuft einmal und dann so lange, wie die Bedingung hinter while gilt. Die Bedingung hat dieselben Formen wie bei if (ein Vergleich, a & b oder ein Flag-Wort); der Sprung geht zurück zum do. Das Label dort macht der Assembler selbst (asmogen_do…).

Du schreibst
        do
          dec ecx
        while not zero
Gleichwertig von Hand, und die Bytes
again:
        dec ecx
        jnz again             ; the condition itself, back
49 75 FD
Mit Vergleich
        do
          inc eax
        while signed eax < 10
Von Hand, und die Bytes
again:
        inc eax
        cmp eax, 10
        jl again
40 83 F8 0A 7C FA
vom Assembler ergänzt (if, else, do, while) deine Zeilen, unverändert

Bei while ist der Sprung die Bedingung selbst (bei if ihr Gegenteil): while not zero ist jnz, while signed eax < 10 ist jl. Ordnungsvergleiche brauchen auch hier signed oder unsigned (oder ein .compare davor). Schleifen lassen sich ineinander und mit if verschachteln; sie müssen sauber ineinander liegen (ein endif in einem offenen do ist ein Fehler). Im Debugger ist die while-Zeile, also Vergleich und Sprung, ein Schritt.

Ein Beispiel mit printf liegt im ZIP: examples\func\func_loop.asm (die Summe von 1 bis 10).

Der Standard: .compare

Wer ein ganzes Stück mit Zahlen mit Vorzeichen schreibt, muss signed nicht jedes Mal tippen: .compare signed (oder .compare unsigned) gilt für alle if dahinter, bis zum nächsten .compare. .compare none nimmt es zurück. Ein Wort in der if-Zeile gilt immer.

Du schreibst
.compare signed
  if eax < ebx          ; signed (from .compare)
    nop
  endif
  if unsigned eax < ebx ; the word in the line wins
    nop
  endif
.compare none           ; back to: a word is required
Der Assembler erzeugt
3B C3 7D 01 90 3B C3 73 01 90

Ohne .compare und ohne Wort ist ein Ordnungsvergleich ein Fehler – es gibt keinen stillen Standard. Die Direktive steht in derselben blauen Schrift wie die Schlüsselwörter, damit sie im Quelltext auffällt.

else und verschachtelte Blöcke

else macht hinter dem ersten Block ein jmp über den zweiten; if darf in if stehen, jedes endif schließt das innerste offene.

else
  if eax != 0
    inc eax
  else
    dec eax
  endif
Von Hand, und die Bytes
  cmp eax, 0
  je else1              ; if not: to else
  inc eax
  jmp end1              ; then part done
else1:
  dec eax
end1:
83 F8 00 74 03 40 EB 01 48
verschachtelt
  if eax == 1
    if ebx == 2
      nop
    endif
  endif
Von Hand, und die Bytes
  cmp eax, 1
  jne end1
  cmp ebx, 2
  jne end2
  nop
end2:
end1:
83 F8 01 75 06 83 FB 02 75 01 90
vom Assembler ergänzt (if, else, do, while) deine Zeilen, unverändert

In einer func

Das Beispiel aus dem ZIP: das Größere von zwei Parametern. Die Namen a und b werden wie sonst in func zu [ebp+8] und [ebp+12].

Du schreibst
func Max(a, b)
begin
  mov eax, a
  if signed eax < b
    mov eax, b
  endif
end
Gleichwertig von Hand, und die Bytes
Max:
  push ebp
  mov ebp, esp
  mov eax, [ebp+8]
  cmp eax, dword [ebp+12]
  jge skip
  mov eax, [ebp+12]
skip:
  pop ebp
  ret 8
55 8B EC 8B 45 08 3B 45 0C 7D 03 8B
45 0C 5D C2 08 00
von func und if ergänzt deine Zeile, die Namen sind ersetzt

Das end der Funktion schreibt pop ebp und ret 8; offene if vor einem end sind ein Fehler.

Regeln und Fehler

Es ist ein Vergleich, kein Ausdruck: if eax+1 == ebx rechnet nicht, und && oder || gibt es nicht (ein einzelnes & ist der Bit-Test oben). Wer mehr braucht, schreibt zwei if oder die Sprünge selbst. Die Meldungen nennen die Zeile des Quelltexts.

Du schreibstMeldungFall
if eax < ebx
  nop
endif
if eax < ebx: say whether it is signed or unsigned (if signed eax < ebx / if unsigned ..., or .compare signed / .compare unsigned before)ein Ordnungsvergleich ohne Wort
if eax = ebx
  nop
endif
use == to compare: if eax == ebxein einzelnes =
if eax == ebx == ecx
  nop
endif
only one comparison behind if, no expressionszwei Vergleiche
if eax == ebx
  nop
endif is missing for the if in line 1endif fehlt
nop
endif
endif without ifendif ohne if
if eax == ebx
  nop
else
  nop
else
  nop
endif
a second else for the if in line 1ein zweites else
while eax != 1
while without dowhile ohne do
do
  nop
while is missing for the do in line 1while fehlt
do
  nop
endif
endif without if (the open block is the do in line 1, while is missing)ein endif im offenen do
if foo
  nop
endif
if needs a comparison between two operands: ==, !=, <, <=, > or >=, or a flag condition (zero, carry, ...)weder Vergleich noch Flag-Wort

Für .arch i386, .arch x64, .arch arm64 und .arch thumb. In x64 nimmt der Assembler für Sprünge vorwärts noch die lange Form (6 statt 2 Byte). In ARM 64 und Thumb schreibst du die Operanden in ARM-Schreibweise (if w0 == 5, if r1 != r2; eine Zahl bekommt ihr # selbst); der Sprung ist b.ne, b.lt (Thumb: bne, blt) und so weiter, bei Thumb wählt der Assembler die lange Form des Sprungs von selbst, wenn der Block zu groß ist, if a & b ist tst, Speicheroperanden gibt es nicht (erst laden), und parity gibt es nicht. carry ist das Flag C (nach einer Subtraktion ist C = 1, wenn nichts geborgt wurde). Eine Marke vor der Zeile (L: if …) geht.

Weiter

Funktionen

func schreibt den Stackframe, call Name(…) legt die Argumente ab.

Die func-Seite →

Die Syntax

Zahlen, Ausdrücke, Adressen, Sprünge und Daten mit den Bytes dazu.

Die Syntax-Seite →

Ausprobieren

Im ZIP liegen func_if.asm und func_loop.asm (Ordner examples\func); in 60 Sekunden vom Download zum ersten Haltepunkt.

Schnellstart →