Tracks
/
x86-64 Assembly
x86-64 Assembly
/
Lehrplan
/
Bedingte Anweisungen
Be

Bedingte Anweisungen in x86-64 Assembly

4 Übungen

Über Bedingte Anweisungen

RFLAGs

Es gibt ein spezielles Register namens rflags. Seine Bits wirken wie Flags für verschiedene Bedingungen.

Einige davon sind unten aufgeführt:

Name Symbol Bit
Übertrag CF 0
Null ZF 6
Vorzeichen SF 7
Überlauf OF 11

Eine vollständige Liste findest du in Intels Handbuch.

Vergleichsinstruktionen

Die Flags in rflags werden nicht direkt geändert. Stattdessen werden sie durch viele verschiedene Instruktionen gesetzt.

Zum Beispiel wird ZF durch viele arithmetische oder bitweise Operationen gesetzt, wenn das Ergebnis null ist.

Eine der gebräuchlichsten Instruktionen zum Testen von Bedingungen ist cmp. Sie nimmt zwei Operanden und aktualisiert die Flags, verändert aber nicht ihre Operanden.

CMP-Instruktion

Die cmp-Instruktion subtrahiert den zweiten Operanden vom ersten und setzt die Flags entsprechend dem Ergebnis.

Wenn A der erste Operand ist und B der zweite:

Flag Gesetzt, wenn
CF A < B (ohne Vorzeichen)
ZF A == B
SF A < B (mit Vorzeichen, kein Überlauf)
OF Überlauf bei vorzeichenbehafteter Subtraktion

Verzweigung

Standardmäßig wird Code in x86-64 sequenziell von oben nach unten ausgeführt.

Aber es gibt viele Situationen, in denen dieses Verhalten geändert werden muss. Zum Beispiel, um als Reaktion auf eine Bedingung eine andere Gruppe von Instruktionen auszuführen.

In höheren Programmiersprachen geschieht dies normalerweise mit Abstraktionen wie if...else-Bedingungen. In x86-64 gibt es diese jedoch nicht.

Stattdessen bietet x86-64 Instruktionen, die die Ausführung effektiv an eine andere Stelle des Codes übertragen. Dies wird als branching bezeichnet.

Note

Wir haben bereits zwei solche Instruktionen gesehen: call und ret.

Wenn eine Funktion aufgerufen wird, wird die Ausführung vom Aufrufer auf die aufgerufene Funktion übertragen. Und bei der Rückkehr wird die Ausführung zurück an den Aufrufer übertragen.

Wenn kein ret gefunden wird, geht die Ausführung direkt von einer Funktion zur nächsten über. Dies kann manchmal verwendet werden, um den Codefluss zu optimieren.

Unbedingter Sprung

Die Instruktion jmp überträgt die Ausführung des Programms bedingungslos an eine andere Stelle des Codes. Ihr einzelner Operand ist ein Label, das die Adresse der Stelle enthält, an der die Ausführung fortgesetzt wird.

Betrachten wir zum Beispiel die folgende Funktion:

fn:
    mov rax, 5
    jmp end

    add rax, 10
end:
    ret

Wenn fn aufgerufen wird, beginnt die Ausführung bei mov rax, 5. Dadurch wird der Wert in rax an dieser Stelle auf 5 gesetzt.

Die nächste Instruktion ist jmp end, die die Ausführung an das Label end übergibt.

Nach end ist die nächste Instruktion ret, die die Ausführung zurück an die aufrufende Funktion übergibt.

Beachte, dass add rax, 10, da es nach jmp end und vor end steht, nie ausgeführt wird. Der Wert von rax ist 5, wenn fn zurückkehrt.

Bedingter Sprung

Die Familie der Instruktionen jcc überträgt die Ausführung des Programms nur dann an eine andere Stelle, wenn eine bestimmte Bedingung erfüllt ist. Andernfalls wird die Ausführung sequenziell fortgesetzt.

Jede Bedingung entspricht einem oder mehreren Flags in rflags. Einige jcc-Varianten testen, ob ein Flag gesetzt ist, andere testen, ob es gelöscht ist.

Das cc in jcc ist nicht wörtlich zu nehmen, sondern bezieht sich auf das spezifische Suffix, das mit dem getesteten Flag verbunden ist.

Es gibt viele Suffixe, und viele von ihnen testen die gleiche Bedingung wie ein anderes. Einige davon beziehen sich direkt auf ein Flag, sodass die Instruktion zu einem Label springt, wenn das spezifische Flag gesetzt ist:

Suffix Springt, wenn
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

Viele andere werden gewählt, um sich auf ihre Bedeutung in einer cmp-Instruktion zu beziehen. Zum Beispiel:

Instruktion Suffix Springt, wenn
cmp A, B e A == B
cmp A, B l A < B (mit Vorzeichen)
cmp A, B b A < B (ohne Vorzeichen)
cmp A, B g A > B (mit Vorzeichen)
cmp A, B a A > B (ohne Vorzeichen)

Es ist möglich, e nach l, b, g oder a hinzuzufügen, um die Gleichheit in die Bedingung einzubeziehen:

cmp rcx, r8
jge two      ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two      ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison

Für alle Suffixe gibt es Varianten, die das entgegengesetzte Verhalten prüfen. Sie haben die gleiche Syntax, aber mit einem n vor dem Suffix.

Zum Beispiel springt jnz, wenn ZF nicht gesetzt ist. Ähnlich springt jnae, wenn A nicht >= B ist (A und B als vorzeichenlose Ganzzahlen interpretiert).

Note

Einige Suffixe sind Aliasse für die gleichen Bedingungen. Zum Beispiel springen jz (Suffix z, für ZF) und je (Suffix e, für gleich) beide, wenn ZF gesetzt ist. Das liegt daran, dass bei cmp ZF gesetzt wird, wenn die Subtraktion null ergibt, was bedeutet, dass die beiden Operanden gleich sind.

Andere Suffixe hingegen testen eine Kombination von Flags und können nicht direkt durch ein einzelnes Flag-Suffix ersetzt werden.

Bevorzuge das Suffix, das die Semantik deines Vergleichs besser beschreibt.

Lokale Labels

Labels sind in der gesamten Quelldatei sichtbar, sie sind nicht lokal auf eine Funktion beschränkt. Daher ist es unmöglich, einen Labelnamen wiederzuverwenden.

Um das Verhalten eines lokalen Labels nachzuahmen, hat NASM eine spezielle Notation für ein Label, das mit einem Punkt (.) davor deklariert wird. Diese Notation definiert ein Label, das implizit den Namen des vorherigen nicht gepunkteten Labels enthält:

section .text
fn1:
    ...
.example: ; this is fn1.example
    ...
    ret

fn2:
    ...
.example: ; this is fn2.example
    ...
    ret

Es ist weiterhin möglich, von überall im Code zu diesem Label zu springen, indem man den vollständigen Labelnamen verwendet, zum Beispiel jmp fn1.example.

Ein Sprung, der den Teil des Labels ab dem Punkt verwendet, führt jedoch zu dem Label innerhalb der übergeordneten Funktion. Zum Beispiel verhält sich .example, als wäre es lokal auf die Funktion beschränkt:

section .text
fn1:
    ...
.example:
    ...
    jmp .example ; this jumps to fn1.example

fn2:
    ...
.example:
    ...
    jmp .example ; this jumps to fn2.example

Beachte, dass ein nicht gepunktetes Label innerhalb einer Funktion in der Praxis eine weitere Funktion definiert:

section .text
fn1:
    ...
.example: ; this is 'fn1.example'
    ...
non_dotted:
    ...
.example: ; this is 'non_dotted.example'
    ...
    ret
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Bedingte Anweisungen