x86-Architektur

Der Intel x86-Prozessor verwendet eine CISC-Architektur (Complex Instruction Set Computer), was bedeutet, dass es eine bescheidene Anzahl von Sonderregistern anstelle großer Mengen von Allzweckregistern 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-Befehlssatz 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-Flachmodus. Diese Dokumentation konzentriert sich nur auf den flachen Modus.

Registriert

Die x86-Architektur besteht aus den folgenden ganzzahligen Registern ohne Privilegierte.

Eax

Akku

Ebx

Basisregister

ecx

Zählerregister

Edx

Datenregister – kann für E/A-Portzugriff und arithmetische Funktionen verwendet werden

Esi

Quellindexregister

Edi

Zielindexregister

Ebp

Basiszeigerregister

Esp

Stack-Pointer

Alle ganzzahligen Register sind 32 Bit. Viele von ihnen verfügen jedoch über 16-Bit- oder 8-Bit-Unterregister.

ax

Eax mit niedrigen 16 Bit

Bx

Ebx mit niedrigen 16 Bits

Cx

Niedrige 16 Bit ecx

Dx

Niedrige 16 Bits von Edx

si

Niedrige 16 Bits von esi

Di

Niedrige 16 Bit edi

Bp

Niedrige 16 Bit ebp

Sp

Niedrige 16 Bits von esp

Al

Niedrige 8 Bit eax

Ah

Hohe 8 Bits der Axt

Bl

Niedrige 8 Bits von ebx

bh

Hohe 8 Bits von bx

Cl

Niedrige 8 Bits von ecx

ch

Hohe 8 Bits von cx

Dl

Niedrige 8 Bits von Edx

dh

Hohe 8 Bits von dx

Der Betrieb in einem Unterregister wirkt sich nur auf das Unterregister und keinen der Teile außerhalb des Unterregisters aus. Wenn Sie beispielsweise im Ax-Register speichern, bleiben die hohen 16 Bit des Eax-Registers unverändert.

Bei Verwendung von ? (Ausdruck auswerten) befehl, sollte registern ein "at"-Zeichen ( @ ) vorangestellt werden. Sie sollten z. B. ? @ax anstelle von ? ax verwenden. Dadurch wird sichergestellt, dass der Debugger ax als Register und nicht als Symbol erkennt.

Das (@) ist im Befehl r (Registers) jedoch nicht erforderlich. Bei instance wird r ax=5 immer richtig interpretiert.

Zwei weitere Register sind wichtig für den aktuellen Zustand des Prozessors.

Eip

Anweisungszeiger

flags

flags

Der Anweisungszeiger ist die Adresse der ausgeführten Anweisung.

Das Flags-Register ist eine Sammlung von Single-Bit-Flags. Viele Anweisungen ändern die Flags, um das Ergebnis der Anweisung zu beschreiben. Diese Flags können dann durch bedingte Sprunganweisungen getestet werden. Weitere Informationen finden Sie unter x86-Flags .

Aufrufen von Konventionen

Die x86-Architektur weist verschiedene Aufrufkonventionen auf. Glücklicherweise befolgen sie alle die gleichen Regeln für die Aufbewahrung von Registern und Funktionen:

  • Funktionen müssen alle Register beibehalten, mit Ausnahme von eax, ecx und edx, die über einen Funktionsaufruf geändert werden können, und esp, die gemäß der Aufrufkonvention aktualisiert werden müssen.

  • Das eax-Register empfängt Funktionsrückgabewerte, wenn das Ergebnis 32 Bit oder kleiner ist. Wenn das Ergebnis 64 Bits ist, wird das Ergebnis im edx:eax-Paar gespeichert.

Im Folgenden ist eine Liste der Aufrufkonventionen aufgeführt, die in der x86-Architektur verwendet werden:

  • Win32 (__stdcall)

    Funktionsparameter werden auf dem Stapel übergeben, von rechts nach links gepusht, und der Aufgerufene bereinigt den Stapel.

  • Aufruf der nativen C++-Methode (auch als thiscall bezeichnet)

    Funktionsparameter werden auf dem Stapel übergeben, von rechts nach links gepusht, der "this"-Zeiger wird im ecx-Register übergeben, und der Angerufene bereinigt den Stapel.

  • COM (__stdcall für C++-Methodenaufrufe)

    Funktionsparameter werden auf dem Stapel übergeben, von rechts nach links gepusht, dann wird der "this"-Zeiger auf den Stapel gepusht, und dann wird die Funktion aufgerufen. Der Aufgerufene entleert den Stapel.

  • __fastcall

    Die ersten beiden DWORD- oder kleineren Argumente werden in den Registern ecx und edx übergeben. Die restlichen Parameter werden auf dem Stapel übergeben und von rechts nach links gepusht. Der Aufgerufene entleert den Stapel.

  • __cdecl

    Funktionsparameter werden auf dem Stapel übergeben, von rechts nach links gepusht, und der Aufrufer bereinigt den Stapel. Die __cdecl Aufrufkonvention wird für alle Funktionen mit Parametern variabler Länge verwendet.

Debuggeranzeige von Registern und Flags

Hier sehen Sie eine Beispieldebuggerregisteranzeige:

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 die iopl und die gesamte letzte Zeile der Debuggeranzeige ignorieren.

x86-Flags

Im vorherigen Beispiel sind die Zweibuchstaben am Ende der zweiten Zeile Flags. Dabei handelt es sich um Single-Bit-Register mit einer Vielzahl von Verwendungsmöglichkeiten.

In der folgenden Tabelle sind die x86-Flags aufgeführt:

Flagcode Flagname Wert Flagstatus BESCHREIBUNG
of Überlaufflagge 0 1 nvov Kein Überlauf – Überlauf
df Richtungsflag 0 1 updn Richtung nach oben – Richtung nach unten
if Interruptflagge 0 1 diei Interrupts deaktiviert – Interrupts aktiviert
sf Sign Flag 0 1 plng Positiv (oder null) – Negativ
Zf Nullflagge 0 1 nzzr Nonzero – Null
af Zusatztrageflagge 0 1 naac Kein Hilfstrag – Hilfstrag
Pf Paritätsflag 0 1 Pepo Parität ungerade – Parität gerade
Cf Trageflagge 0 1 nccy Kein Tragen - Tragen
Tf Trap Flag Wenn tf gleich 1 ist, löst der Prozessor nach der Ausführung einer Anweisung eine STATUS_SINGLE_STEP Ausnahme aus. Dieses Flag wird von einem Debugger verwendet, um eine einstufige Ablaufverfolgung zu implementieren. Es sollte nicht von anderen Anwendungen verwendet werden.
iopl E/A-Berechtigungsebene E/A-Berechtigungsebene Hierbei handelt es sich um eine ganze Zwei-Bit-Zahl mit Werten zwischen 0 und 3. Es wird vom Betriebssystem verwendet, um den Zugriff auf Hardware zu steuern. Es sollte nicht von Anwendungen verwendet werden.

Wenn Register als Ergebnis eines Befehls im Debuggerbefehlsfenster angezeigt werden, wird das Flag status angezeigt. Wenn Sie jedoch ein Flag mithilfe des Befehls r (Registers) ändern möchten, sollten Sie mit dem Flagcode darauf verweisen.

Im Fenster Registers von WinDbg wird der Flagcode verwendet, um Flags anzuzeigen oder zu ändern. Das Flag status wird nicht unterstützt.

Es folgt ein Beispiel. In der vorherigen Registeranzeige wird das Flag status ng angezeigt. Dies bedeutet, dass das Zeichenflag derzeit auf 1 festgelegt ist. Verwenden Sie den folgenden Befehl, um dies zu ändern:

r sf=0

Dadurch wird das Zeichenflag auf 0 festgelegt. Wenn Sie eine andere Registrierungsanzeige durchführen, wird der Code ng status nicht angezeigt. Stattdessen wird der pl-status-Code angezeigt.

Sign Flag, Zero Flag und Carry Flag sind die am häufigsten verwendeten Flags.

Bedingungen

Eine Bedingung beschreibt den Zustand eines oder mehrerer Flags. Alle bedingten Vorgänge auf dem x86 werden in Bedingungen ausgedrückt.

Der Assembler verwendet ein oder zwei Buchstaben, um eine Bedingung darzustellen. Eine Bedingung kann durch mehrere Abkürzungen dargestellt werden. Beispielsweise ist AE ("oben oder gleich") die gleiche Bedingung wie NB ("nicht unten"). In der folgenden Tabelle sind einige allgemeine Bedingungen und ihre Bedeutung aufgeführt.

Bedingungsname Flags Bedeutung

Z

ZF=1

Das Ergebnis des letzten Vorgangs war 0.

NZ

ZF=0

Das Ergebnis des letzten Vorgangs war nicht 0.

C

CF=1

Der letzte Vorgang erforderte ein Tragen oder Ausleihen. (Bei ganzzahligen Zahlen ohne Vorzeichen bedeutet dies einen Überlauf.)

NC

CF=0

Der letzte Vorgang erforderte kein Tragen oder Ausleihen. (Bei ganzzahligen Zahlen ohne Vorzeichen bedeutet dies einen Überlauf.)

E

SF=1

Das Ergebnis des letzten Vorgangs hat seinen hohen Bitsatz.

NS

SF=0

Das Ergebnis des letzten Vorgangs hat sein hohes Bit eindeutig.

O

OF=1

Wenn er als ganzzahliger Vorzeichenvorgang behandelt wird, verursachte der letzte Vorgang einen Überlauf oder Unterlauf.

Nein

OF=0

Bei Behandlung als ganzzahliger Vorzeichenvorgang verursachte der letzte Vorgang keinen Überlauf oder Unterlauf.

Bedingungen können auch verwendet werden, um zwei Werte zu vergleichen. Die cmp-Anweisung vergleicht die beiden Operanden und legt dann Flags fest, als ob ein Operand vom anderen subtrahiert würde. Die folgenden Bedingungen können verwendet werden, um das Ergebnis von cmpvalue1, value2 zu überprüfen.

Bedingungsname Flags Dies bedeutet nach einem CMP-Vorgang.

E

ZF=1

value1 == value2.

NE

ZF=0

value1 != value2.

GE NL

SF=OF

value1>= value2. Werte werden als ganzzahlige Vorzeichen behandelt.

LE NG

ZF=1 oder SF!=OF

value1<= value2. Werte werden als ganzzahlige Vorzeichen behandelt.

G NLE

ZF=0 und SF=OF

value1>value2. Werte werden als ganzzahlige Vorzeichen behandelt.

L NGE

SF!=OF

value1<value2. Werte werden als ganzzahlige Vorzeichen behandelt.

AE NB

CF=0

value1>= value2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt.

BE NA

CF=1 oder ZF=1

value1<= value2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt.

Ein NBE

CF=0 und ZF=0

value1>value2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt.

B NAE

CF=1

value1<value2. Werte werden als ganze Zahlen ohne Vorzeichen behandelt.

Bedingungen werden in der Regel verwendet, um auf das Ergebnis einer cmp- oder Testanweisung zu reagieren. Beispiel:

cmp eax, 5
jz equal

Vergleicht das Eax-Register mit der Zahl 5, indem der Ausdruck (eax - 5) und die Flags entsprechend dem Ergebnis festgelegt werden. Wenn das Ergebnis der Subtraktion null ist, wird das zr-Flag festgelegt, und die jz-Bedingung ist true, sodass der Sprung ausgeführt wird.

Datentypen

  • Byte: 8 Bits

  • word: 16 Bits

  • dword: 32 Bits

  • qword: 64 Bits (enthält Gleitkomma-Doubles)

  • zweird: 80 Bits (einschließlich erweiterter Gleitkomma-Doubles)

  • oword: 128 Bits

Notation

Die folgende Tabelle gibt die Notation an, die verwendet wird, um Anweisungen in der Assemblysprache zu beschreiben.

Notation Bedeutung

r, r1, r2...

Register

m

Speicheradresse (weitere Informationen finden Sie im Abschnitt "Adressierungsmodi".)

#n

Sofortkonstante

r/m

Registrieren oder Arbeitsspeicher

r/#n

Registrieren oder Sofortkonstante

r/m/#n

Registrieren, Arbeitsspeicher oder Sofortkonstante

Cc

Ein Bedingungscode, der im vorherigen Abschnitt Bedingungen aufgeführt ist.

T

"B", "W" oder "D" (Byte, Wort oder dword)

accT

Größe T Akkumulator: al if T = "B", ax if T = "W", oder eax if T = "D"

Adressierungsmodi

Es gibt mehrere verschiedene Adressierungsmodi, aber sie haben alle die Form T ptr [expr], wobei T ein Datentyp ist (siehe vorherigen Abschnitt Datentypen), und expr ist ein Ausdruck mit Konstanten und Registern.

Die Notation für die meisten Modi kann ohne große Schwierigkeiten abgeleitet werden. BYTE PTR [esi+edx*8+3] bedeutet beispielsweise "den Wert des esi-Registers übernehmen, dem Edx-Register das Achtfache des Werts des Edx-Registers hinzufügen, drei hinzufügen und dann auf das Byte an der resultierenden Adresse zugreifen."

Pipelining

Der Pentium ist dual, was bedeutet, dass er bis zu zwei Aktionen in einem Takt ausführen kann. Die Regeln für den Zeitpunkt, in dem zwei Aktionen gleichzeitig ausgeführt werden können (auch als Kopplung bezeichnet), sind jedoch sehr kompliziert.

Da x86 ein CISC-Prozessor ist, müssen Sie sich keine Gedanken über Sprungverzögerungsslots machen.

Synchronisierter Speicherzugriff

Anweisungen zum Laden, Ändern und Speichern können ein Sperrpräfix erhalten, das die Anweisung wie folgt ändert:

  1. Bevor die Anweisung ausgegeben wird, leert die CPU alle ausstehenden Arbeitsspeichervorgänge, um die Kohärenz sicherzustellen. Alle Datenvorschubvorgänge werden abgebrochen.

  2. Während der Ausgabe der Anweisung hat die CPU exklusiven Zugriff auf den Bus. Dadurch wird die Atomarität des Lade-/Änderungs-/Speichervorgangs sichergestellt.

Die xchg-Anweisung folgt automatisch den vorherigen Regeln, wenn sie einen Wert mit dem Arbeitsspeicher austauscht.

Alle anderen Anweisungen sind standardmäßig nicht sperrend.

Sprungvorhersage

Bedingungslose Sprünge werden vorhergesagt.

Bedingte Sprünge werden vorhergesagt oder nicht, je nachdem, ob sie bei der letzten Ausführung genommen wurden. Der Cache für die Aufzeichnung des Sprungverlaufs ist begrenzt.

Wenn die CPU nicht über einen Datensatz verfügt, ob der bedingte Sprung bei der letzten Ausführung durchgeführt wurde oder nicht, sagt sie bedingte Rückwärtssprünge als angenommene bedingte Sprünge voraus, und vorwärts bedingte Sprünge werden als nicht ausgeführt.

Ausrichtung

Der x86-Prozessor korrigiert den nicht ausgerichteten Speicherzugriff automatisch mit einer Leistungseinbuße. 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 (alles ist ein ganzzahliges Vielfaches von 1), WORD-Zugriffe auf gerade Adressen werden ausgerichtet, und DWORD-Adressen müssen ein Vielfaches von 4 sein, um ausgerichtet zu werden.

Das Sperrpräfix sollte nicht für nicht ausgerichtete Speicherzugriffe verwendet werden.