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.