Freigeben über


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:

  1. Vor dem Ausgeben der Anweisung löscht die CPU alle ausstehenden Speichervorgänge, um die Kohärenz sicherzustellen. Alle vorab abgerufenen Daten werden verworfen.

  2. 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.

Weitere Informationen

x64-Architektur

X86-64 Wikipedia