WinAli

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

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.

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).

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.

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.

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 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'
 ...

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.

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

[Bearbeiten | Quelltext bearbeiten]

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.

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.

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

[Bearbeiten | Quelltext bearbeiten]

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

[Bearbeiten | Quelltext bearbeiten]

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.

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.

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.

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'