WinAli
WinAli ist ein Modell-Assembler (siehe auch Assemblersprache) für Windows und DOS. Der generierte Maschinencode wird mit einem Modellrechner ausgeführt, der eine Emulation eines echten Prozessors darstellt. WinAli ist dazu gedacht, um Assembler zu lernen. Der Entwickler richtet sich dabei an Schüler, die bereits Kenntnisse (objektorientierter) Hochsprachen, vorrangig Pascal, haben.
Datentypen
Ordinal
WinAli kennt nur einen einzigen Datentyp. Laut der WinAli Dokumentation handelt es sich um einen Integer. Allerdings ist der Datentyp zwei Byte groß und entspricht somit nicht dem pascal’schen Datentyp Integer
noch dem C’schen int
(auf 32bit Systemen) welche vier Byte belegen.
Die Tatsache, dass der WinAli-Datentyp zwei Byte große, vorzeichenbehaftete Werte speichern kann, macht ihn zu einem Smallint
(Pascal) bzw. zu einem short
(C).
Register
WinAli stellt 16 Register bereit, welche allerdings keine spezifischen Bedeutungen besitzen, wie es etwa bei x86 Assemblern der Fall ist.
Register wie CX
sind also bei WinAli nicht implementiert. Die Register werden stattdessen einfach mit den Zahlen 0
bis 15
angesprochen.
Stack
So wie es 16 Register gibt, gibt es auch 16 voneinander unabhängige Stacks, die genauso angesprochen werden wie die Register. Folglich hängt es also von dem Kontext ab, ob das Zeichen 0
als das erste Register oder als der erste Stack interpretiert wird.
Variablen
Da in WinAli Variablen nur einen einzigen Datentyp haben können, ist es auch nicht erforderlich, diesen explizit als Smallint
auszuweisen. Eine Variable wVar
wird dabei wie folgt definiert;
wVar ds f
Allerdings gibt es nicht nur die Möglichkeit eine einzelne Variable, sondern auch ein ganzes (konstantes) Array rgwVar
mit beispielsweise 16 Elementen zu definieren;
rgwVar ds 16f
Konstanten
Konstanten kann man gleich auf zwei Arten definieren. Die elegantere davon ist die Konstante wTwo = 2
als solche auch zu deklarieren;
wTwo dc '2'
Man muss allerdings nicht für jede Konstante ein neues Symbol deklarieren. Notiert man an einer Stelle, an der eine Variable oder eine Konstante erwartet wird, statt eines Symbols (wie zum Beispiel: wTwo
) die Zahl in Hochkommata, so deklariert WinAli sie beim assemblieren automatisch.
wFive dc '5' ... lda 0,wFive;gleichbedeutend mit: lda 0,'5' ...
Syntax
Formal
Die Syntax von WinAli ist stark an einen echten Assembler wie etwa TASM (Borland) oder MASM (Microsoft) orientiert. Man kann jede Codezeile in vier Spalten unterteilen;
label command params comment
Das Einrücken der Wörter dient dabei der besseren Leserlichkeit.
Schlüsselwort | Bedeutung |
---|---|
label |
Eine Marke oder ein Sprungziel, zu dem aus einer anderen Zeile hingesprungen werden kann. Oft bleibt diese Spalte leer. |
command |
Auszuführender Befehl in dieser Zeile. Eine ausführliche Erläuterung aller Befehle ist unter dem Abschnitt Befehlsreferenz geführt. |
params |
Jeder Befehl hat einen bis zwei Parameter, dies ist mindestens ein Register und, je nach Befehl, ein weiteres Register, einen Stack oder eine Adresse zu einer Speicherstelle. Hat der Befehl zwei Parameter, werden diese durch ein Komma „,“ getrennt. |
comment |
Ein beliebig langer Kommentar, welcher durch ein ";" oder ein "*" eingeleitet und durch das Zeilenende abgeschlossen wird. |
Beispiel
An dem folgenden Beispiel erkennt man die Struktur und das Aussehen eines WinAli Programmes.
loop sta 0,wSav;wSav = r lda 0,cwMult sub 0,'1';cwMult-- sta 0,cwMult cmp 0,'1' add 0,'1' mul 0,wSav;r:=cwMult*wSav bne loop
Wie man an den rechts im Kommentarbereich notierten Pascal-Befehlen anschaulich erkennt, ist ein WinAli Programm immer länger und auch weniger intuitiv als ein Programm in einer Hochsprache, wie etwa C, C++ oder Pascal.
Befehlsreferenz
Es kursieren mehrere Versionen der WinAli Befehlssyntax, sodass die Befehlsnamen voneinander abweichen. Der Befehl lda
kann in einer anderen Version auch stattdessen l
heißen. Die Anzahl der Befehle ist jedoch gleich.
Befehle
Ein- und Ausgabebefehle | ||
---|---|---|
ini A |
Der vom Benutzer eingegebene Wert wird in der Adresse A gespeichert. |
|
outi A |
Der in der Adresse A gespeicherte Wert wird auf dem UI ausgegeben. |
|
Transport | ||
lda R,A |
Lädt den in der Adresse A gespeicherten Wert in das Register R . |
R:=A; |
ldr R0,R1 |
Lädt den Wert in dem Register R1 in das Register R0 . |
R0:=R1; |
ldcr R0,R1 |
Lädt das Komplement zu dem Wert in dem Register R1 in das Register R0 . |
R0:=-R1; |
sta R,A |
Speichert den Wert in dem Register R in die Adresse A . |
A:=R; |
Arithmetik | ||
add R,A |
Addiert den Wert in der Adresse A zu dem Wert des Registers R . |
R:=R+A; |
addr R0,R1 |
Addiert den Wert des Registers R1 zu dem Wert des Registers R0 . |
R0:=R0+R1; |
sub R,A |
Subtrahiert den Wert in der Adresse A von dem Wert des Registers R . |
R:=R-A; |
subr R0,R1 |
Subtrahiert den Wert des Registers R1 von dem Wert des Registers R0 . |
R0:=R0-R1; |
mul R,A |
Multipliziert den Wert in der Adresse A mit dem Wert des Registers R . |
R:=R*A; |
mulr R0,R1 |
Multipliziert den Wert des Registers R1 mit dem Wert des Registers R0 . |
R0:=R0*R1; |
div R,A |
Dividiert den Wert des Registers R durch den Wert in der Adresse A . |
R:=R div A; |
divr R0,R1 |
Dividiert den Wert des Registers R0 durch Wert des Registers R1 . |
R0:=R0 div R1; |
Vergleich (wird relativ zum ersten Parameter ausgewertet) | ||
cmp R,A |
Vergleicht den Wert des Registers R mit dem Wert in der Adresse A . |
if (R ## A) then |
cmpr R0,R1 |
Vergleicht den Wert des Registers R0 mit dem Wert des Registers R1 . |
if (R0 ## R1) then |
Sprung (wenn bedingt, abhängig von dem Ergebnis eines Vergleichs) | ||
b A |
Springt an die Programmzeile, die in der Adresse (dem Label) A spezifiziert wird (Kurz: Springt zu A ). |
goto A; |
be A |
Springt zu A , wenn die Operanden des Vergleiches gleich waren. |
if (R = O) then goto A; |
bne A |
Springt zu A , wenn die Operanden des Vergleiches ungleich waren. |
if (R <> O) then goto A; |
bh A |
Springt zu A , wenn der erste Operand des Vergleiches größer war als der zweite. |
if (R > O) then goto A; |
bnl A |
Springt zu A , wenn der erste Operand des Vergleiches größer/gleich dem zweiten war. |
if (R >= O) then goto A; |
bnh A |
Springt zu A , wenn der erste Operand des Vergleiches kleiner/gleich dem zweiten war. |
if (R <= O) then goto A; |
bl A |
Springt zu A , wenn der erste Operand des Vergleiches kleiner war als der zweite. |
if (R < O) then goto A; |
Sprung in ein Unterprogramm (alle unbedingt) | ||
bal R,A |
Springt zu A und speichert die Rücksprungadresse in das Register R . |
|
balr R0,R1 |
Springt zu R1 und speichert die Rücksprungadresse in das Register R0 . |
|
la R,A |
Lädt die Adresse von A in das Register R . |
R:=@A; |
br R |
Springt zu der Adresse, die in dem Register R gespeichert ist. |
|
Stackoperationen | ||
push R,S |
Push't den Wert in dem Register R in den Stack S . |
S.Push(R); |
pop R,S |
Pop't das oberste Element aus dem Stack S in das Register R . |
R:=S.Pop(); |
top R,S |
Kopiert das oberste Element aus dem Stack S in das Register R . Dabei bleibt das Element jedoch in dem Stack. |
R:=S.Top |
Steuerung | ||
eoj |
Markiert das Ende des Quellcodes. | end. |
nop |
Führt keine Operation aus. Kann genutzt werden um den Code übersichtlicher zu machen. | |
nopr |
Gleiche Wirkung wie nop , belegt jedoch nur zwei, statt vier Bytes. |
Anmerkung zu der Tabelle:
- Erläuterungen sind in (Object) Pascal verfasst.
- Auf die korrekte Notation des Codes MIT Zeilenumbruch wurde zu Gunsten der Übersichtlichkeit verzichtet.
Befehlsgröße
Die Größe eines Befehls beträgt entweder zwei oder vier Bytes.
Jeder Befehl mit zwei Parametern, dessen zweiter Parameter eine Adresse (also kein Register und kein Stack) ist, und der Befehl nop
belegen vier Bytes.
Alle anderen Befehle belegen zwei Bytes.
Datenstrukturen
Arrays
Im Gegensatz zu vielen anderen Assemblern sind Arrays in WinAli direkt implementiert. Ein Adressieren von Elementen durch die direkte Berechnung seiner Adresse mithilfe eines Offsets und der Basisadresse ist daher nicht nötig. WinAli führt beim Adressieren eines Elementes sogar eine Bereichsprüfung und löst bei einem falschen Index einen Laufzeitfehler aus. Hat man das Array rgwVar deklariert;
rgwVar ds 8f
dann kann man ein Element adressieren, indem man das Offset in eines der Register, 1
bis 15
lädt und anschließend jenes Register in Klammern nach dem Namen des Arrays notiert (das benutzen des Registers 0
zum indizieren, funktioniert nicht). Das Offset ist die relative Adresse des Elementes in dem Array, also das Produkt aus Index und Datengröße (welche immer 2 ist). Folgendes Beispiel kopiert den Wert an der Stelle '3'
des Arrays rgwVar
in die Variable iwDrei
, dazu wird das Register 1
zum Indizieren benutzt;
: iwDrei ds f
: rgwVar ds 8f
: ...
: lda 1,'6' ;Index * Datengröße = Offset <=> '3' * '2' = '6'
: lda 0,rgwVar(1)
: sta 0,iwDrei
Dies entspricht folgendem Code in Pascal, bzw. C
//Pascal:
var
iwDrei: SmallInt;
rgwVar: array[0..7] of SmallInt;
...
iwDrei:= rgVar[3];
//C:
short iwDrei;
short rgwVar[8]
...
iwDrei = rgwVar[3];
Das Speichern eines Wertes in ein Array funktioniert analog.
Absolutes Adressieren
Allerdings gibt es noch eine zweite Variante ein Element in einem Array zu adressieren. Diese ist etwas umständlicher, da man dazu nicht – wie oben – das relative Offset benutzt, sondern das absolute. Dazu muss allerdings erst die Adresse des Arrays ausgelesen werden;
la 1,rgwVar ;die absolute Adresse des ersten Elementes des Arrays
add 1,'6'
lda 0,0(1) ;WICHTIG: Die erste 0 bezeichnet das Register 0, die zweite 0 nicht.
sta 0,iwDrei
Das wirklich Praktische an diesem Verfahren ist die Tatsache, dass man so Zeiger auf beliebige Variablen dereferenzieren kann. Obwohl dies von WinAli nicht so vorgesehen ist, ist das Auslesen und Schreiben von Werten so durchaus möglich.
Stack
Wie bereits erwähnt, verfügt der WinAli Assembler über 16 Stacks mit den Bezeichnungen 0
bis 15
. Aus stilistischen Gründen sollte jedoch nur einer verwendet werden, da ein „echter“ Assembler ebenfalls nur über einen Stack verfügt. Allerdings ist es bei der Implementation von rekursiven Methoden wesentlich einfacher, den Stack 1
zum Speichern der Rücksprungadressen zu benutzen.
Sonstige
Alle anderen Datenstrukturen, die man benötigt, muss man selbst implementieren, da es keine Bibliotheken etc. gibt. Dazu ist es die wohl beste Strategie, eine eigene Methode (void*) malloc (char);
und ihr Pendant free (void *,char);
(in Pascal etwa function malloc (byte): Pointer;
bzw. procedure free (Pointer,byte);
) zu schreiben, die in einem Array eine Art Arbeitsspeicher verwalten.
Objekte
Es wäre damit sogar möglich Objekte (im objektorientierten Sinn) zu erzeugen und freizugeben. Eine Klasse TAuto könnte dabei wie folgt notiert werden.
TAuto dc '4' ;Größe eines Objektes dieser Klasse
FcwSize dc '0'
FdwVelo dc '1'
FwColor dc '2'
FdwLast dc '3'
Siehe auch
- Programmfehler
- Assembler
- Assemblersprache
- Emulator
- Virtuelle Maschine
- Ungarische Notation – die hier benutzten Variablen erhielten ihren Namen nach dieser Notation