Adok's Way to Assembler
Folge 4

+++ Flag-Register +++

Das   Flag-Register,  die  Steuerzentrale  des   Computers,  ist  das  einzige
Prozessorregister, das nicht ber den MOV-Befehl angesprochen werden kann. Wie
man es anspricht, werden wir noch spter erfahren.

Das  Flag-Register  ist  16 Bit breit,  wobei  jedes  einzelne Bit eine eigene
Funktion   erfllt.  In  dieser  Kursfolge  werden  wir  folgende  zwei  Flags
bentigen:

- Bit 0 - Carryflag:  Dieses Flag wird u.a. dann automatisch gesetzt, wenn bei
                      einer  mathematischen  Operation   ein  ber-  oder  ein
                      Unterlauf entsteht. -> Das Carryflag wird gesetzt,  wenn
                      die   Quelle   eines  CMP-Befehls - wie  wir  ihn   bald
                      kennenlernen werden - grer als das Ziel ist.
- Bit 6 - Zeroflag:   Ist das Ergebnis eines Befehls gleich 0,  so wird dieses
                      Bit automatisch gesetzt. -> Dieses Flag ist wichtig,  um
                      die Funktionsweise des JE-Befehls, welchen wir ebenfalls
                      bald kennenlernen werden, zu verstehen!

Weitere  Flags, die wir nicht unmittelbar  brauchen werden, aber ganz ntzlich
sind:

- Bit 8 - Trapflag:   Ist dieses Flag gesetzt, wird  nach  jedem  ausgefhrten
                      Befehl  INT 1  ausgelst. (Das machen sich auch Debugger
                      zunutze, um nach jedem Befehl  das  Programm  zu  unter-
                      brechen und dem Benutzer/Hacker den aktuellen Status der
                      Register und des Programms anzuzeigen.)
- Bit 9 - Interrupt-Enable-Flag: Dieses Flag lt  sich  mit  dem  Befehl  STI
                      setzen  und  mit CLI lschen. Ist es nicht gesetzt (also
                      gleich 0), sind alle Interrupts  auer  den  sogenannten
                      NMIs (Non-Maskable-Interrupts, zu deutsch: nichtmaskier-
                      bare Unterbrechungen) deaktiviert.
- Bit 10 - Directionflag: Bei sogenannten String-Befehlen wird  angezeigt,  ob
                      ein  Block  in  aufsteigender (Flag=0) oder absteigender
                      (Flag=1) Reihenfolge abgearbeitet werden soll,  also das
                      Register,  das  auf das aktuelle Element zeigt, jedesmal
                      erhht oder erniedrigt werden soll. Zu diesem Zweck lt
                      sich das Directionflag  auch  vom  Programmierer  selbst
                      direkt  setzen & lschen,  und zwar mit den Befehlen STD
                      und CLD.

Der Vollstndigkeit halber erwhne ich auch die weniger interessanten Flags:

- Bit 2 - Parityflag: Hat das Ergebnis einer Operation eine gerade Anzahl  von
                      gesetzten  Bits,  so  ist  dieses Flag gesetzt. Wird von
                      vielen DF-Programmen bei der  CRC-Prfung der seriellen
                      Schnittstelle verwendet.
- Bit 4 - Auxiliary-Carryflag: Entspricht dem Carryflag, wird  aber  nur  dann
                      gesetzt,  wenn  man  mit  sogenannten BCD-(Binary-Coded-
                      Decimals)-Zahlen arbeitet.  BCD-Zahlen werden allerdings
                      kaum verwendet, weil sie ziemlich speicherintensiv sind.
- Bit 7 - Signflag:   Entspricht dem hchstwertigen Bit  des  Ergebnisses  der
                      letzten Operation. (Das Signflag wird vor allem bei vor-
                      zeichenbehafteten Zahlen bentigt. Dort gibt nmlich das
                      hchste  Bit  das  Vorzeichen an (1=minus, 2=plus). Dies
                      ist auch der Grund, warum in  allen  Programmiersprachen
                      signed-Datentypen  einen  kleineren Hchstwert haben als
                      unsigned-Datentypen. In  diesem  Kurs  werden  wir  aber
                      wahrscheinlich   nicht  nher  auf   vorzeichenbehaftete
                      Zahlen eingehen.)
- Bit 8 - Overflowflag: Ist  ebenfalls   nur  dann   interessant,   wenn   mit
                      vorzeichenbehafteten  Zahlen  gearbeitet  wird. (Rechnet
                      man bspw. 60 + 70, so ergibt dies 130.  Damit  wird aber
                      das hchste Bit  des  Ergebnis-Bytes  gesetzt,  das  als
                      Vorzeichen betrachtet zu einem falschen Ergebnis  fhren
                      wrde.  Deshalb  wird  in solchen Fllen automatisch das
                      Overflowflag gesetzt.)

+++ Vergleiche in Assembler +++

Wozu  nun  das Ganze? Nun, in ASM  gibt es keine IF/THEN-Konstrukte wie in den
Hochsprachen.  Stattdessen existiert ein ganz besonderer Befehl, CMP (nicht zu
verwechseln mit CP/M :-)) ). Syntax:

CMP Ziel,Quelle

Ziel  und Quelle sind die zu  vergleichenden Werte, also Zahlen, Register oder
Speicherstellen. Wie arbeitet CMP? Ganz einfach: CMP zieht die Quelle vom Ziel
ab,  wobei im Gegensatz zum "richtigen" Subtraktionsbefehl, SUB, die Regs bzw.
Speicherstellen  nicht verndert werden. Das Geniale  an der Sache ist nun: Je
nachdem,  welches  Ergebnis bei der  Subtraktion herauskommt, werden bestimmte
Flags gesetzt oder gelscht!

- Sind  Ziel  und  Quelle identisch, ist das Ergebnis gleich 0 - also wird das
  Zeroflag gesetzt. Andernfalls wird das Zeroflag gelscht.
- Ist die Quelle grer als das Ziel, entsteht ein Unterlauf - also  wird  das
  Carryflag gesetzt. Andernfalls wird das Carryflag gelscht.

Und  jetzt  kommt's:  Es  gibt  verschiedene  bedingte  Sprungbefehle, die auf
bestimmte Register reagieren.

- JE und JZ:   Der Sprung zum angegebenen Label wird  nur  dann  durchgefhrt,
               wenn das Zeroflag gesetzt ist.
               -> Dieser  Sprungbefehl  wird  verwendet,  wenn  man berprfen
                  will, ob zwei Werte identisch sind.
- JNE und JNZ: ...wenn das Zeroflag nicht gesetzt ist.
               -> ...ob zwei Werte ungleich sind.
- JA:          ...wenn weder das Carry- noch das Zeroflag gesetzt ist.
               -> ...ob das Ziel grer als die Quelle ist.
- JB:          ...wenn das Carryflag gesetzt ist.
               -> ...ob die Quelle grer als das Ziel ist.

Hngt  man  an  JA bzw. JB noch ein  E  dran  (JAE, JBE), so wird aus "grer"
"grer oder gleich".

Nun habe ich noch zwei HOT TIPS fr euch! :-)

- Wenn man prfen will, ob das CX-Register gleich 0 ist,  kann  man  sich  CMP
  ersparen! Der Befehl JCXZ fhrt einen Sprung aus, wenn CX=0.
- Will  man  prfen,  ob ein Wert gleich 0 ist, so mte man nach dem, was wir
  gelernt haben, schreiben:

  CMP Wert,0
  JE Label

  Es geht aber auch so:

  OR Wert,Wert
  JE Label

  Hiermit wird der angegebene Wert mit sich selbst OR-verknpft.  Dadurch wird
  der  Wert  nicht  gendert, aber, wenn der Wert 0 ist, das Zeroflag gesetzt.
  Statt OR kann man auch AND oder TEST schreiben. Alle drei Mglichkeiten sind
  um einige Taktzyklen schneller als CMP.

Folgendes Beispielprogramm demonstriert die Verwendung des CMP-Befehls.

MODEL TINY               ;Fr COM-Files
CODE SEGMENT             ;Beginn Code-Seg
ASSUME CS:CODE,DS:CODE   ;CS und DS zeigen auf Code-Seg
ORG 100h                 ;Startadresse COM
start:                   ;Startlabel
 JMP begin               ;Sprung zu Label begin
 wert1 DB 10             ;Variable wert1
 wert2 DB 0              ;Variable wert2
 text1 DB "Werte gleich$";Meldung 1
 text2 DB "Wert1 grer$";Meldung 2
 text3 DB "Wert2 grer$";Meldung 3
begin:                   ;Beginn des Proggys
 MOV BH,BYTE PTR wert2   ;Wert 2 auf BH
 CMP wert1,BH            ;Werte vergleichen
 JNE ungleich            ;Wenn ungleich -> Label ungleich
 MOV DX,OFFSET text1     ;Ansonsten Meldung 1
 JMP ausgabe             ;Sprung zu Label ausgabe
ungleich:                ;Wenn ungleich...
 JB w2groesser           ;Wenn W2 grer -> Label w2groesser
 MOV DX,OFFSET text2     ;Ansonsten Meldung 2
 JMP ausgabe             ;Sprung zu Label ausgabe
w2groesser:              ;Wenn Wert 2 grer...
 MOV DX,OFFSET text3     ;Meldung 3
ausgabe:                 ;Meldung ausgeben
 MOV AX,0900h            ;Funkt. 9
 INT 21h                 ;String ausgeben
 MOV AX,4C00h            ;Funkt. 4Ch
 INT 21h                 ;Programm beenden
CODE ENDS                ;Ende Code-Seg
END start                ;Ende des Proggys

Um  deutlich  zu machen, da CMP sowohl  mit  Registern als auch mit Speicher-
stellen arbeiten kann, ist in diesem Programm der eine Parameter ein Register,
der  andere eine Speicherstelle. Das Programm  vergleicht nun die beiden Werte
und  gibt  aus, ob sie gleich sind  oder  welcher der beiden grer ist. Setzt
einfach  in  die  Variablendefinitionen andere  Zahlen  ein und compiliert das
Programm  neu,  um  zu sehen, welche  Auswirkungen  dies hat. Spielt euch auch
herum,  probiert,  andere  Sprungbefehle zu verwenden  -  solange, bis ihr die
Funktionsweise eines jeden Sprungbefehls versteht.

+++ Stack +++

Der  Stack (Stapel, "Kellerspeicher") ist eine besondere Art von Datensegment,
das  mit  den Befehlen PUSH und POP  angesprochen  wird. Das Register SS zeigt
immer  auf  das Segment des Stacks und  das Register SP (Stackpointer) auf die
aktuelle Position im Stapel.

Nehmen  wir an, SP zeige auf den Offset 1000. Nun schreiben wir PUSH AX. Damit
wird  der  Inhalt  von  AX auf  den  Offset  1000 im Stacksegment geschrieben.
Gleichzeitig wird dabei der Inhalt von SP um zwei - denn AX ist ein Word, also
2  Bytes  gro - erniedrigt. Jetzt schreiben  wir PUSH BX. Nun wird der Inhalt
von  BX auf den Offset 998 im Stacksegment geschrieben und der Stackpointer um
zwei weitere Bytes erniedrigt. Er zeigt nun also auf den Offset 996.

Mit dem Befehl POP lassen sich Werte vom Stapel zurckholen. Dabei mu berck-
sichtigt  werden: Das Ganze arbeitet nach  dem sogenannten LIFO-Prinzip - Last
In, First Out. Das bedeutet: Der zuletzt gePUSHte Wert wird als erster gePOPt.
Wenn  man  den Stack mit einem  schmalen Keller vergleicht, wird man erkennen,
da  es  ja ganz logisch ist: Nehmen wir  an, ich werfe einen Fernseher in den
Keller,  danach  eine Waschmaschine. Wenn ich  wieder  einen Gegenstand zu mir
nehmen  will,  mu  ich  zuerst die  Waschmaschine  -  das als letztes hinein-
geworfene  Objekt - nehmen, dann den Fernseher (abgesehen davon, da der Fern-
seher  sowieso  schon beschdigt sein wird :-)  ). Genauso verhlt es sich mit
den Werten am Stack. Schreibt man nun z.B. POP AX, so wird der letzte Wert vom
Stack geholt, in AX geschrieben und der Stackpointer um zwei erhht. Dabei mu
betont  werden, da der Wert nicht vom Stack gelscht wird! Er bleibt noch auf
der  Speicherstelle,  auf  die er gePUSHt  wurde  -  lediglich zeigt jetzt der
Stackpointer  auf  ein anderes Element. Und wenn  wir ein Word POPen, z.B. POP
CX, wird SP wiederum um zwei erhht. Somit zeigt er in unserem Beispiel wieder
auf den Offset 1000.

Drei  wichtige  Dinge, die beim Arbeiten  mit  dem Stack bercksichtigt werden
mssen:

- PUSH  und POP  funktionieren  nur 16-bittig!  PUSH BL bspw. wrde also nicht
  funktionieren.
- Die  Anzahl  der  PUSHs und der POPs mssen einander ausgleichen, so da der
  Stackpointer am Schlu wieder auf  den  Offset  zeigt,  auf  den  er  vorher
  gezeigt hat.
- Verwendet  man den Stack in COM-Dateien,  so zeigt das SS-Register natrlich
  auf das Codesegment und der Stackpointer auf das letzte Byte im Codesegment,
  nmlich  CS:FFFFh.  Normalerweise  ist  dies  unproblematisch. Problematisch
  wird es nur dann, wenn das Programm so gro ist, da beim PUSHen die letzten
  Befehle berschrieben werden. Also Vorsicht!

So,  das  war's fr heute! In der  nchsten Folge geht's weiter, und natrlich
wnsche ich euch auch diesmal bis dahin viel Spa! Adok!
