Assemblerbefehle
Datentransportbefehle
Cast-Operationen und Konvertierungsbefehle
Arithmetische Befehle
Sprungbefehle
Stringbefehle
Bitmanipulationsbefehle / Bitschiebebefehle
Stackbefehle
Sonstige
CPUID

Assemblerbefehle

In diesem Kapitel werden die wichtigsten Befehle erläutert, die es Ihnen ermöglichen, erste kleine Programme zu schreiben. Dieses Kapitel kann allerdings keine genaue Befehlsreferenz ersetzen, die an vielen Stellen wesentlich detailliertere Auskünfte geben kann.

Ein Prozessorbefehl hat eine Länge von mindestens einem bis zu 15 Bytes. Die Befehle (der Maschinencode, den die Mnemonics repräsentieren) sind so codiert, daß der Prozessor die Befehlslänge aus den ersten Bits auslesen kann. Ein übersetzter Befehl wird auch als OpCode (Operation Code) bezeichnet.

Da die meisten Befehle mindestens einen Parameter erwarten, dieser aber unterschiedlichen Typs sein kann, verwende ich folgende Kennzeichnung:
memxx für Variablen und Speicherstellen
regxx für Register
conxx für Konstanten

das 'xx' steht in jedem Fall für die Datengröße des Parameters in Bits. Diese wird in aller Regel entweder 8, 16 oder 32 sein.

Wenn ein Assemblerbefehl Parameter erwartet, dann zumeist nach folgendem Muster:

Befehl Ziel [,Quelle]

Erwartet der Befehl also einen Parameter, dann wird das Ergebnis der Operation meist auch in diesen Parameter geschrieben. Wird noch ein zweiter Operand erwartet, dann ist das zumeist eine Quellangabe, die abhängig vom Befehl nur in den ersten Parameter geschrieben wird oder erst über eine Berechnung mit dem ersten Parameter in diesen geschrieben wird.

Viele Befehle verändern bei ihrer Ausführung die Bits im Flag-Register und zeigen so an, wie das Ergebnis der Ausführung aussieht.
In den entsprechenden Fällen werden Angaben gemacht, in welcher Weise die Flags verändert wurden.

Datentransportbefehle

MOV

Der mit Abstand wichtigste Befehl hat folgende symbolische Syntax:

mov Ziel, Quelle

Der Befehl deutet bereits recht deutlich das englische Wort move (bewegen) an, was in diesem Fall aber nicht ganz den Tatsachen entspricht. Tatsächlich wird der Quelloperand in Ziel kopiert, bleibt also erhalten.
In jedem Fall müssen Ziel und Quelle die gleiche Größe haben. Als Quelle kommen Register, Speicherstellen (Variablen) und Konstanten in Frage, als Ziel sind nur Register und Speicherstellen möglich.

Beispiele:

DATASEG
        MyByte db 7
        MyWord dw 0C3Fh
        MyWord2 dw 8657
        MyConst = 249
CODESEG
        ...
        mov ax,34Fh
        mov bl,[MyByte]
        mov di,[MyWord]
        mov [MyWord],ax
        mov dh,34
        mov [MyByte],dh
        mov [MyByte],128
        mov cl,MyConst
        mov [MyWord],MyConst
        ...
        mov [MyWord],[MyWord2]          ;verursacht einen Fehler
        mov [MyWord],al                 ;verursacht einen Fehler
        mov al,[MyWord2]                ;verursacht einen Fehler
        mov al,cx                       ;verursacht einen Fehler

Leider ist das Kopieren eines Speicherwertes in einen anderen nicht gestattet, deshalb führt der Befehl mov [MyWord],[MyWord2] zu einem Fehler.
Wie schon angesprochen, müssen die Operanden die gleiche Größe aufweisen. In den letzten drei Anweisungen ist dies nicht der Fall; es wird immer ein Byte mit einem Word konfrontiert (s. dazu nächstes Kapitel).

Beachten Sie, daß es nicht möglich ist, ein Segmentregister direkt mit einer Konstanten zu laden. Sie müssen den Wert immer erst in ein allgemeines Register schreiben und von dort in das Segmentregister schreiben.
Dafür ist es durchaus möglich, ein Segmentregister direkt mit einem Speicherwert zu beschreiben.

        mov es.0B800h                   ;verursacht einen Fehler
        mov ax,0B800h                   ;erst in ein allg. Register
        mov es,ax                       ;dann ins Segmentregister
        mov [MyWord],0B800h
        mov es,[MyWord]                 ;kein Problem

XCHG

Ein weiterer Befehl ist in der Lage, den Inhalt seiner zwei Operanden gegeneinander auszutauschen:

xchg Operand1, Operand2

Die Operanden müssen die gleiche Größe haben. Es kann sich jeweils um Register oder Speicherstellen handeln. Wie beim Befehl mov dürfen beide Operanden nicht zugleich Speicherstellen sein.
Beispiele:

        xchg dx,cx
        xchg [MyByte],dh
        xchg bx,[MyWord]

Die OpCodes der dargestellten Beispiele sind mindestens 2 Bytes lang, meistens länger. Es gibt zwei Sonderformen, die kürzer sind:

        xchg ax,reg16                   ;benötigt nur 1 Byte
        xchg eax,reg32

Das ist einer der wenigen Befehle, wo die Operandenreihenfolge keine Rolle spielt, schließlich werden sie nur gegeneinander ausgetauscht.

LDS, LES, LFS, LGS, LSS

Die folgenden Befehle erlauben es, eine Kombination aus einem Segment- und einem Nicht-Segmentregister mit einem einzigen Befehl mit Werten zu füllen. Dabei wird das Segmentregister durch den Befehl selbst bestimmt (LDS füllt also das DS-Register). Das Nicht-Segmentregister ist der erste Operand des Befehls. Der zweite Operand ist ein 32-Bit-Speicherwert, dessen obere 16 Bit ins Segmentregister geschrieben werden. Folglich werden die unteren 16 Bits in das als ersten Operanden übergebene Nicht-Segmentregister geschrieben.

DATASEG
        MyDWord dd 012345h
CODESEG
        ...
        lds reg16,mem32                 ;allg. Form am Beispiel von LDS
        lds ax,[MyDWord]                ;konkrete Form

Die beiden Befehle LFS und LGS stehen erst ab dem 80386 zur Verfügung.

LEA

Die folgende Instruktion mit der allgemeinen Form
lea Ziel, Quelle
lädt die Adresse des Quelloperanden in das Ziel.

Der erste Operand muß ein 16- bzw. 32-Bit-Register sein, der zweite Operand muß ein Speicherwert sein. Der Befehl lädt die effektive Adresse (LEA = Load Effective Address) des Speicherwertes nach Abschluß der Adressberechnungen

Im allgemeinen wird der Befehl verwendet, um Register für Interruptfunktionen mit Werten von Speicheroperanden zu füllen. Es gibt für diesen Zweck eine Assemblerdirektive, die das gleiche erledigt.

DATASEG
        MyText db "Adresse gesucht"
CODESEG
        ...
        lea ax,MyText                   ;Version mit LEA
        mov ax, OFFSET MyText           ;mit Assemblerdirektive

Die Variante mit der Assemblerdirektive ergibt nur Sinn, wenn die Adresse des Speicherwertes zur Übersetzungszeit bekannt ist. In anderen Fällen (z. B. als Parameter bei einem Unterprogramm) muß lea verwendet werden.
Die Intel-Prozessoren verfügen über eine Vielzahl von Adressierungsarten bei denen eine vielfältige Kombination von Registern und Konstanten über Addition und Multiplikation möglich ist. Mit lea ist es möglich, eine Adresse in einem Schritt zu errechnen, statt die Berechnung über mehrere Zwischenschritte mit den entsprechenden Operationen auszurechnen. In diesen Fällen spielt lea seine Stärke aus, da die Berechnung wesentlich schneller ausgeführt wird als über einzelne Operationen.

IN

Der Befehl in ist als eine Sonderform des mov-Befehles zu verstehen. Allerdings ist die Quelle der Daten kein Register oder eine Speicherstelle, sondern einer der I/O-Ports, über die der Prozessor mit der Computerperipherie verbunden ist.
Die möglichen Erscheinungsformen dieses Befehls lauten:

in Ziel, Quelle                         ;generelle Form
in eax/ax/al, Const8                    ;1. spezifische Form
in eax/ax/al, dx                        ;2. spezifische Form

Als Ziel ist natürlich nur jeweils eines der drei angegebenen Register zugelassen. Dabei ist das Register EAX erst ab dem 80386 möglich.
Der 80x86 kann theoretisch mit 65536 Ports kommunizieren. Um alle Ports erreichen zu können muß die gewünschte Portnummer als Word übergeben werden. Der Parameter Const8 (direkte Port-Angabe) in der 1. spezifischen Form ist aber ein Byte-Wert. Aus diesem Grund lassen sich mit der 1. Form nur die ersten 256 Ports erreichen. Statt Port wird also eine konkrete Zahl im Bereich 0..255 angegeben.
Für Ports mit höheren Portnummern müssen Sie die Nummer ins Register DX schreiben und mit der 2. spezifischen Form arbeiten.
Hier ein paar Beispiele

        in al,060h                      ;liest ein Byte vom Port 60h nach AL
        in eax,0A0h                     ;ein DWord von Port 0A0h nach EAX
        mov dx,04F3Ch                   ;Portnummer > 255 nach DX
        in ax,dx                        ;vom Port in DX ein Word nach AX

OUT

Dieser Befehl ist die Umkehrung des Befehls in. Es gelten die gleichen Bedingungen.

        out Ziel, Quelle                ;allgemeine Form
        out Port, al/ax/eax             ;1. spezifische Form
        out dx, al/ax/eax               ;2. spezifische Form
        ;
        out 34h,ax                      ;den Wert in AX an Port 34h senden
        mov dx,04573h                   ;Portnummer > 255 nach DX
        out dx,al                       ;an diesen Port den Inhalt von AL senden

Die Befehle in und out werden hauptsächlich in der hardwarenahen Programmierung eingesetzt. Überall da , wo externe Hardwarekomponenten angesprochen werden sollen (extern=außerhalb der CPU), kann man entweder auf ein BIOS für diese Komponente zurückgreifen (so vorhanden), oder direkt über Ports auf die Hardware zugreifen. Beispiele sind die Programmierung von Grafikkarten oder Netzwerkkarten.
Ports stellen die unterste und damit hardwarenächste Ebene der Programmierung dar. Das bedeutet aber auch, daß Sie sich in ein Gebiet begeben, das nicht in jeder Hinsicht standardisiert ist. Sie könnten also bei intensiver Portprogrammierung Software erstellen, die nur auf einer Hardwarebasis läuft, da zum Beispiel der gleiche Port auf unterschiedlichen Grafikkarten zu verschiedenen Reaktionen führt.

XLAT / XLATB

Als letzten Befehl in dieser Kategorie besprechen wir xlatb. Dieser Befehl ist in der Lage, über einen Index, der im Register AL liegen muß, einen Wert aus einer Tabelle zu entnehmen, deren Anfangsadresse im Register BX steht. Der entnommene Wert wird seinerseits in das Register AL geschrieben.
Dieser Befehl wird häufig in Umsetzungstabellen verwendet, z. B. um verschiedene Zeichensätze umzuwandeln.
Dadurch, daß der Index im Register AL liegt, kann die Tabelle nicht größer als 256 Bytes sein. Ebenso ist immer nur der Zugriff auf Byte- Elemente möglich, eine Tabelle mit Word-Elementen macht also keinen richtigen Sinn.
Beispiel:

DATASEG
        MyTable db 'Hallo, wie geht es Dir'
CODESEG
        ...
        mov bx,OFFSET MyTable           ;Adresse der Tabelle
        mov al,12                       ;der Index in die Tabelle
        xlatb                           ;Wert holen und in AL schreiben

Da die Zählung bei Null beginnt, wird hier das 'e' aus dem Wort 'geht' ins Register Al geschrieben.

Xlatb ist eine verkürzte Form von xlat Tabelle. Bei diesem Befehl wird durch den Operanden Tabelle lediglich festgelegt, in welchem Segment sich die Tabelle befindet, der Offset wird trotzdem in BX erwartet. Bei xlatb wird grundsätzlich davon ausgegangen, daß die Adresse der Tabelle über DS:BX zu ermitteln ist.

Es gibt noch weitere Befehle, die Daten bewegen können, diese gehören aber in die noch andere Befehle umfassende Klasse der Stringbefehle, die in einem eigenen Kapitel behandelt werden.

Cast-Operationen und Konvertierungsbefehle

Es kann, wie auch im vorherigen Abschnitt, vorkommen, daß Sie einen bestimmten Datentyp vor sich haben, die nachfolgende Aktion aber mit einem anderen Datentyp durchgeführt werden muß. Nehmen wir das Beispiel mov [MyWord],al. In diesem Fall wird versucht, eine Variable vom Typ Word mit einem Wert vom Typ Byte zu beschreiben. TASM meldet sich in solchen Fällen mit dem Fehler "Operand types do not match".
In diesem Fall ist folgender Ausweg möglich:

        mov ah,0
        mov [MyWord],ax

Da nur das untere Byte (AL) von Wichtigkeit ist, wird mit dem ersten Befehl das High-Byte mit Null überschrieben. Wenn auch das High-Byte vernachlässigbar ist (in den folgenden Befehlen nicht verwendet wird), kann dieser Schritt auch entfallen. Danach wird das komplette Register AX in MyWord kopiert.
Hätte das Beispiel mov ah,[MyWord] gelautet, bestünde folgender Ausweg:

        mov ax,[MyWord]                 ;AX:=MyWord
        xchg al,ah                      ;AH:=LowByte(MyWord)

In beiden Fällen wäre jeweils der Inhalt des Registers AH verloren gewesen. Wichtige Daten hätten vorher also in andere Register, auf den Stack oder in Variablen kopiert werden müssen.

Das sogenannte Type-Casting bietet die Möglichkeit, diese Umwege zu vermeiden.
Im ersten Beispiel ist es den Umständen entsprechend egal, was im High-Byte steht, deswegen ist für diese Fälle das Typ-Casting nicht so wichtig.
Möchte man dagegen im zweiten Beispiel tatsächlich das Low-Byte von MyWord haben, dann kann man einfach folgendes schreiben:

        mov ah,[byte MyWord]

Mit dem Wörtchen byte in der eckigen Klammer werden die oberen 8 Bits von MyWord abgeschnitten; sie fallen einfach aus dem Betrachtungsrahmen. Auf diese Weise erhält man das Low-Byte von MyWord, ohne zusätzlich Registerinhalte zu überschreiben oder zusätzliche Befehle verwenden zu müssen.

Natürlich läuft es ähnlich ab, wenn Sie ein Word erhalten wollen, z. B. mov ax,[word MyByte] oder ein DWord (mov edx,[dword MyWord]

Wenn Sie von einem größeren Datentyp in einen kleineren casten, dann wird der überschüssige Teil abgetrennt. Im umgekehrten Falle ist der hinzukommende Teil undefiniert. Es macht also keinen Sinn, mit Befehlen zu arbeiten, die diesen undefinierten Teil verwenden.

CBW

cbw

Dieser Befehl erweitert das Byte im Register auf ein Word (cbw=convert byte to word). Dabei wird eine Vorzeichenerweiterung durchgeführt. Dies bedeutet, daß Bit 7 des Quellbytes (in AL) in die Bits 8-15 kopiert wird. Ist im Register AL das 7. Bit gesetzt, dann wären nach Ausführung dieses Befehls die Bits 8-15 im Register AX ebenfalls gesetzt. Da implizit immer mit AL und AX gearbeitet wird, existieren keine Parameter.
Den Befehl gibt es bereits im 8086. Insbesondere wird er bei 8-Bit-Divisionen wichtig.

CWD

cwd

Dieser Befehl führt eine Vorzeichenerweiterung des Words im Register AX auf ein DWord durch. Das 32-Bit-Ergebnis wird im Registerpaar DX:AX abgelegt. Der Befehl existiert bereits seit dem 8086, daher wird keine Erweiterung in das Register EAX durchgeführt. Bit 15 des Registers AX wird in alle Bits des Registers DX kopiert.
Es werden keine Parameter benötigt. Wie cbw hat der Befehl eine wichtige Funktion bei der Division.

CWDE

cwde

Dieser Befehl macht im Grunde das gleiche wie cwd, allerdings wird das 32-Bit-Ergebnis in das Register EAX geschrieben.
Der Befehl ist erst ab dem 80386 verfügbar.

CDQ

cdq

CDQ führt eine Vorzeichenerweiterung des 32-Bit-Wertes im Register EAX auf einen 64-Bit-Wert durch. Das Ergebnis wird in den Registern EDX:EAX abgelegt. Bit 31 des Registers EAX wird in alle Bits des Registers EDX kopiert.
Wie cwde ist dieser Befehl erst ab dem 80386 verfügbar.
Um einen 8-Bit-Wert auf einen 32- oder 64-Bit-Wert zu erweitern, können Sie folgende Sequenzen verwenden

        ;AL auf 32 Bit nach DX:AX erweitern
        cbw
        cwd

        ;AL auf 32 Bit nach EAX erweitern
        cbw
        cwde

        ;AL auf 64 Bit nach EDX:EAX erweitern
        cbw
        cwde
        cdq

MOVSX

movsx Ziel, Quelle

Dieser Befehl ist im Grunde eine Verallgemeinerung der Befehle cbw und cwde. Er steht für MOVe with Sign eXtension.
Die möglichen Erscheinungsformen sind:

movsx reg16, reg8
movsx reg16, mem8
movsx reg32, reg8
movsx reg32, mem8
movsx reg32, reg16
movsx reg32, mem16

Die oben genannten Befehle können Sie mit folgenden Instruktionen simulieren:

movsx ax, al                            ;CBW
movsx eax, ax                           ;CWDE
movsx eax,al                            ;Kombination von CBW und CWD

MOVSX ist ab dem 80386 verfügbar. Es gibt keine movsx-Äquivalente für cwd und cdq.

MOVZX

movzx Ziel, Quelle

MOVZX (MOVe with Zero eXtension) funktioniert eigentlich genauso wie movsx, allerdings mit dem Unterschied, daß hier nicht der Wert des höchstwertigen Bits des Quelloperanden kopiert wird sondern in jedem Falle eine Null.
Die Syntax entspricht der von movsx. Der Befehl ist ab dem 80386 verfügbar.

Beachten Sie, daß sowohl movsx als auch movzx auch Datentransportbefehle sind. Es ist also durchaus möglich, daß bei zwei Registeroperanden das Quellregister nicht Bestandteil des Zielregisters ist.
So ist bei den ersten vier Befehlen dieses Unterkapitels (cbw,cwd,cwde,cdq) die implizite Quelle immer Teil des ebenso impliziten Ziels.
Bei movsx und movzx kann das Ziel beispielsweise AX sein und die Quelle in BL liegen.

Sollte eine zero extension von 8 auf 16 Bit in einem allgemeinen 16-Bit-Register (AX, BX, CX, DX) durchgeführt werden, so ist die Verwendung eines einfachen mov schneller und kürzer.

statt:
        movzx ax,al
lieber:
        mov ah,0h

Sollten Ziel und Quelle allerdings unterschiedlichen Registern angehören, so ist movzx vorzuziehen.

BSWAP

bswap reg32

Es gibt zwei weitverbreitete Formen des Speicherorganisation. Sie nennen sich Little Endian und Big Endian. Little Endian findet vor allem bei x86-Prozessoren Anwendung, Big Endian wird dagegen bei den 68000 von Motorola (verwendet in Apples Macintosh) sowie bei vielen RISC-Prozessoren angetroffen.
Little Endian zeichnet sich dadurch aus, daß der niederwertigste Teil eines Datums im Speicher an der niedrigsten Adresse steht. Bei einem 32-Bit-Wert stehen also die Bits 0-7 an der niedrigsten Adresse, dann folgen die Bits 8-15, 16-23 und 24-31.
Bei Big Endian verhält es sich genau umgekehrt: die Bits 24-31 stehen an der niedrigsten Adresse, dann folgen die Bits 16-23, 8-16 und an der höchsten Adresse stehen die Bits 0-7.

BSWAP nimmt eine Konvertierung zwischen diesen beiden Formaten vor, indem das erste und vierte Byte sowie das zweite und dritte Byte gegeneinander ausgetauscht werden.

Die berechtigte Frage, warum man sich um diese Organisationsfragen kümmern soll, da x86-Programme sowieso nicht auf 68000-Prozessoren laufen, ist leicht beantwortet.
Die eben selbst gegebene Antwort ist natürlich korrekt. Es ist allerdings durchaus üblich, Daten zwischen Rechnern auszutauschenen, die eine unterschiedliche Speicherorganisation verwenden. Um bei der Auswertung dieser Daten auf korrekte Ergebnisse zu kommen, ist eine Umwandlung nötig.
Beachten Sie, daß ein zweimaliges bswap auf dem gleichen Register die ursprüngliche Datenorganisation wiederherstellt.
BSWAP ist erst ab dem 80486 verfügbar.

Arithmetische Befehle

Die arithmetischen Befehle des 8086 arbeiten alle mit ganzen Zahlen. Um mit Fließkommazahlen zu arbeiten, müssen Sie den mathematischen Koprozessor (z. B. 8087) bemühen.

Es werden Befehle für die normalen Grundrechenarten zur Verfügung gestellt, Befehle zum einfachen Addieren und Subtrahieren sowie zur Modulo- Rechnung, Befehle für die Arbeit mit BCDs (Binary Coded Decimals) sowie Befehle, die das Carry-Flag verwenden.

Addition

add Ziel, Quelle

Zum ersten Operanden wird der zweite Operand addiert und im ersten Operanden gespeichert. Beide Operanden müssen den gleichen Datentyp besitzen. Als Operanden kommen Register, Konstanten und Speicherstellen in Byte-, Word-, und DWord-Größe in Frage. Das Ziel darf keine Konstante sein und eine Kombination von zwei Speicherstellen ist ebenfalls nicht gestattet. Ansonsten ist eine beliebige Reihenfolge von Register, Konstanten und Speicherstellen möglich
Beispiele:

        add ax,24
        add [MyByte],15647
        add bx,[MyWord]
        add [MyWord],cx
        add [MyWord],[AddWord]          ;Fehler: zwei Speicheroperanden
        add 54676,dx                    ;Fehler: erster Operand ist Konstante

Nach Ausführung des Befehls werden einige Flags gesetzt. Da der Prozessor nicht wissen kann, ob es sich um vorzeichenbehaftete Zahlen handelt oder nicht werden die Flags so gesetzt, dass sowohl bei vorzeichenbehafteter als auch vorzeichenloser Interpretation eine korrekte Beurteilung des Ergebnisses vorgenommen werden kann. Folgende Flags sind betroffen:

  1. Carry-Flag, wenn bei vorzeichenlosen Zahlen ein Überlauf auftrat, das Ergebnis also nicht mehr korrekt in das Zielregister passt
  2. Overflow-Flag, wenn bei vorzeichenbehafteten Zahlen ein Überlauf auftrat
  3. Auxiliary-Flag, wenn bei BCDs ein Überlauf auftrat
  4. Zero-Flag, wenn das Ergebnis 0 (Null) ist, egal ob Vorzeichen oder nicht
  5. Sign-Flag, wenn das höchstsignifikante Bit im Ergebnis gesetzt ist
  6. Parity-Flag, wenn im Ergebnis eine gerade Anzahl Bits gesetzt ist

Addition mit Carry

adc Ziel, Quelle

ADC macht nichts anderes als ADD, addiert aber noch eine Eins zum Ergebnis hinzu, wenn bei Befehlsausführung das Carry-Flag gesetzt war.
Bei den Prozessoren vor dem 80386 musste ein LongInt aufgrund der Registergröße in zwei 16-Bit-Registern dargestellt werden. Hierfür sind die Registerkombinationen DX:AX und CX:BX vorgesehen. Die Addition kann allerdings nur registerweise, also für jeweils 16 Bit durchgeführt werden. Dazu werden erst die beiden niederwertigen Register der Operanden miteinander addiert (mit ADD). Sollte hier ein Überlauf auftreten, so wird das Carry-Flag gesetzt. Anschließend werden die beiden höherwertigen Register miteinander addiert (mit ADC). Durch Einbeziehung des Carry-Flags in diese Addition wird das korrekte Ergebnis erzeugt.
Anwendungsbeispiel:

        mov ax,45278
        mov dx,0
        mov bx,36498
        mov cx,0
        add ax,bx                       ;81776 passt nicht in AX - CF wird gesetzt
                                        ;in AX steht jetzt 16241 (81776-65535)
        adc dx,cx                       ;addieren mit Carry, DX:AX = 81776
                                        ;weil CF=1 und CF wird zu DX dazuaddiert

Subtraktion

sub Ziel, Quelle

Vom ersten Operanden wird der Wert des zweiten Operanden abgezogen. Beide Operanden müssen den gleichen Datentyp besitzen. Als Operanden kommen Register, Konstanten und Speicherstellen in Byte-, Word-, und DWord- Größe in Frage. Das Ziel darf keine Konstante sein und eine Kombination von zwei Speicherstellen ist ebenfalls nicht gestattet. Ansonsten ist eine beliebige Reihenfolge von Register, Konstanten und Speicherstellen möglich
Beispiele:

        sub ax,24
        sub [MyByte],15647
        sub bx,[MyWord]
        sub [MyWord],cx
        sub [MyWord],[SubWord]          ;Fehler: zwei Speicherstellen
        sub 234,bl                      ;Fehler: erster Operand eine Konstante

Ist der zweite Operand größer als der erste, dann ist das Ergebnis logischerweise negativ. Allein aus den Bits läßt sich aber nicht ablesen, ob das Ergebnis nun eine negative Zahl oder eine hohe positive Zahl ist. Tritt dieser Fall ein, dann wird das Carry-Flag gesetzt. Die Bedeutung der restlichen Flagstellungen ist mit denen nach ADD identisch.

Subtraktion mit Borrow

sbb Ziel, Quelle

Ebenso wie bei ADD gibt es auch bei SUB eine Variante, die das Carry-Flag in die Berechnung einbezieht. Die Verwendung läuft dann auch analog.
Anwendungsbeispiel:

        sub ax,bx
        sbb dx,cx

Vorzeichenlose Multiplikation

mul Faktor

Mit mul wird eine vorzeichenlose Multiplikation durchgeführt.
Der Faktor kann ein Register oder eine Variable mit einer Größe von 8, 16 oder 32 Bit sein. Konstanten sind als Operanden nicht zugelassen. Abhängig von der Größe des Operanden unterscheidet sich auch das Verhalten des Befehles.
Bei einer Operandengröße von 8 Bit wird der Inhalt des Registers AL mit dem Faktor multipliziert, das Ergebnis liegt im gesamten Register AX. Das Carry- und das Overflow-Flag werden auf Null gesetzt, wenn im Register AH Null steht, ansonsten werden beide auf 1 gesetzt.
Bei einem Operanden mit Word-Größe wird der Inhalt von AX mit dem Faktor multipliziert, das Resultat steht hinterher im Registerpaar DX:AX, wobei DX die höherwertigen 16 Bits enthält. Carry- und Overflow-Flag werden auf Null gesetzt, wenn DX Null ist, ansonsten werden beide auf 1 gesetzt.
Bei einem DWord-Operanden wird das Register EAX mit dem Faktor multipliziert und das Resultat im Registerpaar EDX:EAX abgelegt. EDX enthält die höherwertigen 32 Bits des Resultats. Carry- und Overflow-Flag werden auf Null gesetzt, wenn EDX Null ist, ansonsten werden beide auf 1 gesetzt. Da das/die Zielregister immer ausreichend groß sind, kann es keinen Überlauf mehr geben.
Beispiele:

        mul bl                          ;8-Bit-Multiplikation, Ergebnis in BX
        mul [MyWord]                    ;16-Bit-Multiplikation, Ergebnis in DX:AX
        mul edx                         ;32-Bit-Multiplikation, Ergebnis in EDX:EAX

Vorzeichenbehaftete Multiplikation

imul Faktor
imul Ziel, Faktor1, Faktor2             ;ab 80186
imul Ziel, Faktor                       ;ab 80186 (ab 80386 erweitert)

IMUL führt eine Integer-Multiplikation unter Berücksichtigung des Vorzeichens durch.
Wie Sie sehen können, gibt es drei verschiedene Formen. Bei den Formen mit mehr als einem Operanden müssen die Operanden die gleiche Größe haben. Es sind Byte-, Word- und DWord-Operanden erlaubt.
Wird nur ein Operand verwendet (die erste Variante), dann wird implizit abhängig von der Operandengröße das Register AL, AX oder EAX verwendet.
Bei der Variante mit drei Operanden werden Operand 2 und 3 miteinander multipliziert und das Ergebnis in den ersten Operanden geschrieben.
Die folgende Tabelle zeigt, wie bei den unterschiedlichen Varianten die Ablage des Ergebnisses erfolgt und welche Operandenkombinationen möglich sind. Da als Operanden sowohl Register als auch Konstanten sowie Speicherstellen möglich sind, sind an den Stellen, an denen für einen Operanden verschiedene Typen möglich sind, die Typen durch einen Schrägstrich getrennt aufgeführt.

IMUL-Varianten
Befehl Ergebnis
IMUL reg8/mem8 implizite Verwendung von AL, Ergebnis in AX
IMUL reg16/mem16 implizite Verwendung von AX, Ergebnis in DX:AX
IMUL reg32/mem32 implizite Verwendung von EAX, Ergebnis in EDX:EAX
IMUL reg16,reg16/mem16 Ergebnis im ersten Operanden
IMUL reg32,reg32/mem32 Ergebnis im ersten Operanden
IMUL reg16,reg16/mem16,con8 Ergebnis im ersten Operanden
IMUL reg32,reg32/mem32,con8 Ergebnis im ersten Operanden
IMUL reg16,con8 Ergebnis im ersten Operanden
IMUL reg32,con8 Ergebnis im ersten Operanden
IMUL reg16,reg16/mem16,con16 Ergebnis im ersten Operanden
IMUL reg32,reg32/mem32,con32 Ergebnis im ersten Operanden
IMUL reg16,con16 Ergebnis im ersten Operanden
IMUL reg32,con32 Ergebnis im ersten Operanden

Ist das Ergebnis von IMUL für das Ziel zu groß, dann werden das Overflow- und das Carry-Flag gesetzt, ansonsten werden beide gelöscht.

Vorzeichenlose Division

div Divisor

Div führt eine vorzeichenlose Division durch. Der Dividend ist implizit immer gegeben, nur der Divisor wird als Operand übergeben. Die Division erfolgt so, daß immer ein ganzzahliger Rest zurückbleibt (der natürlich auch den Wert Null haben kann). Wenn man nur den Rest betrachet, dann kann man auf diese Weise auch gleich die Modulo-Operation durchführen.
Die folgende Tabelle zeigt, bei welchen Datentypen des Divisors welche Register wie belegt werden.

Größe Dividend Divisor Quotient Rest
Byte AX reg8/mem8 AL AH
Word DX:AX reg16/mem16 AX DX
DWord EDX:EAX reg32/mem32 EAX EDX

Beispiele:
        div dl                          ;8-Bit-Multiplikation
        div bx,[MyWord]                 ;16-Bit-Multiplikation
        div ecx                         ;32-Bit-Multiplikation

Vorzeichenbehaftete Division

idiv Divisor

IDIV führt eine Integer-Division unter Beachtung des Vorzeichens durch. Sowohl der Dividend als auch der Ergebnisspeicher sind implizit festgelegt und von der Operandengröße abhängig. Als Operanden kommen Register und Speicherstellen in Byte-, Word- und DWord- Größe in Frage.
Die verwendeten Register stimmen mit denen des Befehls div überein.

Inkrementieren

inc Ziel

Das Ziel ist gleichzeitig die Quelle. Der Operand wird genau um den Wert 1 erhöht, entspricht also add Ziel, 1, wird aber schneller ausgeführt. Als Operanden kommen Register und Speicherstellen in Byte- Word- und DWord-Größe in Frage.
Beispiele:

        inc ax
        inc ecx                         ;ab 80386
        inc [MyByte]

Wenn der Operand bereits den höchsten im Wertebereich erreichbaren Wert besitzt und dann inkrementiert wird, dann wird als Reaktion das Overflow-Flag gesetzt und der Operand erhält den Wert am unteren Ende des Wertebereichs, bei einem Wertebereich von 0..255 (bei Byte-Operanden) also die Null.
Der Befehl inc beeinflußt nicht das Carry-Flag. Wenn das ein Ziel sein sollte, dann müssen Sie tatsächlich mit dem Befehl add mit einer 1 als zweiten Operanden arbeiten.

Dekrementieren

dec Ziel

Das Ziel ist gleichzeitig die Quelle. Vom Operanden wird genau der Wert 1 abgezogen, der Befehl entspricht also sub Ziel, 1, wird aber schneller ausgeführt und benötigt weniger Platz. Als Operanden kommen Register und Speicherstellen in Byte- Word- und DWord-Größe in Frage.
Beispiele:

        dec ax
        dec ecx                         ;ab 80386
        dec [MyByte]

Dieser Befehl wird häufig in Schleifen mit abzählbaren Wiederholungen verwendet. Sollte sich beim Dekrementieren der Wert Null ergeben, dann wird das Zero-Flag auf 1 gesetzt. Im folgenden Kapitel über Sprungbefehle wird es für diesen Fall ein Code-Beispiel geben.
Wenn der Operand bereits den niedrigsten im Wertebereich erreichbaren Wert besitzt und dann inkrementiert wird, dann wird als Reaktion das Overflow-Flag gesetzt und der Operand erhält den Wert am oberen Ende des Wertebereichs, bei einem Wertebereich von 0..255 (bei Byte-Operanden) also die 255.
Der Befehl dec beeinflußt nicht wie der Befehl sub das Carry-Flag. Wenn das ein Ziel sein sollte, dann müssen Sie tatsächlich mit dem Befehl sub mit einer 1 als zweiten Operanden arbeiten.

Sprungbefehle

Die Sprungbefehle werden unterschieden in bedingte und unbedingte Sprünge.
Beiden gemeinsam ist, daß sie ein Sprungziel benötigen. Dieses ist hinter dem eigentlichen Sprungbefehl anzugeben und sieht folgendermaßen aus:

Sprungzielname:

Man beachte den Doppelpunkt hinter dem Namen.
Das Sprungziel wird auch als Sprungmarke bezeichnet.

Man steht in jedem längeren Programm vor dem Fall, daß man abhängig von einer bestimmten Situation eine bestimmte Handlung auszuführen hat.

Egal in welcher Programmiersprache wir uns bewegen, Entscheidungen werden erst nach einem vorherigen Vergleich getroffen. Egal, ob wir mit IF..THEN..ELSE, WHILE oder FOR arbeiten, immer wird etwas verglichen und abhängig von diesem Vergleich eine Handlung ausgeführt.

Programmcode wird normalerweise linear, so wie er im Hauptspeicher steht, ausgeführt. Bei einer Entscheidung gabelt sich dieser Weg jedoch in mindestens zwei Wege. Um einen dieser Wege zu erreichen, ist ein Sprung zu diesem nötig. Um herauszufinden, zu welchem Weg das Programm springen muß, muß ein Vergleich durchgeführt werden. In fast allen Fällen wird zur Auswertung des Vergleiches das Flagregister verwendet, in zwei anderen Fällen das Register CX bzw. ECX.

Bedingte Sprünge

Den bedingten Sprungbefehlen muss in jedem Falle ein Befehl vorausgehen, der die Flags bzw. (E)CX verändert. Die bedingten Sprungbefehle werten nun die Register aus (Flags bzw. ECX) und springen dann an eine bestimmte Stelle im Code (Sprungmarke).
In der folgenden Tabelle sind einige Sprungbefehle aufgeführt.

Eine Auswahl bedingter Sprungbefehle
Befehl Bedeutung
ja jump if above (ohne Vorzeichen)
jae jump if above or equal
jb jump if below (ohne Vorzeichen)
jbe jump if below or equal
jc jump if Carry Flag = 1
jcxz jump if CX = 0
jecxz jump if ECX = 0
je jump if equal
jg jump if greater (mit Vorzeichen)
jge jump if greater or equal (mit Vorzeichen)
jl jump if lower (mit Vorzeichen)
jle jump if lower or equal (mit Vorzeichen)
jna jump if not above
jnae jump if not above or equal
jnb jump if not below
jnbe jump if not below or equal
jnc jump if not Carry = 1
jne jump if not equal
jng jump if not greater
jnge jump if not greater or equal
jnl jump if not lower
jnle jump if not lower or equal
jno jump if not Overflow = 1
jnp jump if not Parity = 1
jns jump if not Sign = 1
jnz jump if not Zero = 1
jo jump if Overflow Flag = 1
jp jump if Parity Flag = 1
jpe jump if parity even (Parity = 1)
jpo jump if paritiy odd (Parity = 0)
js jump if Sign = 1
jz jump if Zero Flag = 1

In einem Programm würde sich ein bedingter Sprung zum Beispiel folgendermaßen realisieren lassen:

        ...
        mov cx,1
        dec cx
        jz CXIstNull
        ...
        CXIstNull:
                ...                     ;Die Einrückung dient nur der Lesbarkeit
                ...
        ...

Dieses zugegebenermaßen recht sinnlose Programmfragment lädt in das Register CX den Wert 1 und dekrementiert dieses dann. Da dann in CX der Wert 0 (Null) steht, wird durch den Befehl DEC das Zero-Flag im Flag- Register auf 1 gesetzt. Der nachfolgende Befehl JZ überprüft nun, ob denn im Flag-Register das Zero-Flag auf 1 gesetzt ist und führt, sollte dies der Fall sein, einen Sprung an die durch die Sprungmarke CXIstNull gekennzeichnete Stelle durch.

Wenn Sie die obige Tabelle eingehend durchgeschaut haben, dann wird Ihnen vielleicht der Befehl JCXZ aufgefallen sein. Diesen hätten Sie in diesem speziellen Fall anstelle des Befehls JZ verwenden können. Hätten wir allerdings mit dem Register BX statt CX gearbeitet, dann bliebe Ihnen nur ein Code wie der obige.

Mit den bedingten Sprungbefehlen lassen sich Konstrukte im Stile einer FOR-Schleife aufbauen:

        mov cx,400
        LoopStart:
                ...                     ;Code, der eine Handlung ausführt
                dec cx
                jnz LoopStart
        ...

In das Register CX wird erst der Wert 400 geladen. Dann führt der Prozessor den Code aus, der hinter der Sprungmarke steht. Gehen wir einfach davon aus, daß dieser Code die Zahl, die in Register CX steht, auf dem Bildschirm ausgibt, im ersten Durchlauf also 400.
Nachdem dies erledigt ist, wird das Register CX um 1 dekrementiert. Beim ersten Durchlauf steht jetzt also 399 in CX. Danach wird geprüft, ob das Zero-Flag gesetzt ist. Da das vorherige Dekrementieren von CX nicht Null ergab, das Zero-Flag also nicht auf 1 gesetzt wurde, bringt uns JNZ (Jump if Not Zero) wieder zur Sprungmarke LoopStart. Ab da wird dann wieder der Inhalt von CX ausgegeben (noch immer 399), dekrementiert (jetzt ist CX=398), das Zero-Flag ausgewertet und gesprungen. Das geht solange, bis in CX nur noch eine 1 steht, diese dekrementiert wird, dadurch das Zero-Flag gesetzt wird, die Sprungbedingung nicht mehr zutrifft und schlussendlich die Schleife verlassen wird.

LOOP

Befindet sich die Laufvariable wie eben im Register CX, dann hält der Prozessor noch den Befehl LOOP bereit. Mit diesem Befehl würde unser Programm folgendermaßen aussehen:

        mov cx,400
        LoopStart:
                ...                     ;Code, der eine Handlung ausführt
                loop LoopStart
        ...

LOOP dekrementiert selbständig das Register CX und springt, falls es nicht Null enthält, zu der Sprungmarke.
Die Verwendung von LOOP soll sich bis zum 80286 noch lohnen, in neueren Prozessoren ist die Kombination DEC...JXX schneller.

Unbedingter Sprung

Ein unbedingter Sprung wertet keine Flags oder andere Register aus, sondern springt sofort an die durch die Sprungmarke bezeichnete Stelle:

        ...
        mov cx,1
        jmp EinfachSo
        ...
        EinfachSo:
                ...
                ...
        ...

Dem JMP-Befehl ist es total egal, was in CX oder im Flag-Register steht, er springt einfach nur an die Sprungmarke EinfachSo.

Die bedingten Sprünge haben eine Reichweite von 128 Byte, es kann also maximal 128 Bytes vor- oder zurückgesprungen werden. Diese Einschränkung gilt jedoch nur bei Prozessoren, die älter als der 80386 sind. Ab dem 80386 haben die bedingten Sprünge eine Reichweite von bis zu 64 KByte. Sollten Sie für einen Prozessor ab dem 80386 programmieren, dann müssen Sie allerdings mittels der geeigneten Direktive die Codeerzeugung für diesen Prozessor einschalten, also beispielsweise mit P386N.
Im Gegensatz dazu kann der unbedingte Sprung mit jmp auf jedem Prozessor auch Segmentgrenzen überwinden, kurz gesagt also weiter als 64 KByte springen.
Unterhalb des 80386 müssen Sie sich eines kleinen Umweges bedienen, um größere (bedingte) Sprünge zu realisieren.
Das folgende Beispiel zeigt zuerst eine Schleifenkonstruktion, die einen Fehler hervorruft und danach die korrigierte Version.

        ;es gilt die Annahme, daß kein 80386 oder höher eingestellt ist
        ...
        EntferntesZiel:
                ...                     ;Code mit einer Größe von
                ...                     ;mehr als 128 Byte
                dec cx
                jnz EntferntesZiel      ;falls CX <> 0 dann neuer Durchlauf
        ...

Und jetzt die korrigierte Version

        ;gleiche Annahme wie oben
        ...
        EntferntesZiel:
                ...                     ;Code mit einer Größe von
                ...                     ;mehr als 128 Byte
                dec cx
                jz Ende                 ;falls CX=0 dann Sprung zum Schleifenende
                jmp EntferntesZiel      ;ansonsten neuer Durchlauf
        Ende:
        ...

Zwischen der Sprungmarke EntferntesZiel und dem bedingten Sprungbefehl liegt Code, der mehr als 128 Bytes beansprucht. Der Sprung soll durchgeführt werden, wenn nach dem dekrementieren von CX ein anderer Wert als Null in diesem Register steht. Wenn TASM beim Kompilieren zu diesem Befehl kommt, dann gibt er den Fehler "Relative jump out of range by .. bytes", wobei die beiden Punkte durch einen entsprechenden Zahlenwert ersetzt werden.

Bei der korrigierten Variante fallen zwei Dinge auf: es gibt einen weiteren Befehl und es gibt eine weitere Sprungmarke.
Wieder wird das Register CX dekrementiert. Der nachfolgende Befehl prüft aber diesmal, ob nach dem Dekrementieren der Wert Null in CX steht. Sollte dies der Fall sein, dann wird die Schleife mit einem Sprung zur Sprungmarke Ende verlassen. Sollte dies nicht der Fall sein, dann wird einfach beim nächsten Befehl weitergemacht, der ohne Umschweife zum Schleifenanfang (EntferntesZiel) springt.

Um es dem Programmierer zu ersparen, über Sprungweiten nachzugrübeln, existiert die Assemblerdirektive JUMPS, die automatisch Code erzeugt, wenn Sprungweiten überschritten werden. Das funktioniert aber nur dann in beide Richtungen, wenn mittels des Kommandozeilenschalters /m2 die Mehr-Pass- Assemblierung eingeschaltet wurde.

Sollte als Ausführungsplattform ein 80386 oder höher eingestellt sein, so ist auch die Direktive JUMPS unnötig, da ab dem 80386 innerhalb eines Segments bedingte Sprünge mit einer Weite bis zu 64 KB durchgeführt werden können.

Stringbefehle

Ein String im Sinne der im folgenden behandelten Befehlen kann am ehesten als ein Array von Bytes, Words und, ab dem 80386, auch DWords betrachtet werden. Natürlich können auch mit Prozessoren vor dem 80386 DWord-Strings bearbeitet werden, aber erst ab dem 80386 gibt es dafür spezielle Befehle.
Verwechseln Sie diese Art von Strings nicht mit denen, die Sie vielleicht von Pascal oder C her kennen.

LODSB / LODSW / LODSD

lodsb
lodsw
lodsd                                   ;ab 80386

Diese Befehle greifen alle auf die Adresse zu, die durch die Register DS:SI bestimmt wird. Abhängig von der Endung des Befehls (b, w oder d) wird von dieser Adresse ein Byte (b), Word (w) oder ein DWord (d) geladen und in das Register AL, AX oder EAX geschrieben.
Ist das Direction Flag im Flagregister gelöscht, dann wird gleichzeitig zum Inhalt des Registers SI die Operandengröße addiert, also entweder 1, 2 oder 4. In einem String würde das Register damit auf das nächste Element zeigen. Ist das Direction Flag gesetzt, dann wird von SI die Operandengröße abgezogen.
Es gibt zwei Befehle, die das Direction Flag explizit löschen oder setzen, nämlich cld und std

Dieser Befehl wird hauptsächlich in Schleifen verwendet, um ein Stringelement herauszugreifen, zu bearbeiten und dann mit dem nächsten fortzufahren.

STOSB / STOSW / STOSD

stosb
stosw
stosd                                   ;ab 80386

Diese Befehle schreiben den Inhalt des Registers AL (bei Endung b), AX (Endung w) oder EAX (Endung d) an die Adresse, die durch ES:DI bestimmt wird.
Ist das Direction Flag gelöscht, dann wird zu DI wieder die Operandengröße addiert, ist es gesetzt, wird die Operandengröße abgezogen.

Durch Kombination von lods und stos können Sie einen String in einer Schleife elementweise bearbeiten. Voraussetzung ist, daß sowohl DS:SI als auch ES:DI auf die gleiche Adresse verweisen.
Möglicher Code dafür könnte folgendermaßen aussehen.

TITLE Ein ganz einfacher Codieralgorithmus
IDEAL                                   ;IDEAL-Mode einschalten
MODEL SMALL                             ;Speichermodell SMALL
STACK 100                               ;Stack reservieren
DATASEG
        StringToEncode db "Dieser Text soll codiert werden",13,10,"$"
        len = $ - StringToEncode - 3    ;Länge des Strings - 3 als Konstante
CODESEG
start:
        STARTUPCODE
          mov ah,09h                    ;Funktionsnummer für Textausgabe
          mov dx,OFFSET StringToEncode  ;DX mit Adresse des Strings laden
          int 21h                       ;String auf Bildschirm ausgeben
          mov ax,ds                     ;DS und ES müssen gleichen Inhalt haben
          mov es,ax                     ;DS und ES zeigen aufs gleiche Segment
          mov si,dx                     ;SI mit Adresse des Strings laden
          mov di,dx                     ;SI und DI haben gleichen Inhalt
          mov cx,len                    ;nach CX die Stringlänge
          cld                           ;Direction Flag löschen
          encodeLoop:
            lodsb                       ;lädt ein Zeichen, erhöht SI
            xor al,45                   ;das Zeichen codieren
            stosb                       ;Zeichen in String schreiben
            dec cx                      ;Schleifenzähler dekrementieren
            jnz encodeLoop              ;falls nicht Null dann neuer Durchlauf
          mov ah,09h                    ;Funktionsnummer
          mov dx,OFFSET StringToEncode  ;DX mit Adresse des Strings laden
          int 21h                       ;String erneut ausgeben, diesmal codiert
        EXITCODE
END start

Gehen wir dieses Programm Schritt für Schritt durch.
Zuerst kommen die üblichen Einstellungen für Speichermodell und Programmiermodus sowie die Stackgröße. Ganz am Anfang steht eine identifizierende Kurzbeschreibung hinter TITLE. Auf eine Einstellung für den Prozessor habe ich verzichtet, da nur Code verwendet wird, der auch auf einem 8086 lauffähig ist.

Im Datensegment, das mit DATASEG eröffnet wird, definieren wir als erstes unseren String. Ich habe mich hier tatsächlich für eine Zeichenkette entschieden, da das Ergebnis deutlich aufgezeigt werden kann. Ich hätte mich auch für eine Kette von Zahlen im Word-Format entscheiden können.
Dieser String endet mit den ASCII-Codes für Carriage Return und Line Feed und einem Dollarzeichen. Das Dollarzeichen ist als Endemarkierung für die später verwendete DOS-Funktion nötig, die beiden anderen Zeichen für den Zeilenumbruch bei der Stringausgabe.
In der nächsten Zeile wird die Länge der Zeichenkette ermittelt. Dazu wird der sogenannte Location Counter verwendet, der im Kapitel über Strukturierte Programmierung ein eigenes Unterkapitel einnimmt.
Da die letzten drei Zeichen nicht verändert werden sollen (wir brauchen sie für die Ausgabe), werden abschließend noch drei Bytes von der ermittelten Länge abgezogen.

Im Codesegment wird der Programmeintrittspunkt definiert (start:) und dann mittels STARTUPCODE der korrekte Startcode eingefügt.
Als erstes wird der noch unveränderte String mittels einer von DOS bereitgestellten Funktion auf dem Bildschirm ausgegeben. Dazu wird in AH die Funktionsnummer geladen (09h), in DX die Adresse des ersten Bytes des Strings hinterlegt (OFFSET StringToEncode) und schließlich der DOS-Interrupt aufgerufen (int 21h).
Da DS:SI und ES:DI auf die gleiche Adresse verweisen müssen, wird zuerst der Inhalt des Registers DS (dessen Wert wird durch STARTUPCODE eingestellt) nach AX kopiert und von dort ins Register ES geschrieben. Sie erinnern sich: Segmentregister können nicht direkt beschrieben werden, wenn die Quelle auch ein Segmentregister ist; daher der Umweg.
Dann müssen noch die beiden Index-Register geladen werden. Da in DX noch die Offsetadresse des Strings steht (von der vorherigen Ausgabe), kann dieser Wert gleich in die Register SI und DI kopiert werden.
Nach Abschluß dieser Arbeiten wird noch die ermittelte Stringlänge abzüglich der letzten drei Zeichen nach CX geschrieben und das Direction Flag explizit gelöscht (üblicherweise ist dieses Flag immer gelöscht, aber man weiß ja nie).

Da nun alle Register gesetzt sind, wird in einer Schleife in sequentieller Reihenfolge ein Zeichen aus dem String ins Register AL kopiert, dort mittels xor mit dem Wert 45 verknüpft und dann wieder an seine alte Position zurückgeschrieben. Der vorherige Wert wird durch diese Maßnahmen überschrieben. Dann wird das Register CX, das hier als Schleifenzähler fungiert, dekrementiert und anschließend geprüft, ob diese Dekrementierung das Ergebnis Null hatte. Sollte dies nicht der Fall sein, wird an die durch encodingLoop bezeichnete Stelle (resp. Adresse) zurückgesprungen und die Codierungssequenz erneut durchlaufen. Beachten Sie dabei, daß durch die automatische Erhöhung der Register SI und DI diese schon auf die nächsten Elemente des Strings zeigen.

Ist der String codiert, wird er mit der bekannten Funktion auf dem Bildschirm ausgegeben.

MOVSB / MOVSW / MOVSD

movsb
movsw
movsd                                   ;ab 80386

MOVS ermöglicht den Speicher-zu-Speicher-Transfer von Daten. Dabei wird das Byte, Word oder DWord an der Adresse DS:SI in die Speicherstelle an der Adresse ES:DI kopiert.
Abhängig vom Direction Flag werden die Index-Register danach um die Operandengröße erhöht (DF = 0) oder verkleinert (DF = 1).

SCASB / SCASW / SCASD

scasb
scasw
scasd                                   ;ab 80386

SCAS vergleicht den Inhalt des Registers AL (scasb), AX (scasw) oder EAX (scasd) mit dem Inhalt der Speicherstelle an der Adresse ES:DI und setzt danach entsprechend dem Befehl cmp das Flagregister. Abhängig von der Operandengröße und dem Direction Flag wird ebenfalls das Register DI in bekannter Weise verändert.
Diese Operation eignet sich sehr gut, innerhalb einer Schleife in einem String einen bestimmten Wert aufzufinden.

CMPSB / CMPSW / CMPSD

cmpsb
cmpsw
cmpsd                                   ;ab 80386

CMPS vergleicht die Inhalte der Speicherstellen DS:SI und ES:DI miteinander und setzt dann entsprechend dem Befehl cmp das Flagregister. Abhängig von der Operandengröße und dem Direction Flag werden ebenfalls die Register SI und DI in bekannter Weise verändert.

INS

ins{b,w,d}

INS arbeitet implizit mit dem Register DX, in dem die Portnummer erwartet wird. Von diesem Port holt der Befehl ein Byte, Word oder DWord (je nachdem, ob der letzte Buchstabe ein b,w oder d ist). Dieses Byte, Word, Dword wird an die Speicherstelle geschrieben, auf die ES:DI zeigt. Ist das Direction- Flag gelöscht, dann wird danach das Register DI um die Operandengröße erhöht, im anderen Fall um die Operandengröße verringert.

OUTS

outs{b,w,d}

Hier gelten die gleichen Voraussetzungen wie beim Befehl ins. Je nach Suffix wird ein Byte, Word oder DWord an den Port im Register DX gesendet. Die Daten werden von der Adresse DS:SI geholt. Abhängig vom Direction-Flag wird dann SI erhöht oder verringert.

REP

rep ins{b,w,d}
rep outs{b,w,d}
rep lods{b,w,d}
rep stos{b,w,d}
rep movs{b,w,d}

Bei REP handelt es sich um ein sogenanntes Befehlspräfix, das den angegebenen Stringbefehlen vorangestellt werden kann. Implizit wird bei REP und seinen Varianten immer das Register (E)CX verwendet. Der Stringbefehl wird sooft wiederholt, wie in (E)CX angegeben. Um Speicherblöcke zu kopieren, ist ein rep movs{b,w,d} eine sehr schnelle Möglichkeit. Um einen Speicherblock mit einem Wert zu versehen, kann rep stos{b,w,d} verwendet werden.

Zugegebenermaßen ist die Verwendung von REP in Verbindung mit LODS nicht sonderlich sinnvoll, da das Stringelement (E)CX-mal ins Register EAX/AX/AL geschaufelt wird, man aber keine Möglichkeit erhält, diese Zeichen anderweitig zu bearbeiten.
Wie bei den normalen Stringbefehlen wirkt sich auch hier der Wert des Direction-Flags aus.

Bei rep outs{b,w,d} ist zu beachten, dass die Hardware am entsprechenden Port die Daten vielleicht nicht so schnell verarbeiten kann wie sie hereinkommen.

Die Verwendung von REP mit anderen Befehlen führt zu undefinierten Ergebnissen.

Der Ablauf bei REP Stringbefehl lässt sich grundsätzlich wie folgt beschreiben: zuerst wird das Register (E)CX daraufhin überprüft, ob es den Wert Null enthält. Sollte dies der Fall sein, so wird der Befehl nicht ausgeführt. Anschließend wird das Register (E)CX um den Wert 1 dekrementiert. Danach werden abhägig vom Direction Flag die Register SI und DI um die Operandengröße inkrementiert (DF = 0) bzw. dekrementiert (DF = 1).

REPE/REPZ

repe cmps{b,w,d}
repe scas{b,w,d}

Der angegebene Stringbefehl wird so lange ausgeführt, wie das Register CX ungleich Null sowie das Zero-Flag gesetzt ist. Damit kann im String nach Elementen gesucht werden, die mit dem Referenzwert nicht übereinstimmen (wenn ein Vergleich ergibt, dass die beiden Operanden identisch sind, dann wird das Zero-Flag gesetzt, ansonsten gelöscht).
Bei REPZ handelt es sich um ein Synonym für REPE.

REPNE/REPNZ

repne cmps{b,w,d}
repne scas{b,w,d}

Hier wird der String-Befehl solange ausgeführt, wie das Register CX ungleich Null sowie das Zero-Flag gelöscht ist. Auf diese Weise wird das erste Stringelement gefunden, das mit dem Referenzwert übereinstimmt.
Auch hier ist REPNZ ein Synonym für REPNE.

Bitmanipulationsbefehle / Bitschiebebefehle

AND

and Ziel, Quelle                        ;allgemeine Form
and reg, reg                            ;mögliche Operandenkombinationen
and mem, reg
and reg, mem
and reg, con
and mem, con
and al/ax/eax,con

Dieser Befehl führt eine AND-Verknüpfung zwischen den Bits der beiden Operanden durch. Die Operandengröße kann 8, 16 oder 32 Bit betragen, muß aber bei beiden Operanden gleich groß sein. Das Ergebnis der Verknüpfung wird im ersten Operanden hinterlegt.

Eine AND-Verknüpfung verläuft nach folgendem Schema: Werden zwei Bits miteinander verknüpft, dann ist das resultierende Bit nur dann gesetzt, wenn beide Bits gesetzt waren. Ist eines oder sind beide Bits gelöscht, dann ist auch das Ergebnisbit gelöscht.

        10010110            11110000            10101010            10011101
    and 01011011        and 00001111        and 01010101        and 10001111
    ============        ============        ============        ============
        00010010            00000000            00000000            10001101

OR

or Ziel, Quelle                         ;allgemeine Form

Dieser Befehl führt eine OR-Verknüpfung zwischen den Bits der beiden Operanden durch. Die Operandengröße kann 8, 16 oder 32 Bit betragen, muß aber bei beiden Operanden gleich groß sein. Das Ergebnis der Verknüpfung wird im ersten Operanden hinterlegt. Die möglichen Operandenkombinationen stimmen mit denen des Befehls AND überein.

Eine OR-Verknüpfung verläuft nach folgendem Schema: Werden zwei Bits miteinander verknüpft, dann ist das resultierende Bit nur dann gesetzt, wenn mindestens eines der beiden Bits gesetzt war. Sind beide Bits gelöscht, dann ist auch das Ergebnisbit gelöscht.

       10010110            11110000            10101010            10011101
    or 01011011         or 00001111         or 01010101         or 10001111
    ===========         ===========         ===========         ===========
       11011111            11111111            11111111            10011111

OR wird häufig verwendet, um bestimmte Bits zu setzen. Wenn Sie sich eine ASCII-Tabelle zur Hand nehmen, dann können Sie nicht nur erkennen, dass die Großbuchstaben kleinere ASCII-Codes als die Kleinbuchstaben haben, sondern auch, daß die Differenz zwischen ASCII- Codes korrespondierender Klein- und Großbuchstaben genau 32 beträgt. Um nun einen Großbuchstaben in einen Kleinbuchstaben zu konvertieren, kann folgender Code verwendet werden:

        mov al,[char]                   ;Zeichen nach AL
        cmp al,65                       ;ist der Buchstabe kleiner als 'A'
        jb NoChange                     ;ja --> keine Umwandlung
        cmp al,90                       ;ist der Buchstabe größer als 'Z'
        ja NoChange                     ;ja --> keine Umwandlung
        or al,32                        ;den Buchstaben hier auf klein stellen
        NoChange:                       ;Sprungmarke, falls keine Umwandlung
        ...

Dieser Code berücksichtigt noch keine Sonderzeichen wie z. B. Umlaute.
Das zu bearbeitende Zeichen befindet sich in der Variablen char, die ins Register AL kopiert wird. Danach wird geprüft, ob sich das Zeichen überhaupt im Bereich der Großbuchstaben befindet. Sollte dies der Fall sein, dann wird durch eine OR-Verknüpfung zwischen AL und dem Wert 32 das Bit 5 in AL gesetzt. Dies ist die schon besprochene Differenz zwischen Groß- und Kleinbuchstaben.

Die obige Konvertierung hätte auch komplett mit der Variablen char durchgeführt werden können (ohne Einbeziehung von AL); die Entscheidung für die Verwendung von AL fiel aus Gründen der Geschwindigkeit: der Prozessor kann schneller auf Werte in seinen Registern als auf den Speicher zugreifen. Das mag bei einem Zeichen nicht viel ausmachen, aber wenn man diese Befehlsfolge verwendet, um einen längeren Text in Kleinbuchstaben zu konvertieren, dann könnte es schon zu einer messbaren Verlangsamung kommen.

XOR

xor Ziel, Quelle                        ;allgemeine Form

Dieser Befehl führt eine XOR-Verknüpfung zwischen den Bits der beiden Operanden durch. Die Operandengröße kann 8, 16 oder 32 Bit betragen, muß aber bei beiden Operanden gleich groß sein. Das Ergebnis der Verknüpfung wird im ersten Operanden hinterlegt. Die möglichen Operandenkombinationen stimmen mit denen des Befehls AND überein.

Eine XOR-Verknüpfung verläuft nach folgendem Schema: Werden zwei Bits miteinander verknüpft, dann ist das resultierende Bit nur dann gesetzt, wenn genau eines der Bits gesetzt war. Sind beide Bits gesetzt oder gelöscht, dann ist auch das Ergebnisbit gelöscht.

        10010110            11110000            10101010            10011101
    xor 01011011        xor 00001111        xor 01010101        xor 10001111
    ============        ============        ============        ============
        11001101            11111111            11111111            00010010

XOR wird häufig verwendet, um Bits zwischen zwei Zuständen umzuschalten. Der Vorteil hierbei ist, dass man den aktuellen Zustand der Bits nicht kennen muß, die Umschaltung erfolgt auf jeden Fall. Nehmen wir an, daß die Zustände true and false durch ein Bit repräsentiert werden, wobei true=1 (gesetzt) und false=0 (gelöscht) bedeuten. Für die Umschaltung zwischen true und false kann man dieses Bit einer XOR-Verknüpfung mit 1 unterziehen:

Fall 1: Wert ist true (Bit=1)
        xor [Wert],1                    ;Ergebnis (Wert) ist 0

Fall 2: Wert ist false (Bit=0)
        xor [Wert],1                    ;Ergebnis (Wert) ist 1

Da die Ergebnisse in den ersten Operanden geschrieben werden, ist Wert nach Ausführung der Befehle umgeschaltet. Die XOR-Verknüpfung mit Null wäre in diesem Fall nur wirksam, wenn Wert=true ist (1 xor 0 = 1). Im anderen Fall gilt: 0 xor 0 = 0 (keine Umschaltung).

Eine Besonderheit der XOR-Verknüpfung, die auch im letzten Beispiel ersichtlich wurde, besteht darin, daß bei einer Verknüpfung zweier Werte die Verknüpfung des Ergebnisses mit dem 2. Wert den ersten Wert ergibt. Ein Beispiel soll dies verdeutlichen:

Wert 1: 10010110b
Wert 2: 01011011b

1. Verknüpfung
        10010110
    xor 01011011
    ============
        11001101                        ;Ergebnis der 1. Verknüpfung

2. Verknüpfung zw. Erg. und 2. Wert
        11001101                        ;Ergebnis der 1. Verknüpfung
    xor 01011011                        ;2. Wert
    ============
        10010110                        ;Ergebnis = 1.Wert

NOT

not Ziel                                ;allgemeine Form
not reg                                 ;mögliche Operanden
not mem

Der Operand dieses Befehls kann 8, 16 oder 32 Bit groß sein. Der Befehl kehrt den Wert jedes Bits im Operanden um, ein gesetztes Bits ist nach Ausführung also gelöscht, ein gelöschtes Bit ist nach Ausführung gesetzt.

        not 10010101=01101010
        not 11110000=00001111
        not 10101010=01010101

SHL

shl Ziel, Anzahl                        ;shift left
shl reg,CL
shl reg,con8
shl mem,CL
shl mem,con8

Für die folgenden Bitschiebebefehle gilt die Annahme, dass das höchstwertige Bit am weitesten links in einem Byte/Word etc steht und die Zählung der Bits mit Null beginnt. Dementsprechend steht in einem Byte das höchstwertige Bit an Position 7, das Bit mit dem geringsten Wert an Position 0.

Beim Befehl SHL werden aus dem ersten Operanden so viele Bits nach links herausgeschoben, wie im zweiten Operanden angegeben. Am rechten Ende werden ebenso viele Bits nachgeschoben, die alle nicht gesetzt sind. Das zuletzt herausgeschobene Bit läßt sich im Carry-Flag finden.

       mov dx,0000010011101010b         ;1258d ins Register DX schreiben
       shl dx, 3                        ;3 Bits nach links herausschieben

Das Ergebnis dieser Operation besteht darin, dass jetzt in DX der Binärwert 0010011101010000b bzw. 10064d steht. Nicht ohne Zufall ist dieser Wert genau 8mal höher als der Wert vor SHL.
Es gilt allgemein: Erg = Quellwert * 2^Anzahl.

SHR

shr Ziel, Anzahl                        ;shift right
shr reg,CL
shr reg,con8
shr mem,CL
shr mem,con8

Verhält sich wie SHL, nur werden hier die Bits nach rechts geschoben.
Es gilt allgemein: Erg = Quellwert / 2^Anzahl.

SAL

sal Ziel, Anzahl                        ;shift arithmetic left
sal reg,CL
sal reg,con8
sal mem,CL
sal mem,con8

Verhält sich genau wie SHL.

SAR

sar Ziel, Anzahl                        ;shift arithmetic right
sar reg,CL
sar reg,con8
sar mem,CL
sar mem,con8

Verschieben der Bits in Ziel um die angegebene Anzahl nach rechts. Dabei wird das höchstwertige Bit (das Vorzeichen) immer erhalten. Also wenn das Vorzeichen-Bit gesetzt war, dann ist es auch nach der Verschiebung noch gesetzt.
Das zuletzt herausgeschobene Bit befindet sich im Carry-Flag.

SHLD

shld Ziel, Quelle, Anzahl               ;shift left with double precision
shld reg16,reg16,con8
shld reg32,reg32,con8
shld mem16,reg16,con8
shld mem32,reg32,con8
shld reg16,reg16,CL
shld reg32,reg32,CL
shld mem16,reg16,CL
shld mem32,reg32,CL

SHLD schiebt die Bits in Ziel um die angegebene Anzahl nach links und fügt an den freiwerdenden Positionen die höchstwertigsten Bits von Quelle ein.
Es werden nur die unteren 5 Bits von Anzahl verwendet (max 32). Erst ab 80386.

SHRD

shrd Ziel, Quelle, Anzahl               ;shift right with double precision
shrd reg16,reg16,con8
shrd reg32,reg32,con8
shrd mem16,reg16,con8
shrd mem32,reg32,con8
shrd reg16,reg16,CL
shrd reg32,reg32,CL
shrd mem16,reg16,CL
shrd mem32,reg32,CL

SHRD schiebt die Bits in Ziel um die angegebene Anzahl nach rechts und fügt an den freiwerdenden Positionen die geringwertigsten Bits von Quelle ein. Es werden nur die unteren 5 Bits von Anzahl verwendet (max 32). Erst ab 80386.

RCL

rcl Ziel, Anzahl                        ;rotate through carry left
rcl reg,CL
rcl reg,con8
rcl mem,CL
rcl mem,con8

Hier werden die Bits eins nach dem anderen nach links herausgeschoben, ins Carry-Flag bugsiert und von dort am rechten Ende wieder eingefügt. Das zuletzt herausgeschobene Bit befindet sich im Carry-Flag.

RCR

rcr Ziel, Anzahl                        ;rotate through carry right
rcr reg,CL
rcr reg,con8
rcr mem,CL
rcr mem,con8

Hier werden die Bits eins nach dem anderen nach rechts herausgeschoben, ins Carry-Flag bugsiert und von dort am linken Ende wieder eingefügt. Das zuletzt herausgeschobene Bit befindet sich im Carry-Flag.

ROL

rol Ziel, Anzahl                        ;rotate left
rol reg,CL
rol reg,con8
rol mem,CL
rol mem,con8

Verhält sich ähnlich wie RCL, nur das die Bits hier nicht den Weg durchs Carry-Flag nehmen, sondern sofort am rechten Ende wieder eingefügt werden. Das zuletzt herausgeschobene Bit befindet sich im Carry-Flag.

ROR

ror Ziel, Anzahl                        ;rotate right
ror reg,CL
ror reg,con
ror mem,CL
ror mem,con

Verhält sich ähnlich wie RCR, nur das die Bits hier nicht den Weg durchs Carry-Flag nehmen, sondern sofort am rechten Ende wieder eingefügt werden. Das zuletzt herausgeschobene Bit befindet sich im Carry-Flag.

TEST

test Ziel, Quelle

Dieser Befehl führt eine logische UND-Verknüpfung der beiden Operanden durch, die die Flags verändern. Das Ergebnis der Verknüpfung wird nicht gespeichert. Dient häufig dazu herauszufinden, ob bestimmte Bits eines Operanden gesetzt sind oder nicht.

BT

bt Quelle, Index                        ;Bit test

Der Index im 2. Operanden zeigt auf ein Bit im ersten Operanden. Der Wert dieses Bits wird im Carry-Flag gespeichert.
Der Befehl existiert seit dem 80386.

BTC

btc Quelle, Index                       ;Bit test and complement

Der Index im 2. Operanden zeigt auf ein Bit im ersten Operanden. Der Wert dieses Bits wird im Carry-Flag gespeichert. Das Bit, auf das der Index verweist, wird komplementiert, also umgekehrt.
Der Befehl existiert seit dem 80386.

BTR

btr Quelle, Index                       ;Bit test and reset

Der Index im 2. Operanden zeigt auf ein Bit im ersten Operanden. Der Wert dieses Bits wird im Carry-Flag gespeichert. Das Bit, auf das der Index verweist, wird gelöscht.
Der Befehl existiert seit dem 80386.

BTS

bts Quelle, Index                       ;Bit test and set

Der Index im 2. Operanden zeigt auf ein Bit im ersten Operanden. Der Wert dieses Bits wird im Carry-Flag gespeichert. Das Bit, auf das der Index verweist, wird gesetzt.
Der Befehl existiert seit dem 80386.

BSF

bsf Ziel, Quelle                        ;Bit scan forward
bsf reg, mem                            ;Ziel kann nur ein Register sein

Der Befehl sucht im zweiten Operanden von Bit 0 beginnend nach dem ersten gesetzten Bit. Wird kein gesetztes Bit gefunden (Quelle = Null), dann wird das Zero-Flag gesetzt und das Ziel ist undefiniert, anderenfalls wird das Zero-Flag gelöscht und in das Ziel wird der Index des ersten gesetzten Bits geschrieben.
Beide Operanden müssen die gleiche Größe haben, die 16 oder 32 Bit betragen kann, die Quelle kann nur ein Register oder eine Speicherstelle sein. Der Befehl existiert seit dem 80386.

BSR

bsr Ziel,  Quelle                       ;Bit scan reverse
bsr reg, mem                            ;Ziel kann nur ein Register sein

Der Befehl sucht im zweiten Operanden vom höchstwertigen Bit beginnend nach dem ersten gesetzten Bit. Wird kein gesetztes Bit gefunden (Quelle = Null), dann wird das Zero-Flag gesetzt und das Ziel ist undefiniert, anderenfalls wird das Zero-Flag gelöscht und in das Ziel wird der Index des ersten gesetzten Bits geschrieben.
Beide Operanden müssen die gleiche Größe haben, die 16 oder 32 Bit betragen kann, die Quelle kann nur ein Register oder eine Speicherstelle sein. Der Befehl existiert seit dem 80386.

Stackbefehle

Worum es sich beim Stack handelt, wurde bereits besprochen. Die Prozessoren der x86-Familie stellen einige Befehle zur Verfügung, mit denen speziell der Stack bearbeitet wird.

Wie Sie wissen, ist der Stack immer im Stacksegment gelagert (das Segment, dessen Adresse im Register SS steht). Das bedeutet, daß alle Stackbefehle implizit auf das Register SS sowie auf das Register SP zugreifen. Im Register SP steht die Adresse der aktuellen Stackspitze (TOS = Top Of Stack).

Bei allen nachfolgenden Befehlen handelt es sich um 16- bzw. 32-Bit-Operationen. Es ist mit diesen Befehlen nicht möglich, Bytes auf den Stack zu kopieren oder von dort zu holen. Es ist natürlich grundsätzlich möglich, auch mit Bytes auf dem Stack zu arbeiten, allerdings kann man sich dabei mehr Probleme einhandeln als einem lieb ist.

PUSH / PUSHW / PUSHD

PUSH lässt sich am ehesten mit schieben oder stoßen übersetzen. Die folgenden Befehle kopieren einen Wert auf den Stack. Die Konsequenz aus diesem Kopiervorgang besteht u. a. darin, daß das Register SP abhängig von der Operandengröße verändert wird.

Bei 16-Bit-Operanden wird vom Inhalt des Registers SP der Wert 2 abgezogen, bei 32-Bit-Operanden der Wert 4. Über die abzuziehenden Werte muß nicht weiter gesprochen werden, es handelt sich um die Größe der Operanden in Bytes. Was vielleicht mehr Verwirrung hervorruft ist die Tatsache, daß die Werte abgezogen und nicht addiert werden, zumal die Redewendung "auf den Stack kopieren/pushen/schieben" berechtigterweise eine Addition vermuten läßt. Allerdings dürfen Sie nicht vergessen, daß der Stack von oben nach unten wächst, also in Richtung kleiner werdender Adressen. Und da SP die Adresse der Stackspitze enthält, muß diese Adresse nach dem kopieren korrigiert werden. Dieser Service wird vom Befehl push gleich mit erledigt.

        push reg16
        push reg32
        push mem16
        push mem32
        push con16                      ;direktes Kopieren erst ab 80286
        push con32

Die 32-Bit-Varianten sind erst ab dem 80386 möglich.

Die OpCodes für die Befehle ermittelt der Assemblierer in aller Regel selbst und mit großer Treffsicherheit. Wird allerdings mit Speicherwerten gearbeitet, dann kann bei bestimmten Adressierungsarten ein Type-Casting nötig werden, das dem Assemblierer den Datentyp mitteilt. Bei Verwendung von Konstanten hängt die Wahl des OpCodes von der Größe der Konstanten ab. Wird also beispielsweise eine Zahl größer als 65536 gepusht, dann wird automatisch der OpCode für 32-Bit-Konstanten verwendet.

Die beiden verbleibenden Befehle pushw und pushd sind Alternativen zum Type-Casting. Die Befehle teilen dem Assemblierer direkt mit, welche Operandengröße verwendet werden soll. Es wäre zum Beispiel möglich, daß Sie den Wert 400 auf den Stack kopieren wollen. Mit einem Befehl wie push 400 wird automatisch der 16-Bit-OpCode verwendet und es gilt: SP := SP - 2. Allerdings ist es möglich, daß eine nachfolgende Operation, z. B. ein Unterprogramm, ein DWord auf dem Stack erwartet und aus genau diesem Grunde ein DWord vom Stack holt (s. nächster Abschnitt über den Befehl pop). Damit würde die gesamte bisherige Struktur des Stack durcheinandergebracht werden. Da auch die nachfolgenden Befehle, die Werte vom Stack holen, mit falschen Resultaten arbeiten, kann ein solcher Vorgang auch zum Systemabsturz führen.

        push 400                        ;kopiert 16-Bit-Wert
        pushd 400                       ;erweitert den Wert auf 32 Bit
        pushw [bx]

Der letzte Befehl bedarf vielleicht einer Erklärung, die ich Ihnen nicht vorenthalten möchte.
Es handelt sich um die sogenannte indirekte Adressierung. Hierbei wird nicht direkt auf eine Adresse zugegriffen, sondern der Umweg über ein Register gegangen, dessen Inhalt als Adresse interpretiert wird. Mit der letzten Anweisung wird auf einen Wert zugegriffen, dessen Offsetadresse durch den Wert im Register BX repräsentiert wird. Als Segmentadresse wird implizit der Wert im Register DS angenommen.

Zur Verdeutlichung:
Mit dem Befehl push bx würde der Inhalt des Registers BX auf den Stack kopiert werden. Mit dem Befehl pushw [bx] wird der Inhalt der Speicherstelle kopiert, auf die die Adresse im Register BX verweist. Da pushw verwendet wurde, wird dieser Wert im Word-Format (16 Bit) auf den Stack gelegt.

TASM ermöglicht es Ihnen, mit nur einem Befehl mehrere Operanden zu pushen, vorausgesetzt sie haben die gleiche Größe.

statt:
        push ax
        push bx
        push si
        push di
lieber:
        push ax bx si di                ;durch Leerzeichen getrennt

POP / POPW / POPD

Dieser Befehl ist die Umkehrung des Befehls push. Mit ihm werden Werte vom Stack geholt und in ein als Operand zu übergebendes Ziel geschrieben.

Abhängig von der Operandengröße wird wieder das Register SP verändert. Bei Word-Operanden gilt SP := SP + 2, bei DWord-Operanden gilt SP := SP + 4.

        pop reg16                       ;außer CS
        pop reg32
        pop mem16
        pop mem32

Werte vom Stack in das Register CS zu kopieren, hätte starke Auswirkungen auf den weiteren Programmverlauf. Da CS die Adresse des Codesegments enthält und über die Registerkombination CS:IP die Adresse des nächsten auszuführenden Befehls festgelegt wird, würde nach dem Kopieren eines Wertes vom Stack ins Register CS an einer u. U. komplett anderen Stelle des Speichers nach Befehlen gesucht werden bzw. würden an anderer Stelle, die durch CS:IP referenziert wird, Befehle ausgeführt werden, die den Rechner mit hoher Wahrscheinlichkeit abstürzen lassen. Riskieren Sie also möglichst nicht, durch übermütiges poppen der Werte Ihren Rechner ins Nirvana zu schicken, selbst dann nicht, wenn Sie wissen, daß eigentlich ein korrekter Wert nach CS kopiert wird.

POPW und popd sind wieder Befehle, die explizit festlegen, daß ihr Operand mit einer bestimmten Größe behandelt werden soll. Wie bei den Befehlen push(w|d) machen diese Befehle nur bei bestimmten Adressierungsarten Sinn.

TASM ermöglicht es Ihnen, mit nur einem Befehl mehrere Werte vom Stack zu holen, vorausgesetzt sie haben die gleiche Größe.

statt:
        pop ax
        pop bx
        pop si
        pop di
lieber:
        pop ax bx si di                 ;durch Leerzeichen getrennt

PUSHA / PUSHAD

pusha
pushad

Diese Befehle pushen die allgemeinen Register sowie die Index- und Pointerregister in der folgenden Reihenfolge auf den Stack: AX, CX, DX, BX, SP, BP, SI, DI. PUSHAD führt die gleiche Operation in der gleichen Reihenfolge für die 32-Bit-Varianten der Register durch.

POPA / POPAD

popa
popad

Diese Befehle sind die Umkehrung der Befehle pusha und pushad. Vom Stack werden Werte entnommen und in umgekehrter Reihenfolge von pusha in die Register geschrieben, also in folgender Reihenfolge: DI, SI, BP, BX, DX, CX, AX. Der Wert für SP wird zwar vom Stack geholt, aber nicht in das Register SP geschrieben. POPAD führt wieder die gleiche Funktion in gleicher Reihenfolge für die 32-Bit-Versionen der Register durch.

PUSHF / PUSHFD

pushf
pusfd

Diese Befehle pushen entweder das 16-Bit-Flagregister (pushf) oder ab dem 80386 das EFlags-Register (pushfd) auf den Stack. Von dort muß es, um es bearbeiten zu können, mit einem pop bzw. popd (bei vorherigem pushfd) in ein Register oder eine Variable geschrieben werden.

POPF / POPFD

popf
popfd

Diese Befehle nehmen 16 bzw. 32 Bit (bei popfd) von der Stackspitze und schreiben sie in das Flagregister bzw ins EFlags-Register.

Sollten Sie das EFlags-Register bearbeitet haben, dann beachten Sie, daß die Bits 16 und 17 nicht verändert werden. Sollten Sie diese Bits also manipuliert haben, so haben die neuen Werte nach einem popfd keine Auswirkung.

Sonstige

SETcc

setcc reg8/mem8                         ;set byte on condition

Der Befehl ist im Grunde eine Variation der bedingten Sprungbefehle. Die/das als Parameter übergebene Speicherstelle/Register wird auf 1 gesetzt, wenn die statt cc angegebene Bedingung zutrifft.

Eine Auswahl bedingter Set-Befehle
Befehl Bedeutung
seta set if above (ohne Vorzeichen)
setae set if above or equal
setb set if below (ohne Vorzeichen)
setbe set if below or equal
setc set if Carry Flag = 1
sete set if equal
setg set if greater (mit Vorzeichen)
setge set if greater or equal (mit Vorzeichen)
setl set if lower (mit Vorzeichen)
setle set if lower or equal (mit Vorzeichen)
setna set if not above
setnae set if not above or equal
setnb set if not below
setnbe set if not below or equal
setnc set if not Carry = 1
setne set if not equal
setng set if not greater
setnge set if not greater or equal
setnl set if not lower
setnle set if not lower or equal
setno set if not Overflow = 1
setnp set if not Parity = 1
setns set if not Sign = 1
setnz set if not Zero = 1
seto set if Overflow Flag = 1
setp set if Parity Flag = 1
setpe set if parity even (Parity = 1)
setpo set if paritiy odd (Parity = 0)
sets set if Sign Flag = 1
setz set if Zero Flag = 1

LAHF

lahf

Dieser Befehl überträgt die unteren 8 Bit des Flagregisters in das Register AH. Folgende Bits sind davon betroffen:

Belegung von AH nach LAHF
Bitnummer 7 6 5 4 3 2 1 0
Flag Sign Zero nicht definiert Auxiliary Carry nicht definiert Parity nicht definiert Carry

SAHF

sahf

Dieser Befehl lädt den Inhalt des Registers AH in die unteren 8 Bit des Flagregisters. Zur Belegung siehe LAHF

.

CALL

call addr

Für nähere Informationen zu diesem Befehl verweise ich auf das Kapitel Unterprogramme - Prozeduren.

CLC

clc                                     ;clear carry flag

Setzt das Carry-Flag auf Null, löscht es also.

STC

stc                                     ;set carry flag
Setzt das Carry-Flag auf Eins.

CLD

cld                                     ;clear direction flag

Setzt das Direction-Flag auf Null, löscht es also. Bei den Stringbefehlen führt das dazu, daß nach Ausführung des Stringbefehls die von diesem Befehl verwendeten Indexregister um die Operandengröße erhöht werden.

STD

std                                     ;set direction flag

Setzt das Direction-Flag auf Eins. Bei den Stringbefehlen führt das dazu, daß nach Ausführung des Stringbefehls die von diesem Befehl verwendeten Indexregister um die Operandengröße verringert werden.

CLI

cli                                     ;clear interrupt-enable flag

Setzt das Interrupt-Enable-Flag auf Null, löscht es also. Ab jetzt werden nur noch NMIs (Non-Maskable Interrupts) zugelassen. Der Befehl wird vor allem verwendet, um in ISRs (Interrupt-Handler) eine ungestörte Abarbeitung zu gewährleisten.

STI

sti                                     ;clear interrupt-enable flag

Setzt das Interrupt-Enable-Flag auf Eins. Die Bearbeitung kann ab jetzt durch Interrupts unterbrochen werden.

CPUID

Der Befehl CPUID wurde erst mit dem Pentium eingeführt, wurde aber dann auch in die späteren Versionen der 80486 eingebaut. Ob ein Prozessor über diesen Befehl verfügt kann man über das ID-Flag (Bit 21) im EFlags-Register ermitteln. Wenn ein Programm dieses Flag verändern kann dann existiert der Befehl auf diesem Prozessor. Dazu muss aber ebenfalls ermittelt werden, ob es überhaupt das erweiterte Flag- Register gibt (sprich: ob man einen 80386 oder höher vor sich hat).

Mit diesem Befehl wurde endlich ein definierter Weg geschaffen, um etwas über die Fähigkeiten des Prozessors zu erfahren. Vorher mussten mehr oder weniger komplizierte Tricks angewandt werden, die teilweise bekannte Prozessorbugs als Informationsquelle ausnutzten. Mit diesem Befehl kann man in der Tat sehr viele Informationen ermitteln. Der Befehl wurde zwar bei allen Pentium-Klonen implementiert, allerdings in unterschiedlichem Umfang und mit unterschiedlichen Ergebnissen.

CPUID ist sozusagen themenorientiert. Das Thema wird dabei vor dem Aufruf ins Register EAX geschrieben.

Funktion 0

Mit dieser Funktion wird der sogenannte Vendor Identification String sowie die höchste gültige Funktionsnummer für diesen Prozessor ausgegeben. Der Identifikationsstring für Intel-Prozessoren lautet "GenuineIntel", bei AMD "AuthenticAMD". Das EAX-Register enthält die höchste Funktionsnummer, die vor dem Aufruf von CPUID in EAX eingegeben werden kann. Beim reinen Pentium sowie den neueren 80486ern wird 01H zurückgegeben, beim Pentium Pro, Pentium II sowie den Celerons eine 02H, der P III kennt 3 Funktionen und der Pentium 4 sowie der Intel XEON werden 02H zurückgeben.

Funktion 1

Funktion 1 gibt in EAX Versionsinformationen über den Prozessor zurück. Die Bits 0-3 enthalten die Stepping ID, Bits 4-7 das Modell, Bits 8-11 die Familie und Bits 12-13 den Prozessortyp. Der Prozessortyp kann die Werte 00B (Original OEM Processor), 01B (Intel Overdrive ® Processor), 10B (Dual Processor) und 11B (Intel reserved) annehmen.

In den Bits 0-7 des Registers EBX wird ein Index in eine Tabelle mit Markenstrings hinterlegt. Die Indizes 4-7 und 9-255 sind reserviert, Index 1 steht für einen Celeron Prozessor, Index 2 für den Pentium III Prozessor, Index 3 für den Pentium III Xeon und und Index 8 für den Pentium 4. Index 0 besagt, dass das Feature der Markenstrings nicht unterstützt wird. Dieses Feld gibt es seit dem Pentium III Xeon.

Bits 8-15 (das 2. Byte) geben die Größe einer Cache Line an, die mit CLFLUSH geschrieben wird. Der Wert muss dazu mit 8 multipliziert werden. Dieses Feld wurde mit dem Pentium 4 eingeführt.

Bits 16-23 geben die Anzahl der logischen Prozessoren je physikalischem Prozessor an. Dies steht im Zusammenhang mit Technologien wie Hyperthreading.

Bits 24-31 ist die ID, die dem lokalen APIC (Advanced Programmable Interrupt Controller; auf dem Prozessor untergebrachter Interrupt Controller) beim Booten zugewiesen wird. Gibt es auch erst seit dem Pentium 4.

Register ECX ist derzeit reserviert.
Register EDX enthält sehr umfangreiche Informationen über Prozessorfeatures.

Bit Beschreibung
0 Der Prozessor beinhaltet eine On-Chip-FPU
1 Erweiterungen für den Virtual 8086 Mode sind vorhanden
2 Erweiterungen fü prozessorgestütztes Debugging sind vorhanden
3 gibt an ob die Pages auch 4 MByte groß werden können (sonst 4 KByte)
4 gibt an, ob der Befehl RDTSC (Read Time Stamp Counter) unterstützt wird
5 die Befehle RDMSR und WRMSR werden unterstützt. MSR = Model Specific Register
6 physikalische Adressen oberhalb von 32 Bit sind möglich; die Anzahl der Zusatzbits ist nicht definiert und prozessorabhängig
7 Machine Check Exceptions sind verfügbar
8 die Instruktion CMPXCHG8B wird unterstützt
9 es befindet sich ein APIC auf dem Prozessor
10 reserviert
11 die Befehle SYSENTER und SYSEXIT sind verfügbar
12 Memory Type Range Register sind vorhanden
13 in Page Directory Entries sowie Page Table Entries ist das Global Bit vorhanden, das angibt, dass die Page global verfügbar ist
14 die Machine Check Architecture wird unterstützt, die ein einheitliches Verfahren für Fehlersuche und -report darstellt. Dazu werden MSRs verwendet.
15 die CMOVXX-Befehle sind verfügbar; ähnlich wie die Sprungbefehle und die Varianten des SET-Befehls bedingen Flagstellungen ein MOV
16 Page Attribute Tables sind vorhanden
17 der Prozessor unterstützt eine weitere Möglichkeit Speicher jenseits der 32 Bit zu adressieren
18 das Feature der Prozessorseriennummer wird unterstützt
19 der Befehl CLFLUSH ist vorhanden
20 reserviert
21 Debug Informationen können in einen Speicherbereich geschrieben werden
22 ACPI wird unterstützt; der Prozessor kann seine eigene Temperatur verfolgen und davon abhängig die Performance anpassen
23 MMX wird unterstützt
24 FXSAVE und FXRSTOR zum Bearbeiten des FPU-Contextes werden unterstützt
25 SSE ist verfügbar
26 SSE2 ist verfügbar
27 Self Snoop; damit kann der Prozessor auf kollidierende Speichertypen reagieren indem der Cache überprüft wird
28 Hyper-Threading ist implementiert
29 im Prozessor ist eine Temperatur-Verfolgungs-Schaltung eingebaut
30 reserviert
31 zeigt an, dass ein bestimmter Pin vorhanden ist, der besagt, dass ein Interrupt auf Verarbeitung wartet

Funktion 2

Diese Funktion gibt Informationen über die Caches sowie die TLBs (Translation Lookaside Buffer) in den Registern EAX, EBX, ECX und EDX aus. Das unterste Byte in EAX gibt an, wie oft die Funktion aufgerufen werden muss, um alle Informationen über Caches und TLBs zu erhalten. Das Bit 31 aller 4 Register gibt an, ob das Register auswertbare Informationen (Bit 31 = 0) enthält oder nicht (Bit 31 = 1). Sobald ein Register auswertbare Informationen beinhaltet, werden die Registerinhalte byteweise betrachtet. Jedes Byte kann einen bestimmten Wert aus der folgenden Tabelle annehmen. Die Reihenfolge der Werte in den Registern ist nicht definiert.

Wert Beschreibung
00H Nulldeskriptor
01H Instruction TLB: 4 KByte Pages, 4-Wege-assoziativ, 32 Einträge
02H Instruction TLB: 4 MByte Pages, 4-Wege-assoziativ, 2 Einträge
03H Data TLB: 4 KByte Pages, 4-Wege-assoziativ, 64 Einträge
04H Data TLB: 4 MByte Pages, 4-Wege-assoziativ, 8 Einträge
06H 1st Level Instruction Cache: 8 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
08H 1st Level Instruction Cache: 16 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
0AH 1st Level Data Cache: 8 KByte, 2-Wege-assoziativ, 32 Byte Line-Größe
0CH 1st Level Data Cache: 16 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
22H 3rd Level Cache: 512 KByte, 4-Wege-assoziativ,Dual-sectored line, 64 Byte Sektor-Größe
23H 3rd Level Cache: 1 MByte, 8-Wege-assoziativ,Dual-sectored line, 64 Byte Sektor-Größe
40H Kein 2nd Level Cache, oder, falls der Prozessor einen gültigen 2nd Level Cache besitzt, dann kein 3rd Level Cache
41H 2nd Level Cache: 128 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
42H 2nd Level Cache: 256 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
43H 2nd Level Cache: 512 KByte, 4-Wege-assoziativ, 32 Byte Line-Größe
44H 2nd Level Cache: 1 MByte, 4-Wege-assoziativ, 32 Byte Line-Größe
45H 2nd Level Cache: 2 MByte, 4-Wege-assoziativ, 32 Byte Line-Größe
50H Instruction TLB: 4 KByte und 2 MByte oder 4 MByte Pages, 64 Einträge
51H Instruction TLB: 4 KByte und 2 MByte oder 4 MByte Pages, 4-Wege-assoziativ, 128 Einträge
52H Instruction TLB: 4 KByte und 2 MByte oder 4 MByte Pages, 4-Wege-assoziativ, 256 Einträge
5BH Data TLB: 4 KByte und 4 MByte Pages, 4-Wege-assoziativ, 64 Einträge
5CH Data TLB: 4 KByte und 4 MByte Pages, 4-Wege-assoziativ, 128 Einträge
5DH Data TLB: 4 KByte und 4 MByte Pages, 4-Wege-assoziativ, 256 Einträge
66H 1st Level Data Cache: 8 KByte Pages, 4-Wege-assoziativ, 64 Byte Line-Größe
67H 1st Level Data Cache: 16 KByte Pages, 4-Wege-assoziativ, 64 Byte Line-Größe
68H 1st Level Data Cache: 32 KByte Pages, 4-Wege-assoziativ, 64 Byte Line-Größe
70H Trace Cache: 12K-microOP, 8-Wege-assoziativ
71H Trace Cache: 16K-microOP, 8-Wege-assoziativ
72H Trace Cache: 32K-microOP, 8-Wege-assoziativ
79H 2nd-level cache: 128 KB, 8-Wege-assoziativ, Dual-sectored Line, 64 Byte Sektor-Größe
7AH 2nd-level cache: 256 KB, 8-Wege-assoziativ, Dual-sectored Line, 64 Byte Sektor-Größe
7BH 2nd-level cache: 512 KB, 8-Wege-assoziativ, Dual-sectored Line, 64 Byte Sektor-Größe
7CH 2nd-level cache: 1 MB, 8-Wege-assoziativ, Dual-sectored Line, 64 Byte Sektor-Größe
82H 2nd-level cache: 256 KB, 8-Wege-assoziativ, 32 Byte Line-Größe
83H 2nd-level cache: 512 KB, 8-Wege-assoziativ, 32 Byte Line-Größe
84H 2nd-level cache: 1 MByte, 8-Wege-assoziativ, 32 Byte Line-Größe
85H 2nd level cache: 2 MByte, 8-Wege-assoziativ, 32 Byte Line-Größe

Funktion 80000000H

Seit dem Pentium 4 kennt Intel auch die Erweiterten Funktionen, mit denen AMD schon länger arbeitet. Die maximale Anzahl an erweiterten Funktionen ermitteln Sie, indem Sie CPUID mit dem Wert 80000000H in EAX aufrufen. Sollte der Prozessor keine erweiterten Funktionen unterstützen so wird automatisch die höchste verfügbare Basisfunktion ausgeführt. Ansonsten wird in EAX sowohl für den Pentium 4 als auch den Intel Xeon 80000004H ausgegeben. Die Register EBX, ECX und EDX sind reserviert.

Funktion 80000001H

Diese Funktion gibt erweiterte Informationen zur Prozessorsignatur sowie zu Feature Bits zurück. Alle Register sind zur Zeit reserviert (nicht definiert).

Funktionen 80000002H - 80000003H

Wenn diese Funktionen in aufsteigender Reihenfolge nacheinander aufgerufen werden, dann werden in den Registern EAX, EBX, ECX und EDX 16 Byte (je Aufruf) hinterlegt, die, miteinander verbunden, den sogenannten Brand String ergeben (nicht zu verwechseln mit der Funktion 01H). Insgesamt kann dieser Brand String 48 Bytes lang werden, wobei das Byte 48 per Definition den Wert Null hat. Der Brand String ist rechts ausgerichtet, notfalls werden Leerzeichen vorangestellt. Sollte der Brand String eine MHz-Angabe enthalten, dann wird dadurch die von Intel sichergestellte maximale Taktfrequenz angegeben, nicht die aktuell geltende Taktfrequenz.