Adok's Way to Assembler
Folge 3

Begleit-Dateien:

+++ Variablen gibt es auch in Assembler! +++

Was  sind  Variablen?  Unsere Mathelehrer  würden  auf  diese Frage antworten:
"Platzhalter  für  bestimmte Werte." In Assembler  muß man das ein klein wenig
anders  definieren. Variablen sind ganz  normale Speicherstellen, nur verbirgt
sich  ihr Offset hinter einem  "Decknamen", auch Variablenname oder Bezeichner
genannt.

Letztes Mal lief uns einmal der Befehl MOV BYTE PTR 1000h,AL über den Weg. Mit
solchen  Anweisungen  läßt  sich  eine  beliebige Speicherstelle manipulieren.
JEDOCH  muß  hier  der  Offset - in  unserem  Beispiel  1000h  - als Konstante
angegeben  werden  und  daher  schon beim  Coden  bekannt  sein. Hier schaffen
Variablen  Abhilfe!  Ein Befehl wie MOV BYTE PTR variable,AL bewirkt,  daß der
Speicherstelle,   auf  die  die  Variable  zeigt,  der  Wert  eines  Registers
zugewiesen  wird.  Und  hier braucht sich  der  Coder  nicht darum zu kümmern,
welchen Offset die Speicherstelle hat - beim Compilieren ersetzt der Assembler
automatisch die Variable durch den konstanten Offset.

+++ Variablendefinition +++

Cool!  Doch halt, eines fehlt noch! Bevor eine Variable verwendet werden kann,
muß  sie natürlich zuerst definiert werden. Das geht nach dem Schema "Name Typ
Inhalt".

Was  'Name'  bedeutet, erklärt sich wohl von  selbst.  Bei 'Typ' wird es schon
etwas komplizierter. Es gibt mehrere, verschiedene Typen. Die wichtigsten sind
DB und DW.

DB reserviert eine Speicherstelle mit der Länge von einem Byte. Das entspricht
   einer Zahl von 0 bis 255 bzw. einem ASCII-Zeichen. Sollten im 'Inhalt' (wie
   im folgenden Beispielprogramm, deshalb will  ich's  ja  jetzt  sagen  ;)  )
   mehrere  Zeichen,  also ein sogenannter 'String', stehen, werden einfach so
   viele Bytes reserviert, wie der String Zeichen hat.
DW reserviert ZWEI Speicherstellen mit der Länge von ZWEI  Bytes,  also  einem
   Word. Das entspricht einer Zahl von 0 bis 65535 bzw. ZWEI ASCII-Zeichen.

Last  but'nt  least kann man mit  'Inhalt' die Speicherstelle gleich auf einen
Anfangswert  setzen, ohne ihn extra mit MOV  zuweisen zu müssen. Der Wert kann
dabei  entweder als Zahl oder als Zeichen angegeben werden. (Wird der Wert als
Zeichen  angegeben, so muß man  ihn zwischen Anführungszeichen oder Hochkommas
setzen.)  Will man von dieser Option keinen Gebrauch machen, setzt man einfach
ein  Fragezeichen  hin. Habe ich das  lieb ausgedrückt? Also, ein Beispiel für
eine Variablendefinition: vary DB ? (das Fragezeichen gehört dazu!)

Ebenso  lassen  sich mit dem DB/DW-Befehl  Arrays  definieren. Schreibt man DB
Anzahl DUP(?), wobei Anzahl für die Anzahl der Bytes, welche reserviert werden
sollen,  steht,  wird...  äh, tja, diese  Anzahl  von Bytes reserviert. :) Das
Fragezeichen  bewirkt, wie bereits gesagt, daß diese Bytes nicht initialisiert
werden,  sprich, der zuvor vorhandene  Speichermüll stehenbleibt. Anstelle des
Fragezeichens läßt sich auch ein numerischer Wert einsetzen:

feld DB 10 DUP (0)

Obige  Anweisung  etwa  würde, von der  Variablen  feld  beginnend, zehn Bytes
reservieren und diese mit dem Wert 0 initialisieren.

Um dieses Kapitel abzuschließen, gibt's ein Beispielprogramm.

MODEL SMALL           ;Kleinstes EXE-Speichermodell
DATA SEGMENT          ;Beginn Data-Seg
ASSUME DS:DATA        ;DS zeigt auf Data-Seg
 text DB "ASM rulez!$";Variable text
DATA ENDS             ;Ende Data-Seg
CODE SEGMENT          ;Beginn Code-Seg
ASSUME CS:CODE        ;CS zeigt auf Code-Seg
 MOV AX,SEG DATA      ;Data-Seg initialisieren
 MOV DS,AX            ;
 MOV AX,0900h         ;Funkt. 9
 MOV DX,OFFSET text   ;Offset -> DX
 INT 21h              ;String ausgeben
 MOV AX,4C00h         ;Funkt. 4Ch, Exit-Code 0
 INT 21h              ;DOS-Exit
CODE ENDS             ;Ende Code-Seg
END                   ;Ende des Proggys

Und,  wie manche vielleicht beim  aufmerksamen Durchlesen des Listings bemerkt
haben...  Um  gleich zwei Fliegen auf  einen Schlag zu erwischen (wie brutal),
habe  ich  das Programm als EXE-Datei  aufgebaut.  Somit werden wir gleich das
Geheimnis  um  dieses  Dateiformat  lüften!  Ein  Programm  wird mit folgenden
Befehlen zu einer EXE-Datei compiliert:

TASM /T/L progname
TLINK progname

Um  EXEs zu erzeugen, entfällt bei TLINK also der Parameter /T. Nun zum Aufbau
einer EXE an Hand dieses Beispiels!

+++ EXE-Dateien +++

Zeile 1:  MODEL SMALL     Stellt  das  Speichermodell  SMALL  ein.  Es ist das
                          kleinste Speichermodell für EXE-Dateien.
Zeile 2:  DATA SEGMENT    Der Vorteil  von  EXEs  ist  ja,  daß  das  Programm
                          mehrere Segmente haben kann. Hier definieren wir den
                          Anfang   des  Datasegments,  in  dem  meistens   die
                          Variablen gespeichert werden!
Zeile 3:  ASSUME DS:DATA  Damit wird DS befohlen, auf das Dataseg  zu  zeigen.
                          Das  allein  genügt jedoch nicht, wie wir bald sehen
                          werden...
Zeile 5:  DATA ENDS       Ende des Datasegs!
Zeile 6:  CODE SEGMENT    Das kennen wir ja schon. Anfang des Codesegs.
Zeile 7:  ASSUME CS:CODE  Und CS zeigt auf's Codeseg... chrrrr...
Zeile 8:  MOV AX,SEG DATA DAS IST JETZT NEU! Um DS  auf's  Dataseg  zeigen  zu
                          lassen,  genügt  ASSUME  nicht. Vielmehr muß man das
                          Segment manuell an DS zuweisen.  Mit  diesem  Befehl
                          weisen wir das Datensegment AX zu...
Zeile 9:  MOV DS,AX       ...und  damit  bringen  wir  das Ganze an den recht-
                          mäßigen Besitzer, DS.
Zeile 15: CODE ENDS       Ende des Codesegs.
Zeile 16: END             Ende des Proggys.

Wie  ihr  seht, entfällt also bei EXE-Dateien  zum einen ORG 100h, zum anderen
kann  auf  ein  Startlabel verzichtet werden.  Deshalb  genügt es, am Ende des
Programms  statt  END und dem Startlabel (z.B.  END  start) einfach nur END zu
schreiben.

+++ Interrupt 21h Funktion 9 +++

Nun, auch dieses Programm gibt etwas auf dem Bildschirm aus. Diesmal wird aber
nicht  nur  ein einzelnes Zeichen, sondern  ein ganzer String ausgegeben! Und,
wie  ihr euch wohl denken werdet...  auch dieses Programm verwendet dazu einen
Interrupt. Es ist Interrupt 21h Funktion 9! (Schon wieder Funktion 9, die Zahl
9  scheint  wohl ein Synonym für  Textausgabe  zu sein.) So funktioniert diese
Funktion:  AH=9,  DS=Segment, in dem sich  der  String befindet, DX=Offset des
Strings.  Ganz wichtig: Der String muß mit einem Dollarzeichen (Umschalt/Shift
und  4  gleichzeitig  drücken)  enden.  Dieses  Zeichen  wird  nicht  auf  dem
Bildschirm ausgegeben.

Übrigens  ist INT 21h einer der Interrupts, die von DOS zur Verfügung gestellt
werden. Deshalb nennt man seine Funktionen "DOS-Funktionen".

Jetzt sind auch die restlichen Zeilen des Proggys hoffentlich klar!

Zeile 4:  text DB "ASM rulez!$" Definiert die Variable text, in der der String
                                gespeichert werden soll.
Zeile 10: MOV AX,0900h          Schreibt die Funktionsnummer in AH  und löscht
                                zum Spaß gleichzeitig AL.
Zeile 11: MOV DX,OFFSET text    Mit dem Operator OFFSET wird  der  Offset  der
                                Variablen text an DX zugewiesen.
Zeile 12: INT 21h               Ruft INT 21h Ufo 9 auf, die den Text ausgibt!
Zeile 13: MOV AX,4C00h          Funktionsnummer 4Ch...
Zeile 14: INT 21h               ...zum Beenden des Programms.

+++ Variablen in COM-Dateien +++

In COM-Dateien Variablen zu verwenden, ist nicht ganz so einfach. Denn in COM-
Dateien  darf es nur ein Segment geben, das Codeseg. Was nun? Die Variablen in
das Codeseg schreiben? Gute Idee, aber VORSICHT! So einfach geht das nicht!

Damit ihr versteht, warum es so nicht geht, ist ein bißchen Theorie notwendig.
Also: Die ganzen ASM-Befehle wie MOV werden als Mnemonics bezeichnet. Wenn man
nun  aus  einem  Assemblerprogramm  assembliert,  also  eine  COM-  oder  eine
EXE-Datei  erzeugt, werden diese Mnemonics in eine für den Compi verständliche
Form  umgewandelt, die OP-Codes. Dabei sind die OP-Codes jedoch nichts anderes
als Zahlen, die vom Compi als Befehle interpretiert werden!

Das Teuflische an der Sache ist nun, daß der Computer nicht zwischen Daten und
Befehlen  entscheiden  kann.  Wenn sich  also  inmitten  des Codesegments eine
Variable befindet, führt der Compi den Befehl aus, der den OP-Code des Inhalts
der  Variable hat! Hat man großes Pech  und enthält die Variable OP-Codes, mit
denen der Interrupt zum Formatieren der Festplatte ausgelöst wird...

So  schlimm  wird's  zwar  nur  selten kommen,  aber  auf  jeden  Fall  ist es
ärgerlich,  wenn  der Computer die  Variableninhalte als Befehle interpretiert
und  somit unerwünschte Nebeneffekte auftreten. Wie  kann man das umgehen? Die
Antwort  lautet: Ganz einfach! Man muß einfach dafür sorgen, daß die Variablen
bei  der Ausführung des Programms übersprungen werden. So macht es auch dieses
Beispielprogramm:

MODEL TINY            ;Für 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
 text DB "ASM rulez!$";Variable text
begin:                ;'Richtiger' Beginn des Proggys
 MOV AX,0900h         ;Funkt. 9
 MOV DX,OFFSET text   ;Offset -> DX
 INT 21h              ;String ausgeben
 MOV AX,4C00h         ;Funkt. 4Ch, Exit-Code 0
 INT 21h              ;DOS-Exit
CODE ENDS             ;Ende Code-Seg
END start             ;Ende des Proggys

Wie  gehabt,  erzeugt  ihr daraus eine  COM-Datei  mit  den Befehlen TASM /T/L
progname  und  TLINK progname /T. Wenn  ihr das Programm startet, wird genauso
wie  beim  vorigen Proggy der Text  'ASM  rulez!' ausgegeben. Doch schaut mal,
wieviel  Speicherplatz  die beiden Programme  benötigen!  Die EXE-Datei belegt
über 500 Byte (ein halbes Kilobyte), die COM-Datei... nur 16 Byte!!! Das liegt
daran, daß EXE-Dateien am Anfang noch einen 500 Byte großen Header beinhalten,
während  COM-Dateien wirklich nur OP-Codes enthalten.  Schauen wir uns nun an,
wie wir das Problem mit den Variablen gelöst haben.

+++ Der JMP-Befehl +++

In  der 6. Zeile finden wir einen neuen Befehl! Mit Hilfe von JMP kann man ein
Label  'anspringen'. Das heißt, daß der Compi die nächsten Befehle überspringt
und  erst  nach dem angegebenen Label  (in  unserem Beispiel begin) fortsetzt.
'Rücksprünge'  (das  Label  befindet sich  irgendwo  vor  dem JMP-Befehl) sind
natürlich auch möglich.

JMP  ist  nicht der einzige Sprungbefehl  - jedoch der einzige bedingungslose.
JMP  sagt dem Compi, daß er unbedingt  zu einem bestimmten Label springen muß.
Die  anderen  Sprungbefehle  werden nur  dann  ausgeführt, wenn eine bestimmte
Bedingung erfüllt ist. Wir werden sie noch später kennenlernen!

Übrigens   sind  Labels  auch  nichts  anderes  als  Decknamen  für  bestimmte
Speicherstellen.  Der  Unterschied  zu  Variablen  ist,  daß  man  Labels auch
anspringen kann.

Weiter  mit dem Programm: In Zeile 7 wird nun die Variable definiert. Dank des
JMPs  in Zeile 6 wird der Inhalt  dieser Variable nicht ausgeführt. In Zeile 8
finden  wir  dann das Label, zu dem  gesprungen wurde, und der Rest müßte klar
sein.

Bis zum nächsten Mal viel Spaß wünscht euch (immer noch) euer Adok!