x86-Architektur
Der Intel x86-Prozessor verwendet komplexe Anweisungssatz-Computerarchitektur (CISC), was bedeutet, dass es eine geringe Anzahl von speziellen Registern anstelle großer Mengen von allgemeinen Registern gibt. Es bedeutet auch, dass komplexe Sonderanweisungen vorherrschen.
Der x86-Prozessor verfolgt sein Erbe mindestens so weit zurück wie der 8-Bit Intel 8080-Prozessor. Viele Besonderheiten im x86-Anweisungssatz sind auf die Abwärtskompatibilität mit diesem Prozessor (und mit seiner Zilog Z-80 Variante) zurückzuführen.
Microsoft Win32 verwendet den x86-Prozessor im 32-Bit-Flatmodus. Diese Dokumentation konzentriert sich nur auf den flachen Modus.
Register
Die x86-Architektur besteht aus den folgenden nicht privilegierten ganzzahligen Registern.
eax |
Akkumulator |
ebx |
Basisregister |
ecx |
Zählerregister |
edx |
Datenregister – kann für den E/A-Portzugriff und arithmetische Funktionen verwendet werden |
esi |
Quellindexregister |
edi |
Zielindexregister |
ebp |
Basiszeigerregister |
esp |
Stack-Pointer |
Alle ganzzahligen Register sind 32 Bit. Viele davon verfügen jedoch über 16-Bit- oder 8-Bit-Unterregister.
ax |
Untere 16 Bits von eax |
bx |
Untere 16 Bits von ebx |
cx |
Untere 16 Bits von ecx |
dx |
Untere 16 Bits von edx |
si |
Untere 16 Bits von esi |
di |
Untere 16 Bits von edi |
bp |
Untere 16 Bits von ebp |
sp |
Untere 16 Bits von esp |
al |
Untere 8 Bits von eax |
ah |
Obere 8 Bits von ax |
bl |
Untere 8 Bits von ebx |
bh |
Obere 8 Bits von bx |
cl |
Untere 8 Bits von ecx |
ch |
Obere 8 Bits von cx |
dl |
Untere 8 Bits von edx |
dh |
Obere 8 Bits von dx |
Das Arbeiten mit einem Unterregister betrifft nur das Unterregister und keine Teile außerhalb des Unterregisters. Beispiel: Beim Speichern im ax-Register bleiben die oberen 16 Bits des eax-Registers unverändert.
Bei Verwendung des Befehls ? (Ausdruck auswerten) sollte Registern ein At-Zeichen (@) vorangestellt werden. Beispielsweise sollten Sie ? @ax anstelle von ? ax verwenden. Dadurch wird sichergestellt, dass der Debugger ax als Register und nicht als Symbol erkennt.
Die (@) ist jedoch im r (Registers) Befehl nicht erforderlich. r ax=5 wird beispielsweise immer korrekt interpretiert.
Zwei weitere Register sind für den aktuellen Zustand des Prozessors wichtig.
eip |
Anweisungszeiger |
Flags |
Flaggen |
Der Anweisungszeiger ist die Adresse der ausgeführten Anweisung.
Das Kennzeichenregister ist eine Sammlung von Single-Bit-Flags. Bei vielen Anweisungen werden die Flags geändert, um das Ergebnis der Anweisung zu beschreiben. Diese Flags könnten dann durch bedingte Sprunganweisungen getestet werden. Weitere Informationen finden Sie unter x86-Flags.
Aufrufkonventionen
Die x86-Architektur verfügt über verschiedene Aufrufkonventionen. Glücklicherweise befolgen sie alle die gleichen Regeln für die Registerarchivierung und Funktionsrückgabe:
Funktionen müssen alle Register mit Ausnahme von eax, ecx und edx beibehalten, die mithilfe eines Funktionsaufrufs geändert werden können, sowie esp, das gemäß der Aufrufkonvention aktualisiert werden muss.
Das eax-Register empfängt Funktionsrückgabewerte, wenn das Ergebnis 32 Bits oder weniger umfasst. Wenn das Ergebnis 64 Bit ist, wird das Ergebnis im edx:eax Paar gespeichert.
Es folgt eine Liste der Aufrufkonventionen, die für die x86-Architektur verwendet werden:
Win32 (__stdcall)
Funktionsparameter werden an den Stapel übergeben und von rechts nach links verschoben. Die aufgerufene Funktion bereinigt den Stapel.
Nativer C++-Methodenaufruf (auch als „thiscall“ bezeichnet)
Funktionsparameter werden an den Stapel übergeben und von rechts nach links verschoben. Der „this“-Zeiger wird im ecx-Register übergeben, und der Stapel wird der aufgerufenen Funktion bereinigt.
COM (__stdcall für C++-Methodenaufrufe)
Funktionsparameter werden an den Stapel übergeben und von rechts nach links verschoben. Anschließend werden der „this“-Zeiger in den Stapel verschoben und die Funktion aufgerufen. Die aufgerufene Funktion bereinigt den Stapel.
__fastcall
Die ersten beiden DWORD- oder kleineren Argumente werden in den ecx- und edx-Registern übergeben. Die verbleibenden Parameter werden an den Stapel übergeben und von rechts nach links verschoben. Die aufgerufene Funktion bereinigt den Stapel.
__cdecl
Funktionsparameter werden an den Stapel übergeben und von rechts nach links verschoben. Die aufrufende Funktion bereinigt den Stapel. Die __cdecl Aufrufkonvention wird für alle Funktionen mit Parametern mit variabler Länge verwendet.
Debuggeranzeige von Registern und Flags
Hier ein Beispiel für die Debuggeranzeige von Registern:
eax=00000000 ebx=008b6f00 ecx=01010101 edx=ffffffff esi=00000000 edi=00465000
eip=77f9d022 esp=05cffc48 ebp=05cffc54 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000286
Beim Debuggen im Benutzermodus können Sie iopl und die gesamte letzte Zeile der Debuggeranzeige ignorieren.
x86-Flags
Im vorherigen Beispiel sind die aus zwei Buchstaben bestehenden Codes am Ende der zweiten Zeile Flags. Dies sind Single-Bit-Register und haben eine Vielzahl von Verwendungsmöglichkeiten.
In der folgenden Tabelle sind die x86-Flags aufgeführt:
Flaggenkodex | Flagname | Wert | Flagstatus | Beschreibung |
---|---|---|---|---|
von | Überlaufflag | 0 1 | nvov | Kein Überlauf – Überlauf |
df | Richtungsflag | 0 1 | updn | Nach oben – Nach unten |
if | Interruptflag | 0 1 | diei | Interrupts deaktiviert – Interrupts aktiviert |
sf | Vorzeichenflag | 0 1 | plng | Positiv (oder Null) - Negativ |
zf | Null-Flag | 0 1 | nzzr | Ungleich null – null |
af | Hilfsübertragsflag | 0 1 | naac | Kein Hilfsübertrag – Hilfsübertrag |
pf | Paritätsflag | 0 1 | pepo | Ungerade Parität – gerade Parität |
cf | Übertragsflag | 0 1 | nccy | Kein Übertrag – Übertrag |
tf | Einzelschrittflag | Wenn tf gleich 1 ist, löst der Prozessor nach der Ausführung eines Befehls eine STATUS_SINGLE_STEP-Ausnahme aus. Dieses Flag wird von einem Debugger verwendet, um eine Einzelschritt-Ablaufverfolgung zu implementieren. Sie sollte nicht von anderen Anwendungen verwendet werden. | ||
iopl | E/A-Berechtigungsstufe | Bei der E/A-Berechtigungsstufe handelt es sich um eine 2-Bit-Ganzzahl mit Werten zwischen 0 und 3. Es wird vom Betriebssystem verwendet, um den Zugriff auf Hardware zu steuern. Sie sollte nicht von Anwendungen verwendet werden. |
Wenn Register als Ergebnis eines Befehls im Debugger-Befehlsfenster angezeigt werden, handelt es sich um den Flagstatus. Wenn Sie jedoch ein Flag mithilfe des Befehls r (Register) ändern möchten, sollten Sie unter Verwendung des Flagcodes darauf verweisen.
Im Registerfenster von WinDbg wird der Flagcode verwendet, um Flags anzuzeigen oder zu ändern. Der Flagstatus wird nicht unterstützt.
Hier ist ein Beispiel. In der vorherigen Registeranzeige wird der Flagstatus ng angezeigt. Dies bedeutet, dass das Kennzeichen derzeit auf 1 festgelegt ist. Um dies zu ändern, verwenden Sie den folgenden Befehl:
r sf=0
Dadurch wird das Vorzeichenflag auf null gesetzt. Bei einer erneuten Registeranzeige wird der Statuscode ng nicht angezeigt. Stattdessen wird der Statuscode pl angezeigt.
Die Sign Flag, Zero Flag und Carry Flag sind die am häufigsten verwendeten Flags.
Bedingungen
Eine Bedingung beschreibt den Status eines oder mehrerer Flags. Alle bedingten Vorgänge auf dem x86 werden in den Bedingungen ausgedrückt.
Der Assembler verwendet eine ein- oder zweibuchstabige Abkürzung, um eine Bedingung darzustellen. Eine Bedingung kann durch mehrere Abkürzungen dargestellt werden. Beispielsweise ist AE ("über oder gleich") die gleiche Bedingung wie NB ("nicht darunter"). In der folgenden Tabelle sind einige allgemeine Bedingungen und deren Bedeutung aufgeführt.
Bedingungsname | Flaggen | Bedeutung |
---|---|---|
Z |
ZF=1 |
Das Ergebnis des letzten Vorgangs war Null. |
NZ |
ZF=0 |
Das Ergebnis des letzten Vorgangs war ungleich null. |
C |
CF=1 |
Der letzte Vorgang erforderte einen Übertrag oder ein Entleihen. (Bei nicht signierten ganzzahligen Zahlen gibt dies einen Überlauf an.) |
NC |
CF=0 |
Der letzte Vorgang erforderte keinen Übertrag oder kein Entleihen. (Bei nicht signierten ganzzahligen Zahlen gibt dies einen Überlauf an.) |
S |
SF=1 |
Beim Ergebnis des letzten Vorgangs wurde das höchstwertige Bit gesetzt. |
NS |
SF=0 |
Beim Ergebnis des letzten Vorgangs wurde das höchstwertige Bit gelöscht. |
O |
OF=1 |
Wenn es sich um einen Vorgang mit einer signierten Ganzzahl handelt, hat der letzte Vorgang zu einem Überlauf oder Unterlauf geführt. |
NO |
OF=0 |
Wenn es sich um einen Vorgang mit einer signierten Ganzzahl handelt, hat der letzte Vorgang zu keinem Überlauf oder Unterlauf geführt. |
Bedingungen können auch verwendet werden, um zwei Werte zu vergleichen. Die cmp-Anweisung vergleicht die beiden Operanden und setzt dann Flags, als ob ein Operand vom anderen subtrahiert würde. Mithilfe der folgenden Bedingungen kann das Ergebnis von cmpWert1, Wert2 überprüft werden.
Bedingungsname | Flaggen | Bedeutung nach einem CMP-Vorgang. |
---|---|---|
E |
ZF=1 |
Wert1 == Wert2. |
NE |
ZF=0 |
Wert1 != Wert2. |
GE NL | SF=OF |
Wert1>= Wert2. Werte werden als signierte ganze Zahlen behandelt. |
LE NG | ZF=1 oder SF!=OF |
Wert1<= Wert2. Werte werden als signierte ganze Zahlen behandelt. |
G NLE | ZF=0 und SF=OF |
Wert1>Wert2. Werte werden als signierte ganze Zahlen behandelt. |
L-NGE | SF!=OF |
Wert1<Wert2. Werte werden als signierte ganze Zahlen behandelt. |
AE NB | CF=0 |
Wert1>= Wert2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt. |
BE NA | CF=1 oder ZF=1 |
Wert1<= Wert2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt. |
Eine NBE | CF=0 und ZF=0 |
Wert1>Wert2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt. |
B NAE | CF=1 |
Wert1<Wert2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt. |
Bedingungen werden in der Regel verwendet, um auf das Ergebnis einer cmp- oder test-Anweisung zu reagieren. Beispiel:
cmp eax, 5
jz equal
vergleicht das eax-Register mit der Zahl 5, indem der Ausdruck (eax - 5) berechnet wird und Flags entsprechend dem Ergebnis gesetzt werden. Wenn das Ergebnis der Subtraktion null ist, wird das zr-Flag gesetzt. Außerdem ist die jz-Bedingung „true“, sodass der Sprung ausgeführt wird.
Datentypen
byte: 8 Bits
word: 16 Bits
dword: 32 Bits
qword: 64 Bits (schließt Gleitkomma-double-Werte ein)
tword: 80 Bits (schließt erweiterte Gleitkomma-double-Werte ein)
oword: 128 Bits
Notation
In der folgenden Tabelle ist die Schreibweise angegeben, die zum Beschreiben von Anweisungen zur Assemblysprache verwendet wird.
Notation | Bedeutung |
---|---|
r, r1, r2... |
Register |
m |
Speicheradresse (weitere Informationen finden Sie im folgenden Abschnitt zu Adressierungsmodi). |
#n |
Unmittelbare Konstante |
r/m |
Register oder Speicher |
r/#n |
Register oder direkte Konstante |
r/m/#n |
Register, Speicher oder direkte Konstante |
cc |
Ein im vorherigen Abschnitt "Bedingungen" aufgeführter Bedingungscode. |
T |
„B“, „W“ oder „D“ („byte“, „word“ oder „dword“) |
accT |
Größe T Akkumulator: al, wenn T = „B“, ax, wenn T = „W“ oder eax, wenn T = „D“ |
Adressierungsmodi
Es gibt mehrere verschiedene Adressierungsmodi, aber sie alle nehmen die Form T ptr [expr]an, wobei T ein Datentyp ist (siehe vorherigen Abschnitt Datentypen) und expr ein Ausdruck aus Konstanten und Registern ist.
Die Notation für die meisten Modi kann ohne große Schwierigkeiten abgeleitet werden. Zum Beispiel bedeutet BYTE PTR [esi+edx*8+3] „nimm den Wert des esi-Registers, addiere dazu 8 Mal den Wert des edx-Registers, addiere 3, und greife dann auf das Byte an der resultierenden Adresse zu“.
Pipelining
Der Pentium ist ein Dual-Issue-Prozessor, was bedeutet, dass er bis zu zwei Aktionen in einem Taktzyklus ausführen kann. Die Regeln darüber, wann es in der Lage ist, zwei Aktionen gleichzeitig auszuführen (auch als Pairingbezeichnet), sind jedoch sehr kompliziert.
Da x86 ein CISC-Prozessor ist, müssen Sie sich keine Gedanken über Sprungverzögerungsmodule machen.
Synchronisierter Speicherzugriff
Lade-, Änderungs- und Speicheranweisungen können ein lock-Präfix erhalten, das die Anweisung wie folgt ändert:
Vor dem Ausgeben der Anweisung löscht die CPU alle ausstehenden Speichervorgänge, um die Kohärenz sicherzustellen. Alle vorab abgerufenen Daten werden verworfen.
Beim Ausstellen der Anweisung hat die CPU exklusiven Zugriff auf den Bus. Dadurch wird die Atomarität des Lade-/Änderungs-/Speichervorgangs sichergestellt.
Die xchg Anweisung befolgt automatisch die vorherigen Regeln, wenn ein Wert mit dem Speicher ausgetauscht wird.
Alle anderen Anweisungen sind standardmäßig nicht sperrend.
Sprungvorhersage
Die Ausführung nicht bedingter Sprünge wird vorhergesagt.
Bei bedingten Sprüngen wird vorhergesagt, ob sie ausgeführt werden oder nicht, je nachdem, ob sie bei der letzten Ausführung ausgeführt wurden oder nicht. Der Cache für die Aufzeichnung des Sprungverlaufs verfügt über eine Größenbegrenzung.
Wenn die CPU keine Aufzeichnung darüber hat, ob der bedingte Sprung beim letzten Mal ausgeführt wurde oder nicht, werden bedingte Rückwärtssprünge als ausgeführt und bedingte Vorwärtssprünge als nicht ausgeführt vorhergesagt.
Ausrichtung
Der x86-Prozessor korrigiert automatisch nicht ausgerichtete Speicherzugriffe, was jedoch zu einer Leistungseinbuße führen kann. Es wird keine Ausnahme ausgelöst.
Ein Speicherzugriff gilt als ausgerichtet, wenn die Adresse ein ganzzahliges Vielfaches der Objektgröße ist. Beispielsweise sind alle BYTE-Zugriffe ausgerichtet (alle Adressen entsprechen einem ganzzahligen Vielfachen von 1), während WORD-Zugriffe auf gerade Adressen ausgerichtet sind und DWORD-Adressen einem Vielfachen von 4 entsprechen müssen, damit sie ausgerichtet sind.
Das lock-Präfix sollte nicht für nicht ausgerichtete Speicherzugriffe verwendet werden.