home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga MA Magazine 1998 #6
/
amigamamagazinepolishissue1998.iso
/
coders
/
assembler-kurs
/
lektionen
/
lektion2.txt
< prev
next >
Wrap
Text File
|
1977-12-31
|
31KB
|
705 lines
ASSEMBLERKURS - Lektion 2
Na, habt ihr Listing1a.s perfekt verstanden? Wenn nicht, dann seit ihr
reif für die Klapsmühle, und ihr solltet den Kurs hiermit beenden.
Nun wollen wir die 68000er-"Sprache" etwas vertiefen. Ich habe vorhin
schon angedeutet, daß der Prozessor nur als Organisator wirkt, selbst aber
nur Werte herumkopiert und ändert. Indem man gewisse Werte in bestimmte
Zonen im Speicher gibt, z.B. $DFFxxx oder $BFExxx, gibt man den Pins der
einzelnen Chips Strom, wie dem der Grafik, des Sound, der Ports, und
folgedessen kann man, wie im vorgegangenen Beispiel, die Farben am
Bildschirm ändern, oder durch Auslesen bestimmter Adressen die Information
erhalten, wo sich gerade der Elektronenstrahl des Monitors befindet oder
ob der Mausknopf gedrückt ist. Um ein Demo oder ein Spiel zu schreiben,
braucht man einen Haufen solcher Adressen, REGISTER genannt, und es ist
nötig, sie zu kennen genauso wie die Programmiersprache des 68000ers
(MOVE, JSR, ADD, SUB etc.), mit denen man die beschreibt.
Für das Programmieren auf diese Art brauchen wir die Bibliotheken
(Libraries) der ROM / des Kickstarts 1.2/1.3/2.0/3.0 (also die
Subroutines, die uns erlauben, ein Workbenchfenster zu öffnen oder einen
File zu lesen...)nicht, oder vielmehr nur sehr wenig: z.B. um das
Multitasking abzuschalten, um die Workbench nicht in GURU zu schicken!
Ich halte es für notwendig, in dieser Lektion 2 die Verwendung des 68000
zu vertiefen, so daß verstanden wird, was sein Zweck ist.
Das wichtigste, das es zu lernen gilt, sind die Adressierungsarten. Das
ist fast wichtiger als die Befehle selbst, denn wenn das einmal sitzt,
kann man die einzelnen Befehle lernen, sie funktionieren alle mit der
gleichen Syntax ("Rechtschreibung"), und man muß sich nur noch merken, was
jeder einzelne tut. Wie schon gesagt, arbeitet der Prozessor im Speicher,
der in Adressen eingeteilt ist, dessen Einheit das Byte ist. Normalerweise
wird die hexadezimale Schreibweise angewandt, das ist ein Zahlensystem,
das sich von gebräuchlichen Dezimalsystem darin unterscheidet, daß es
nicht nur die Ziffern 0 bis 9 besitzt, sondern 0 bis 9 und A bis F, es hat
als Basis nicht Zehn sondern Sechzehn. Es ist so, als ob die Ziffern A, B,
C... wie 10, 11, 12, ... dastehen würden. Um die Zahlen von Hexadeziaml in
Dezimale zu konvertieren, reicht es, in der Kommandozeile des ASMONE den
Befehl "?" einzugeben: "?10000" liefert als Resultat $2710, den
entsprechenden Wert als Hexzahl (die Hexzahlen werden immer von einem
$-Zeichen angeführt, die Dezimalzeichen von nichts und die Binärzeichen
von einem %). Das Hex-system wird verwendet, weil es der Art des
Computers, Zahlen darzustellen am nächsten liegt. Der Computer selbst
"denkt" aber klarerweise im Binärsystem, also nur 0 und 1.
Um die verschiedenen Arten der Adressierung des 68000ers verstehen zu
lernen, werden wir den Befehl CLR verwenden, der die angegebene
Speicherzelle löscht:
CLR.B $40000 ; Erinnert ihr euch an den Unterschied
; zwichen .B, .W und .L ?
Diese Instruktion "säubert" die Speicherzelle $40000, es löscht also das
Byte Nr. $40000 im Speicher (setzt es auf 0). Das ist der einfachste Fall,
die sogenannte ABSOLUTE Adressierung; d.h., man gibt direkt die Adresse
an, auf die man ein CLR anwenden will. Im Assembler sind LABELS in
Gebrauch, die helfen, einen "Ort" im Programm zu identifizieren, in dem
z.B. ein Byte steht, das es anzusprechen gilt. In diesem Fall reicht statt
der Adresse der Name des Label. Der Assembler kümmert sich dann darum, das
Label durch die effektive Adresse des Bytes zu ersetzen. Wenn wir unser
erstes Listing in etwa so modifizieren:
Waitmouse:
move.w $dff006,$dff180 ; gibt den Wert von $dff006 in $dff180
; also das VHPOSR in COLOR0
btst #6,$bfe001 ; limke Maustaste gedrückt ?
bne.s Waitmouse ; wenn nicht, zurück zu Waitmouse und
; wiederhole
; (das .s ist äquivalent zu .B in diesem
; Typ von Anweisung (bne.s = bne.b)
clr.b Wert1 ; Setze Wert1 auf 0
rts ; Steige aus
Wert1:
dc.b $30 ; dc.b bedeutet "Gib folgendes Byte in den
; Speicher", in diesem Fall wird $30 unter Wert1:
; gesetzt.
Vor dem Ausstieg mit dem RTS würde das Byte, das durch das Label Wert1:
gekennzeichnet wird, auf NULL gesetzt. Diesem Byte würde in der Fase des
Assemblierens eine ganz bestimmte, absolute Adresse zugewiesen werden,
z.B. wenn das Programm mit dem ASMONE assembliert würde, auf eine Adresse
ab $50000, hier würde nach danach ein CLR.B $5001c stehen, also die reale
Adresse von Wert1: , aber bestimmt nicht CLR.B Wert1, da Wert1: nur ein
Name ist, den der Programmierer dem dc.b $30 gegeben hat. Hier wird auch
die Nützlichkeit der Labels klar, man stelle sich vor, ein Listing
schreiben zu müssen, bei dem immer die numerischen Adressen angegeben
werden müssen; abgesehen von der Unbequemlichkeit, wenn man eine Routine
inmitten der anderen einfügt, müßten alle Adressen neu geschrieben
werden... Um zu sehen, auf welche Adressen die Labels gelegt werden, kann
man den Befehl "D" des ASMONE verwenden: z.B. nach dem Assemblieren von
Listing1a.s kann man ein "D Waitmouse" durchführen, und ihr werdet den
disassemblierten Speicher ab Waitmouse erhalten, und im Listing werden
nicht die Labels, sondern die reellen Adressen aufscheinen.
Ihr werdet bemerken, daß in den Beispielprogrammen niemals numerische
Adressen auftreten, aber immer nur Labels. Einzige Ausnahmen sind die
Spezialadressen wie $dffxxx oder $bfexxx. Im letzten Beispiel habe ich ein
dc.b verwendet. Dieser Befehl hat die Aufgabe, bestimmte Bytes einzufügen;
z.B. um $12345678 an einem bestimmten Punkt des Programmes einzufügen,
verwende ich ein DC, das in drei Formen auftreten kann: .B (Byte), .W
(Word), .L (Long):
dc.b $12,$34,$56,$78 ; in bytes
dc.w $1234,$5678 ; in words
dc.l $12345678 ; in longwords
Diesen Befehl verwendet man auch, um Sätze in den Speicher zu geben, wie
etwa den Text, der am Bildschirm ausgegeben werden soll, wenn z.B. eine
Routine PRINT aufgerufen wird, die das ausdruckt, was unter dem Label
TEXT: steht:
TEXT:
dc.b "Viele schöne Grüße"
oder
dc.b 'Viele schöne Grüße'
dc.b "Viele schöne Grüße",0
Erinnert euch, den Text unter Gänsefüßchen zu setzen und das dc.b zu
verwenden, nicht ein dc.w oder dc.l!! Die Charakter sind ein Byte groß,
und sie entsprechen einem bestimmten Byte: probiert ?"a" einzugeben, und
ihr werdet merken, daß es $61 entpricht. Daraus folgt, daß dc.b "a" das
gleiche ist wie dc.b $61. Achtung aber, Großbuchstaben haben andere
Werte!! Ein "A" hingegen ist $41. Die häufigste Verwendung des dc.b ist
aber jener, Bytes, Words oder noch größere Speicherzonen zu definieren, in
denen unsere Daten festgehalten werden. Wenn man zum Beispiel ein Programm
schreiben möchte, das zählt, wie oft eine Taste gedrückt wird, müßte man
ein Label definieren, gefolgt von einem - auf NULL gesetzem - Byte, und
jedesmal, wenn ich nun die Taste drücke, 1 dazuzählen. Dafür verwendet man
ein ADD, gefolgt von dem Label, somit wird das Byte unter dem Label um
eins raufgezählt. Zum Schluß braucht man nur noch den Wert auszulesen:
; Wenn die Tasate gedrückt wurde, dann ADDQ.B #1,ANZAHL, also
; zähle ein Byte unter dem Label ANZAHL dazu.
ANZAHL:
dc.b 0
Am Ende des Progammes wird der anfängliche Nuller nicht mehr existieren,
statt dessen wird die Anzahl der Tastendrucke darinstehen. Ein ähnliches
Beispiel ist in Listing2a.s enthalten, es ist auch ausführlich
dokumentiert. Ich rate euch, es in einen anderen Textbuffer zu laden: um
einen anderen Buffer auszuwählen müßt ihr nur mit einer F-Taste auswählen,
F1 bis F10. Wenn dieser Text z.B. in Buffer 1 ist, dann drückt F2, und ihr
seid im 2. Um das Listing zu laden, tippt "R" in der Kommandozeile. Danach
Listing2b.s in Buffer 3 usw. So habt ihr alles sofort zur Hand. Es ist
aber besser, die LEKTION.TXT zu folgen und dann Schritt für Schritt die
einzelnen Listings reinzuholen und zu testen. Danch kehrt ihr zu
LEKTION.TXT zurück, fahrt fort bis zum nächsten angedeuteten Beispiel,
ladet dieses, führt aus... Das ist, glaube ich, die beste Art, zu lernen:
man macht ein bißchen Theorie und verifiziert das gelernte nebenbei.
Habt ihr Listing2a.s verstanden?
Habt ihr die Wichtigkeit von Byte, Word und Longword bemerkt? Was die
Binärzahlen betrifft, um Bits zu zählen beginnt man rechts und geht nach
links, also "umgekehrt", und man startet bei 0, nicht bei 1. Ein Byte (das
aus 8 Bit besteht) beginnt bei 0 und geht bis 7. Z.B. diese Zahl:
%000100010000
Bit 4 und Bit 8 sind "angeschaltet". Um euch ein bißchen unter die Arme zu
greifen, könnt ihr sie auch numerieren:
;5432109876543210 <- intelligente Anwendung des ;
move.w #%0011000000100100,$dffxxx
Hier sind Bit 2,5,12 und 13 der WORD angeknipst. Nochmal zum mitschreiben:
ein Byte hat 8 Bit, ein Word 16 (von 0 bis 15), ein Longword 32 (von 0 bis
31).
In der Anweisung
BTST #6,$bfe001
wird kontrolliert, ob Bit 6 des Byte $bfe001 NULL ist. Wenn es
;76543210
%01000000
wäre, dann ist Bit 6 = 1, also ist der Mausknopf nicht gedrückt!!
Nochmals, ein BYTE hat 8 BIT: um sie einzeln angeben zu können, ist das
Bit ganz rechts das Bit 0, auch NIEDERWERTIGSTES BIT genannt, oder LSB,
aus dem Englischen (Least Significant Bit), während Bit Nr. 7 das am
weitesten links ist, und HÖCHSTWERTIGSTES heißt (MSB Most Significant
Bit). Am höchstwertigsten deshalb, weil es am meißten zählt, genauso wie
beim 1000-DM Schein, bei dem der Einser ganz links um so mehr zählt, desto
weiter links er ist und umsomehr Nullen rechts von ihm sind. Ein Byte kann
als höchsten Wert 255 annehmen, also %11111111.
Ein WORD hingegen besteht aus 16 Bit, praktisch aus zwei Byte, und analog
zum Byte startet man rechts mit dem Bit 0, immer dem niederwertigsten, und
endet ganz links, bei Bit 15, dem höchstwertigsten. Ein Word kommt bis
maximal 65536.
Ein Longword ist aus 32 Bit zusammengesetzt, von 0 bis 31, das sind,
Wunder Wunder, 4 Bytes, oder, noch größeres Wunder, 2 Words,
zusammengeklebt. Maximun ist hier 4294967299 ( 4 Milliarden!!!).
Nun geht´s weiter mit den verschiedenen Adressierungsarten: wie wir sahen,
haben wir mit CLR.W $100 die Speicherplätze $100 und $101 gelöscht, auf
NULL gesetzt, also ein Word beginnend bei $100 (da ein Word aus 2 Bytes
besteht, und der Speicher in Bytes aufgeteilt ist, killen wir 2 Bytes!).
Auf die gleiche Art kopiert ein Move.B $100,$200 den Inhalt von Zelle $100
in Zelle $200. Das kann man auch durch Labels erledigen, ohne die Adressen
spezifizieren zu müssen: MOVE.B LABEL1,LABEL2; also kopiere das Byte unter
LABEL1 nach LABEL2. Es gibt auch Kombinationen davon, so kann ich auch ein
MOVE.B #$50000,LABEL2 machen, das mir einen FIXEN Wert in LABEL2 schreibt.
Wenn z.B. LABEL2 auf Adresse $60000 steht, dann bewegen wir dem Wert
$00050000 nach $60000. Mit einem M $60000 erhalten wir 00 05 00 00. Wenn
das Symbol des Lattenzaunes (#) vor einer Zahl oder einem Label auftaucht,
dann bedeutet das, daß diese Zahl den Wert darstellt, und nicht die
Adresse, an der er liegt, wie es vorkommt, wenn KEIN # davorsteht. Ein
Beispiel:
1) MOVE.L $50000,$60000 ; die Werte in den Speicherzellen
; $50000, $50001, $50002 und $50003
; werden in die Zellen $60000,
; $60001, $60002 und $60003 kopiert.
2) MOVE.L #$50000,$60000 ; Diesmal wird in $60000 der Wert nach
; dem # gellegt, also $50000. Zu beachten,
; daß hier $50000 als Adresse absolut
; nichts zu tun hat, die einzige Adresse,
; die vorkommt, ist $60000.
Wenn Label verwendet werden, ändert sich nichts:
1) MOVE.L HUND,KATZE ; Der Inhalt der Longword HUND, also
; $00123456 wird in die Longword
; KATZE kopiert ($123456 ist das Erste,
; das unter dem Label HUND steht).
HUND:
dc.l $123456
KATZE:
dc.l 0
Nach ausführen des Programmes:
HUND:
dc.l $123456
KATZE:
dc.l $123456
2) MOVE.L #HUND,KATZE ; Diesmal wird die Adresse des Label
; HUND in das Label KATZE kopiert.
Vor der Ausführung: ; Nehmen wir an, daß das Label HUND: auf Adresse
; $34500 steht, wenn man also ein M HUND macht,
; wird man ein 00034500 00 12 34 56 00 00 00 ...
; erhalten.
HUND:
dc.l $123456
KATZE:
dc.l 0
Nach der Ausführung:
HUND:
dc.l $123456
KATZE:
dc.l 34500 ; also wo sich das LABEL im Speicher befindet.
Zu beachten ist, daß wenn versucht worden wäre, ein MOVE.W #HUND,KATZE
oder ein MOVE.B #HUND,KATZE zu tun, der Assembler einen Fehler gemeldet
hätte, da ADRESSEN IMMER ein LONGWORD groß sind. Im Speicher ist an Stelle
eines MOVE.L #LABEL,LABEL immer ein Befehl wie MOVE.L #$12345,$12345
vorzufinden, der Assembler verwandelt das Label in ihre reale Adresse.
Listing2b.s veranschaulicht das.
Nun wenden wir uns an die anderen Adressierungsarten mit Registern (sie
sind schwieriger); wie ich schon andedeutet hatte, gibt es 8 Datenregister
und 8 Adressregister, jeweils D0, D1, D2, D3, D4, D5, D6, D7 und a0, a1,
a2, a3, a4, a5, a6 und a7. Das Adressregister a7 wird auch STACK POINTER,
oder SP, genannt, wir werden es später näher behandeln. Im Moment lasst es
bitte stehen, verwendet nur jene bis a6. Diese Adressen sind alle ein
Longword groß, es sind sowas wie kleine Speicher im 68000er, die als Folge
davon, hurtig schnell sind. Mittels Registern kann man viel anstellen, und
deswegen gibt es eine eigene Syntax dafür. Als erstes vorweg: man kann mit
den A-Registern keine Byte-Arbeiten verrichten. Ein MOVE.B LABEL,A0 gibt
einen Fehler. Mit den Adress-Registern kann man also als .W oder .L
arbeiten. Die Datenregister sind da flexibler: sie erlauben .B, .W oder
.L. Die A-Register sind, wie man bemerkt, für Adressoperationen
prädestiniert, und deswegen gibt es auch eigene Befehle dafür, wie z.B.
LEA, was sovie wie "LOAD ENTIRE ADRESS" bedeutet, also lade die gesamte
Adresse ins Register. LEA steht alleine, ohne .b oder .w, denn es könnte
nur .L geben, es sind ja Adressen, deswegen wird es weggelassen. Um einen
Wert in ein Adressregister zu laden gibt es zwei Methoden:
1) MOVE.L #$50000,A0 ; (oder MOVE.L #LABEL,a0)
2) LEA $50000,A0 ; (oder LEA LABEL,A0)
Wenn man die erste Methode auf Daten- wie auf Adressregister anwenden kann
(Beispiel: move.l #$50000,d0 - move.l #$50000,LABEL - move.l
#$LABEL,LABEL...), so ist die zweite auf Adressen beschränkt.
P.S: Ob man nun move.l #$50000,d0 oder MOVE.L #$50000,D0 schreibt, ist
egal, auch MoVe.L #$50000,d0 ist identisch, das Resultat ändert sich
nicht, nur ästhetisch ist eine Version der anderen zu bevorzugen...
Anderes gilt für das Label: zwar können sie in einem Punkt des Programmes
groß geschrieben sein, weiter später klein, etc. aber nur, weil es im
Preferences des TRASH´M-ONE so eingetragen ist. Diese Option ist die
"UCase = LCase" im Menü "Assembler/Assemble..", was soviel bedeutet wie
"Upper case = Lower case", praktisch "Groß ist gleich Klein". Wenn ihr
diese Option ausschaltet, dann wird bei der Erkennung des Label auf
Groß-Kleinschreibung geachtet, HUND: wird also etwas anderes wie Hund oder
HuNd sein...
Die zweite Methode kann nur auf Adressregister angewandt werden, und
intuitiv kann man sich denken, daß das die schnellere Art sein wird:
richtig. Erinnert euch deshalb, daß wenn ihr eine Adresse in ein Register
wie a0, a1... geben müßt, ihr den Befehl LEA verwenden solltet, gefolgt
von der Adresse OHNE vorangehendem Lattenzaun (#) und dem anvisierten
Register. Schaut die folgenden zwei Beispiele genau an:
1) MOVE.L $50000,a0 ; gib in a0 den ab Adresse $50000
; ($50000 + $50001 + $50002 + $50003,
; ein Longword = 4 Byte!) enthaltenen
; Wert
2) LEA $50000,a0 ; gib die Zahl $50000 in a0
Passt also gut auf, wenn ihr mit MOVE mit und ohne "#" arbeitet, vor allem
am Anfang wird es oft passieren, daß ihr die Adresse mit einem Wert
verwechselt, weil das # fehlt, oder umgekehrt. Den Unterschied versucht
auch Listing2c.s nochmals zu vertiefen.
Mit den Adressregistern sind verschiedene Arten der Adressierung möglich:
Zu Beginn analysieren wir diese zwei Instruktionen:
move.l a0,d0 ; gib die in a0 enthaltene Zahl ins Register d0
move.l (a0),d0 ; gib das Longword, das an Adresse a0 zu finden
; ist, in d0
Die Adressierung mit den Klammern nennt man INDIREKT, da nicht der Wert in
a0 kopiert wird (Direkt...), sondern der, der an der Adresse steht, die in
a0 enthalten ist. Ein praktische sBeispiel ist in Listing2d.s zu finden.
Durch Verwendung der indirekten Adressierung kann man auf die Register
INDIREKT zugreifen, z.B. indem die Adresse der Maustaste und der Farbe 0
in die Register gegeben wird, kann man das Listing von Lektion1
neuschreiben. Das wurde in Listing2e.s gemacht.
Machen wir die letzten Beispiele zur indirekten Adressierung, um noch
eventuelle Zweifel zu beheben:
move.l a0,d0 ; kopiert den Wert in a0 ins Register d0
move.b (a0),d0 ; kopiert das Byte, das an Adresse a0 steht,
; in das Register d0 move.w
(a0),(a1) ; kopiert das Word, das an Adresse a0 steht,
; in die Adresse, die in a1 angegeben ist
; (und in die folgende, denn ein Word besteht
; ja aus zwei Bytes, also zwei Adressen !)
clr.w (a3) ; Löscht das Word (die zwei Bytes), ab der
; Adresse a3, also Adresse a3 und Adresse a3+1
clr.l (a3) ; Wie oben, nur werden Adresse a3, a3+1,
; a3+2 und a3+3 auf NULL gesetzt.
move.l d0,(a5) ; der Wert in d0 wird in die Adresse kopiert,
; die in a5 steht. D.h. an Adresse a5 und die
; drei folgenden wird der Inhalt von d0
; geschrieben, insgesamt 4 Bytes, LongWord.
Also nochmal, bitte beseitigt alle Zweifel, die die Adressierungsarten,
die bis hierher behandelt wurden, betreffen! Eventuell schaut euch nochmal
die Listings bis Listing2e.s durch, da die folgenden Adressierungsarten
auf die bisherigen aufbauen.
Jetzt ist´s wieder mal Zeit für eine Kundgebung: das hier wird der
abstrakteste Teil von Lektion2, denn hier werden die letzten Arten der
Adressierung aufgezählt, aber ich versichere euch, schon ab Lektion3
werdet ihr das angeeignete Wissen einsetzen, und ihr werdet die ersten
Videoeffekte mit dem Copper starten!! Also, dieser Teil überstanden, wird
alles sehr viel praktischer: jeder Erklärung wird ein neuer Spezialeffekt
oder eine hypergeile Farbe zugeordnet sein. Also, haut jetzt noch rein,
und gebt nicht auf, weil es langweilig erscheint, denn ich selbst hatte an
diesem Punkt alles Fallen gelassen, als ich das erste Mal Assembler
lernte, genauso, weil auch ich total durcheinander war von all den
Befehlen und Klammern, die dann alles so ins Chaos stürzten. Aber wenn ihr
mal die Befehle gelernt habt, dann werdet ihr abzischen wie eine Rakete,
und von selbst weiterlernen, indem ihr Listings von hier und da durchlest
und durchstrebt. Die Schritte werden immer größer werden, genauso wie die
Regeln bei einer Sprotart lernen: jemand, der das Set der 68000er Befehle
nicht kennt ist gleich mit einem, der z.B. die Regeln beim Fußball nicht
kennt: wenn er einem Spiel zusieht (Listing...), wird er sich wundern,
wieso die Verrückten alle auf den Ball reindreschen, und er wird sich zu
Tode langweilen, aber wenn er weiß, wie die Spielregeln lauten
(Adressierung etc), wird er die Fasen im Spiel verstehen und die Tricks
kapieren (z.B. die Programmiertricks bei den Grafikregistern).
Schauen wir uns zwei weitere Adressierungsarten an:
lea OPA,a0 ; gibt in a0 die Adresse von OPA:
MOVE.L (a0)+,d0 ; gibt in d0 den .L-Wert (Long), der
; an der Adresse a0 enthalten ist, also
; $3231020 ( genau das gleiche wie ein
; Move.L (a0),d0 )
; DANACH aber SUMMIERE 4 ZUM WERT IN a0 DAZU
; "POST-INKREMENTAL"
; praktisch "zeigen" wir jetzt auf das folgende
; Longword im Speicher
; Wenn es ein move.w (a0)+,d0 gewesen wäre, dann
; würde danach zum a0 nur 2 dazugezählt (ein Word=2)
; bei einem Move.b nur eins (ein Byte...)
MOVE.L (a0)+,d1 ; das Gleiche: kopiert in d1 den Long-Wert,
; der in der Adresse a0 enthalten ist, die
; nun die Adresse OPA + ein Longword ist, also
; OPA + 4, -> $13478
rts ; RAUS!
OPA:
dc.l $3231020,$13478
END
Wir können diesen Typ der Adressierung folgendermaßen übersetzen:
1) Move.L (a0)+,LABEL
ist äquivalent mit:
1b) Move.L (a0),LABEL ; kopiert ein Longword von der Adresse a0
; in das LABEL.
ADDQ.W #4,a0 ; zählt zu a0 4 dazu (.L=4)
; Bemerkung: wenn eine Zahl kleiner als
; 9 dazugezählt wird, verwendet man den
; Befehl ADDQ, weil er für kleine Zahlen
; zurechtgeschnitten ist und daher schneller!
; Weiters, wenn zu Adressregister eine Zahl
; kleiner als $FFFF (Word) summiert wird, kann
; ein .W an Stelle des .L verwendet werden, es
; wird trotzdem immer auf das gesamte Longword
; zugegriffen.
Das Selbe:
2) MOVE.W (a0)+,LABEL
Bedeutet soviel wie:
2b) Move.W (a0),LABEL ; kopiert ein Word von Adresse a0 ins LABEL
ADDQ #2,a0 ;summiert 2 zu a0 (.W = 2 Bytes)
Nochmal das Geliche:
3) MOVE.B (a0)+,LABEL
Ist gleich dem:
3b) MOVE.B (a0),LABEL ; kopiert das Byte an der Adresse a0 ins LABEL
ADDQ #1,a0 ; Zählt 1 zu a0 dazu (.B = 1 Byte)
Also, zusammenfassend kann man sagen, daß die indirekte Adressierung mit
Post-Inlkrementierung mehr oder weniger mit einem Fließband verglichen
werden kann, bei dem der Arbeiter zuerst die Arbeit am Werkstück
verrichtet (MOVE), und jedesmal, wenn er fertig ist, das Fließband mit
einem Pedal (dem +) nach vorne weiterfahren läßt (die Adresse, auf die a0
zeigt). Ein Beispiel anhand einer Schleife wird vielleicht klarer wirken:
Anfang:
lea $60000,a0 ; Hier beginnt der Putztrupp
lea $62000,a1 ; und hier endet er
CLELOOP:
clr.l (a0)+ ; löscht (setzt auf 0) ein Long an der Adresse, die
; in a0 steht, und erhöhe a0 um ein long, also
; um 4 Byte, anders ausgedrückt, lösche ein Long
; und geh zum Nächsten weiter
cmp.l a0,a1 ; ist a0 bei $62000 angekommen; oder: ist a0 gleich
; a1?
bne.s CLELOOP ; wenn nicht, mache einen weiteren Durchgang, bei
; CLELOOP startend.
rts
Wie man sieht, "putzt" dieses Programm den Speicher von Adresse $60000 bis
$62000, indem es ein CLR (a0)+ verwendet, das wiederholt wird, bis wir
nicht zur gewünschten Adresse angekommen sind. Ein ähnliches Beispiel
ist Listing2f.s.
Nun kommen wir zur Adressierung mittels Pre-Decrement, also das Gegenteil
von dem, das ich gerade beschrieben habe, denn anstatt die Adresse im
Register zu erhöhen, nachdem die Operation durchgeführt wurde, wird hier
zuerst dekrementiert (abgezogen), und DANN kommt die Operation. Beispiel:
lea OPA,a0 ; gibt in a0 die Adresse von OPA:
MOVE.L -(a0),d0 ; a0 wird dekrementiert, also um 4 herunter
; gezählt (in diesem Fall um 4, da es sich
; um ein .L handelt, bei einem .W=2, .B=1)
; danach wird in d0 der Wert kopiert, der
; sich an der nun entstandenen Adresse
; befindet, also OPA-4, => $12345678
rts ; im Register bleibt nun der anfängliche
; Wert - 4. Bei einem move.w -(a0),d0 würde
; vorher dem a0 2 abgezogen (-> .W!!), DANACH
dc.l $12345678 ; das Move auf der errechneten Adresse ausgeführt,
OPA: ; bei einem .B würde dem a0 eins abgezogen, a0
dc.l $ffff0f0f ; würde nun auf die vorhergehende Adresse "zeigen"
Wir können diesen Typ von Adressierung mit zwei geteilten Operationen
gleichsetzen:
1) MOVE.L -(a0),LABEL
entspricht:
1b) SUBQ.W #4,a0 ; Ziehe 4 von a0 ab (= .L)
; wichtig: wenn Zahlen kleiner als 9
; abgezogen werden, sollte das SUBQ dem
; SUB vorgezogen werden, es ist schneller
MOVE.L (a0),LABEL ; kopiert den Wert, der an Adresse a0 steht,
; in das LABEL ( an seine Adresse... )
Dementsprechend:
2) MOVE.W -(a0),LABEL
ist gleich mit:
2b) SUBQ.W #2,a0 ; subtrahiere 2 von a0 (.W=2)
MOVE.W (a0),LABEL ; kopiert das Word, das sich an Adresse a0
; befindet, in das LABEL
Und das Letzte:
3) MOVE.B -(a0),LABEL
heißt soviel wie:
3b) SUBQ.W #1,a0 ; Zieht vom Wert in a0 eins ab (.B=1)
MOVE.B (a0),LABEL ; Kopiert das Byte an Adresse a0 ins LABEL
Zusammenfassend, mit dem Beispiel des Fließbandarbeiters von vorhin, kann
man sich vorstellen, daß die Adressierung mit Pre-Decrement so aussieht:
Zuerst verschiebt er das Fließband rückwärts (a0) mit seinem Pedal (-),
DANN erfolgt das MOVE oder die gewünschte Operation. Ein
Schleifenbeispiel:
Anfang:
lea $62000 ; hier beginne ich
lea $60000 ; und hier ende ich
CLELOOP:
clr.l -(a0) ; verringere a0 um ein Long (4 Byte) und
; lösche dann die daraus resultierende
; Speicherzelle, oder anders: geh´ zum
; vorherigen Long und lösche es
cmp.l a0,a1 ; ist a0 bei $60000 angekommen, ist also a0=a1?
bne.s CLELOOP ; wenn nicht, wiederhole alles ab CLEOLOOP
rts
Wie man sieht, "putzt" dieses Programm den Speicher von der Adresse $62000
bis zu Adresse $60000, indem es ein clr -(a0) verwendet, bis es nicht zur
gewünschten Adresse heruntergekommen ist (Eben rückwärts, bei einem (a0)+
begannen wir bei $60000 und kamen in 4er Schritten bis $62000, mit dem
-(a0) hingegen beginnen wir bei $62000 und zählen in 4er-Schritten bis
$62000 herunter).
Schaut euch Listing2g.s und Listing2h.s an, um die letzten zwei
Adressierungen besser zu verstehn.
Jetzt lernen wir, wie man die Adressierungsdistanz verwendet: ein MOVE.L
$100(a0),d0 kopiert den Inhalt, der in Adresse a0+$100 enthalten ist, in
d0. Wenn z.B. a0 die Adresse $60200 enthält, dann kommt in d0 das
Longword, das ab Speicherzelle $60300 enthalten ist ($60200+$100). Auf die
gleiche Art und Weise funktioniert ein MOVE.L -$100(a0),d0, in d0 wird der
Wert von a0-$100 landen, also das Long ab Adresse $60100. Zu beachten ist
aber, daß sich der Wert in a0 NICHT ändert, der Prozessor berechnet
jedesmal die Adresse neu, auf der es zu arbeiten gilt, indem er den Wert
vor den Klammern zur Adresse in den Klammern summiert. Die maximale
"Distanz" (oder Offset), die erreicht werden kann, liegt zwischen -32768
und 32768 (-$7FFF, $8000). Ein Beispiel dafür gibt´s in Listing2i.s.
Die letzte Adressierungsart ist die folgende:
MOVE.L 50(a0,d0),Label
Diese Art hat sowohl eine Adressierungsdistanz (den 50er), wie auch einen
INDEX (das d0): die Distanz und und der Inhalt von d0 werden alle
summiert, um die Adresse zu definieren, von der kopiert werden soll.
Praktisch gesehen ist es das gleiche wie die Adressierung mit Distanz, nur
daß hier auch noch der Inhalt des Registers d0 hinzugefügt wird, das in
diesem Fall aber von minimal -128 bis maximal +128 geht. Ich will euch
nicht mit weiteren Beispielen über diese Adressierung langweilen, ihr
werdet damit besser vertraut werden, wenn sie später in den Listings
vorkommen.
Um die LEKTION2 abzuschließen, die, wenn ihr sie gut durchgekaut hat, euch
in die Lage versetzt, ASM-Listings zu lesen und zu verstehen, ist es
unerläßlich, den DBRA-Zyklus zu erklären. Er wird sehr oft verwendet:
durch Verwenden eines Datenregisters kann man gewisse Befehle öfters
ausführen lassen, es reicht, in das Register (d0, d1,...) die Anzahl-1 zu
geben. Z.B. kann die Routine, die den Speicher mit dem CLR.L (a0)+ löschr,
so modifiziert werden, daß sie mit einem DBRA-Loop funktioniert und den
Putzzyklus so oft aufruft, wie wir es wünschen:
Anfang:
lea $60000,A0 ; Anfang
move.l #($2000/4)-1,d0 ; Gib in d0 die Anzahl der notwendigen
; Durchgänge, um $2000 Bytes zu löschen:
; $2000/4 (also dividiert durch 4, da jedes
; CLR.L 4 Bytes löscht, eben ein Long). Alles
; minus 1, weil der Loop im Endeffekt einmal
; mehr ausgeführt wird.
CLEARLOOP:
CLR.L (a0)+
DBRA d0,CLEARLOOP
rts
Diese Routine löscht den Speicher von $60000 bis $62000, genauso wie das
Beispiel von vorher, das das CMP verwendete, um a0 mit a1 zu vergleichen,
um zu sehen, ob wir angekommen sind, wo wir wollten. In diesem Fall wird
das CLR 2047 mal ausgeführt, probiert mal, in der Kommandozeile des ASMONE
?($2000/4)-1 zu tippen. Das DBRA funktioniert folgendermaßen: Das erste
Mal kommt in d0 z.B. der Wert 2047, das CLR wird ausgeführt, und dann, am
DBRA angekommen, wird d0 um eins verringert, der Prozessor springt zum CLR
zurück. Das wiederholt sich solange, bis d0 "verbraucht" ist, bis es also
NULL enthält. Es muß die Anzahl der Durchgänge minus eins eingegeben
werden, weil beim ersten Durchgang das d0 nicht dekrementiert wird.
Als letztes Beispiel studiert euch Listing2l.s, das Subroutinen mit BSR
aufruft und DBRA-Schleifen in Action zeigt. Es wird nützlich sein, um die
Struktur komplexerer Programme zu verstehen.
Zum Abschluß möchte ich euch noch auf den Unterschied zwischen BSR und
BEQ/BNE hinweisen: im Falle von BSR Label springt der Prozessor zur
Routine, die bei Label liegt, und verharrt darin, bis er ein RTS findet,
das ihn veranlasst, zur Instruktion direkt unter dem BSR zurückzukehren.
Man kann also sagen, es wird eine UNTERROUTINE ausgeführt, d.h. eine
Routine, die in Mitten einer anderen aufgerufen wird:
Hauptprogramm:
move.l ding1,d0
move.l ding2,d1
bsr.s Restposten
move.l ding3,d2
move.l ding4,d3
rts ; ENDE DES HAUPTPROGRAMMES, DER HAUPTROUTINE
; ZURÜCK ZUM ASMONE
Restposten:
move.l nixwert,d4
move.l nixwert2,d5
rts ; ENDE DER UNTERROUTINE, KEHRE ZU "move.l ding3.d2"
; ZURÜCK, ALSO UNTER DAS "bsr.s Restposten"
Im Falle einer BNE/BEQ - Verzweigung hingegen wird entweder ein Weg oder
der andere eingeschlagen:
Hauptprogramm:
move.l ding1,d0
move.l ding2,a0
cmp.b d0,a0
bne.s weg2
move.l ding3,d1
cmp.b d1,a0
beq.s weg3
move.l ding4,d0
rts ; ENDE DER HAUPTROUTINE , ZURÜCK ZUM ASMONE
weg2:
move.l nixwert,d5
move.l nixwert2,d6
rts ; ENDE DER ROUTINE; ZURÜCK ZUM ASMONE, NICHT unter das bne!!!
; Hier haben wir diesen Weg ausgesucht, und wenn ein RTS
; auftaucht, geht´s zurück zum ASMONE!!!
weg3:
move.l nixwert3,d1
move.l nixwert4,d2
rts ; ENDE DER ROUTINE; ZURÜCK ZUM ASMONE, NICHT unter das beq!!!
; Auch hier wurde dieser Weg gewählt, und nach dem RTS
; geht´s zurück zum ASMONE!!!
Das gleiche gilt für das BRA Label, das soviel bedeutet wie SPRING ZU
Label, äquivalent zu JMP, es ist wie ein Zug, der zu einer Weiche kommt,
der kommt auch nicht zurück, wenn das Gleis fertig ist! Am Ende des
Gleises angekommen ist Schluß, kein beamen wie bei Raumschiff Enterprise,
das uns zurück bringt.
Für eine letzte Präzisierung über die Register, schut euch Listing2m.s an.
Um LEKTION3.TXT zu laden, könnt ihr zwei Methoden wählen: entweder "R"
tippen und im Requestorfenster einen Text auswählen (in diesem Fall df0:
LEKTIONEN/LEKTION3.TXT), oder, wenn ihr in der richtigen Directory seid,
einfach "R LEKTION3.TXT". Um Directory zu wechseln, "V df0:LEKTIONEN".