Adok's Way to Assembler
Folge 5
Willkommen zur fünften Folge des Assembler-Kurses! Diesmal geht's um das
Ansprechen des Flag-Registers, einige weitere Stack-Befehle, Bitverknüpfungen
und Shift-Befehle (wie ihr sehen werdet, in einem fließenden Übergang).
Außerdem gibt's ein bißchen was (nämlich alles :) ) zum Thema Ports.
+++ PUSHF und POPF +++
Wie kann man das Flag-Register verändern? Zum einen gibt es Befehle wie STI,
CLI, STD, CLD etc. Damit lassen sich jedoch nur bestimmte Flags verändern. Was
ist, wenn man andere Flags oder mehrere auf einmal verändern will? Das läßt
sich leicht mit Hilfe des Stacks realisieren. Der Befehl PUSHF sichert das
Flag-Register auf den Stack, der Befehl POPF holt einen Wert vom Stack und
setzt das Flag-Register auf diesen Wert. "Aha!" wird bei manchen von euch wohl
schon ein Licht aufgehen! Wenn man das Flag-Register auf einen bestimmten Wert
setzen will, so kann man diesen Wert auf den Stack sichern und mit POPF in das
Flag-Register schreiben. Das würde im Programm so aussehen:
PUSH wert
POPF
Wie ihr seht, ist es also kein großes Problem, das Flag-Register zu verändern!
Allerdings wird bei dieser Methode das Flag-Register überschrieben. Was ist,
wenn man nur einzelne Bits verändern, die anderen gleich lassen will? Das
würde grob so aussehen:
;Hole Flags in AX:
PUSHF
POP AX
;nun schreibt man die Befehle, um die Flags (=Bits) zu verändern
;danach wird das Flag-Register gesetzt:
PUSH AX
POPF
Welche Befehle benötigt man, um die Bits zu verändern? Richtig, die
Bitverknüpfungen. Für diejenigen, die zuvor noch in keiner anderen Sprache
programmiert haben oder die Bitverknüpfungen nicht kennen, folgt ein kleiner
Exkurs.
+++ Funktionsweise der Bitverknüpfungen +++
- Die einfachste Verknüpfung ist die NOT-Verknüpfung. Sie invertiert die Bits
eines Bytes oder eines Words. Beispiel: Nehmen wir an, AH enthalte den Wert
11001010b. Nach der Anweisung NOT AH beträgt AH 00110101b. (Das kleine b
kennzeichnet in Assembler Binärzahlen.)
- Die AND-Verknüpfung (bitweises UND) erwartet bereits zwei Parameter. Das
Ziel wird mit der Quelle AND-verknüpft. Das Ergebnis der Verknüpfung wird
wiederum in das Ziel geschrieben. Wie funktioniert die AND-Verknüpfung? Ich
zeige es euch an einer Wertetabelle:
1100b
AND 1010b
ergibt 1000b
Das heißt, es muß sowohl in der Quelle als auch im Ziel an der jeweiligen
Position ein Bit gesetzt sein, damit auch im Ergebnis ein Bit gesetzt wird.
Ansonsten wird das Bit im Ergebnis gelöscht. Anschließend wird das Ergebnis
in das Zielregister bzw. in die Zielspeicherstelle geschrieben.
- Die OR-Verknüpfung (bitweises ODER) erwartet ihre Parameter wie AND, hat
jedoch eine andere Wertetabelle:
1100b
OR 1010b
ergibt 1110b
Das heißt, es muß sowohl in der Quelle als auch im Ziel an der jeweiligen
Position ein Bit gelöscht sein, damit auch im Ergebnis ein Bit gelöscht
wird. Ansonsten wird das Bit im Ergebnis gesetzt.
- Last but not least lautet die Wertetabelle der XOR-Verknüpfung (bitweises
ENTWEDER-ODER):
1100b
XOR 1010b
ergibt 0110b
Es wird also nur dann ein Bit an einer bestimmten Position gesetzt, wenn es
ENTWEDER im Ziel ODER in der Quelle gesetzt ist. Wenn jedoch Quelle und Ziel
an derselben Stelle das gleiche Bit haben, wird es im Ergebnis gelöscht!
(Nun wissen wir auch schon, warum XOR reg,reg das Reg löscht. Verknüpfe ich
einen Wert mit sich selbst, so haben Quelle und Ziel auf jeder Position das
gleiche Bit.)
So, wie läßt sich dieses Wissen anwenden? Wie lassen sich einzelne Bits setzen
oder löschen? Ganz einfach!
- Bits lassen sich mit der OR-Verknüpfung setzen. Wollen wir beispielsweise
Bit 3 im AL-Register setzen und die anderen Bits unverändert lassen, so
schreiben wir:
OR AL,00001000b
(Damit keine Mißverständnisse entstehen: Bits werden von rechts nach links
gezählt. Von rechts nach links kommt zuerst Bit 0, dann Bit 1 etc.)
Warum ist das so? Nun, die Nullen bewirken bei OR, daß die Bits nicht
verändert werden. Und auf die Position, bei der wir das Bit auf 1 setzen
wollen, schreiben wir 1. Damit wird das Bit auf jeden Fall gesetzt, egal,
welchen Wert es vorher enthielt. (... Verwirrung? Tja, den meisten ASM-
Neulingen ist es bei diesem Thema nicht anders gegangen, falls sie nicht
schon vorher die boolesche Algebra beherrschten. Seht euch einfach die
Wertetabellen noch einmal an und denkt nach.)
- Mit AND lassen sich Bits wieder löschen. Hier geht's genau umgekehrt wie bei
OR. Das heißt, wenn wir Bit 3 von AL löschen wollen, schreiben wir:
AND AL,11110111b
Nach genauerem Überlegen ist es doch logisch, oder? Bei AND bewirken die
Einsen, daß die Bits nicht gelöscht werden, und die Nullen löschen die Bits
auf einen Schlag.
Hier nun ein Beispiel, wie man das Zeroflag (Bit 6) löschen kann:
PUSHF
POP AX
AND AX,1111111110111111b
PUSH AX
POPF
Und gesetzt wird es mit:
PUSHF
POP AX
OR AX,1000000b
PUSH AX
POPF
OK, so weit, so gut... Wie gesagt, falls etwas nicht klar ist, Wertetabelle
ansehen oder, wenn's wirklich nicht anders geht, mich fragen.
+++ Shift-Befehle +++
Shift-Befehle, auch Bitschiebeoperatoren genannt, sind eine schnelle Form der
Multiplikation. Mit
SHL Reg,Anzahl
wird ein Register um die angegebene Anzahl von Bits nach links verschoben,
also praktisch mit 2 hoch Anzahl multipliziert. Ebenso gibt es SHR, um Bits
nach rechts zu verschieben, also zu dividieren! Diese beiden Befehle sind
wesentlich schneller als die universellen Arithmetikoperationen, die wir
später kennenlernen werden.
Und SHL kann uns außerdem bei noch etwas helfen! Nehmen wir an, wir wollen
eine Routine schreiben, die ein bestimmtes Bit eines Registers - sagen wir, AX
- setzt. Im Gegensatz zu obengenannten Routinen wissen wir jedoch nicht im
voraus, welches Bit gesetzt werden soll, sondern nur, daß die Nummer des Bits
in der Byte-Variablen bitnr zu finden sei. Wie erstellt man daraus die Maske
für den OR-Befehl? Ganz einfach: Mit Hilfe von SHL. Und zwar so:
MOV BX,1 ;Gesetztes Bit in BX
MOV CL,BYTE PTR bitnr;Nummer der Position holen
SHL BX,CL ;Gesetztes Bit auf richtige Position schieben
Nehmen wir als Beispiel an, wir wollen Bit 3 setzen. Also muß bitnr gleich 3
sein. Nun wird Bit 0 in BX gesetzt und in CL geschrieben, welche Position
gesetzt werden soll - also, um welche Anzahl von Bits BX nach links verschoben
werden soll. Als nächstes SHL - tralalalala, wir haben die Maske erstellt. BX
ist nun gleich 00001000b, und mit OR AX,BX führen wir die Bitverknüpfung
durch.
Beim Löschen eines Bits wird es ein wenig komplizierter. Zuerst führen wir
obige drei Befehle durch, die schon beim Setzen eines Bits nötig sind. Damit
AND korrekt arbeitet, müssen wir jedoch noch die Bits invertieren. Nichts
leichter als das! Wir schreiben:
NOT BX
Dann ist BX gleich 11110111b, und die Sache hat sich erledigt! AND AX,BX, und
wir sind fertig.
Zum Schluß noch einige Worte zu den Themen Stack, Shift und AND:
- Wer den Stack in EXE-Dateien verwenden will, benutzt am besten am Anfang
seines Programms die vereinfachte Segmentanweisung
.STACK Groesse
Wenn ihr also z.B. .STACK 255 schreibt, wird ein 255 Byte großes
Stacksegment definiert, das ihr alsgleich mit PUSH und POP ansprechen könnt.
Um das ASSUME-Zeug braucht ihr euch nicht zu kümmern, das erledigt der
Assembler automatisch.
Ebenso existieren für Code- und Datensegment die vereinfachten
Segmentanweisungen .CODE und .DATA. Die Größe muß hierbei nicht angegeben
werden, Punkt vor .CODE und .DATA aber nicht vergessen! Wenn ihr außerdem
am Anfang eures Programms die ASM-Direktive (Direktiven sind Befehle, die
den Assembler bzw. den Compiler beeinflussen) DOSSEG schreibt, werden die
Segmente automatisch in einer bestimmten Reihenfolge angeordnet, die von den
meisten DOS-Programmen verwendet wird.
- Der Parameter Anzahl bei den Shift-Befehlen funktioniert erst ab einem
80286-Prozessor. Wollt ihr, daß eure Programme auch auf älteren Systemen
laufen, so müßt ihr statt SHL AX,6 sechsmal hintereinander SHL AX schreiben.
Und unsere Bitsetz- und -löschroutinen müssen umgeschrieben werden:
MOV BX,1 ;Gesetztes Bit in BX
XOR CH,CH ;CH löschen
MOV CL,BYTE PTR bitnr;Nummer der Position holen
JCXZ shiftlbl2 ;Wenn CX=0 -> nicht shiften
shiftlbl1:
SHL BX ;Um eine Position nach links shiften
DEC CL ;CL erniedrigen
OR CL,CL ;CL=0?
JNE shiftlbl1 ;Wenn nein, zu Label 1
shiftlbl2:
Danach kann ganz normal geORt oder mit Hilfe von NOT geANDet werden.
- Der Befehl TEST führt auch eine AND-Verknüpfung durch, läßt jedoch das Ziel
unverändert. TEST ist also für AND das, was CMP für SUB ist. Wozu TEST gut
ist? Try it out! Es läßt sich mit Hilfe von TEST und Zeroflag abfragen, ob
ein Bit gesetzt oder gelöscht ist.
+++ OUT und IN +++
Mit OUT und IN lassen sich die Ports ansprechen. Mit OUT wird ein Port auf
einen Wert gesetzt, mit IN ein Port ausgelesen. Die Nummer des Ports muß in DX
angegeben werden, der Wert, der geOUTet oder in den das Ergebnis von IN
geschrieben werden soll, in AL. Dann schreibt man OUT DX,AL bzw. IN AL,DX -
und schon hat man getan, was man tun wollte.
Sonderformen der Portbefehle: Wird statt AL AX verwendet, so können gleich
zwei Ports beschrieben werden, wobei der Port mit der Nummer aus DX den Wert
von AH zugewiesen bekommt und der mit der Nummer DX+1 den Wert von AL.
Bestimmte Ports können außerdem direkt angesprochen werden, also OUT Nummer,AL
etc. Es handelt sich um jene Ports, deren Nummern in ein Byte passen.
Mit Hilfe der Portbefehle kann man mit der Hardware des PCs (Grafikkarte,
Schnittstellen etc.) in Kontakt treten. Fast jedes Demo muß von den
Portbefehlen Gebrauch nehmen.
So, das war diese Folge des Kurses. Viel Theorie, wenig Praktisches, und die
Theorie findet teilweise auch gar keine praktische Verwendung. Dennoch mußte
es sein. Jetzt haben wir wenigstens die absoluten Grundtechniken der
Assembler-Programmierung durch (das, was jeder Coder im Kopf haben
muß/sollte). In der nächsten Folge werden wichtige Befehle besprochen, und
danach könnten wir uns vielleicht noch mit fortgeschrittenen Techniken
beschäftigen. Seid froh! Euer Adok.