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:
Bevor die Anweisung ausgegeben wird, leert die CPU alle ausstehenden Arbeitsspeichervorgänge, um die Kohärenz sicherzustellen. Alle Datenvorschubvorgänge werden abgebrochen.
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.
Feedback
https://aka.ms/ContentUserFeedback.
Bald verfügbar: Im Laufe des Jahres 2024 werden wir GitHub-Issues stufenweise als Feedbackmechanismus für Inhalte abbauen und durch ein neues Feedbacksystem ersetzen. Weitere Informationen finden Sie unterFeedback senden und anzeigen für