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!