Eigenheiten der ASM/XS-Syntax

1. Allgemein

Die Syntax des Assembler XS wurde vor allem von NASM übernommen. Allerdings habe ich auf einige (ev. nützliche) Fähigkeiten verzichtet, um das Programm möglichst klein zu halten. Vor allem beschränkt sich mein Assembler auf "flache" Binärdateien ohne Linking-Informationen. Deshalb entfallen die SECTION- oder SEGMENT- Direktiven und externe Labels.

Der Assembler XS benutzt immer 2 Passes (Wiederholungen) für die Übersetzung eines Quelltextes. In beiden Passes wird der Quelltext ganz bearbeitet. Der erste Pass dient dazu, Label-/Konstantennamen zu sammeln, und im 2. Pass, in dem diese Namen bekannt sind, wird das Programm in die Ausgabedatei geschrieben. Werden im 1. Pass Fehler entdeckt, hört der Assembler schon nach dem 1. Pass auf; werden im 2. Pass Fehler entdeckt, wird die Ausgabedatei sofort geschlossen und gelöscht. Der Assembler arbeitet dann weiter, ohne in die Datei zu schreiben. Bei kritischen Fehlern (Datei-I/O, Labelcache voll) bricht der Assembler sofort ab.

Der Assembler XS unterscheidet nicht zwischen Gross- und Kleinbuchstaben, ausser innerhalb von Stringkonstanten.

2. Zahlen, Konstanten und Labels

Diese Ausdrücke werden im Folgenden alle als "Zahlkonstanten" bezeichnet.

2.1. Zahlen

Der Assembler XS unterstützt folgende Zahlenformate:

Wie schon bei 1. erwähnt, werden Gross- und Kleinbuchstaben nicht unterschieden, deshalb darf man auch die Zahlenpräfixe und -suffixe beliebig schreiben.

2.2. Konstanten und Labels

Konstanten und Labels sind Ausdrücke, die für eine Zahl stehen. Sie werden vom Assembler XS beim Lesen (Verwenden in einem Befehl) genau gleich behandelt, zum Schreiben (Definieren) benutzt man diese Befehle:

label: mov ax, konstante       ;Labeldefinition mit Doppelpunkt (wie gewohnt), Befehle in der gleichen Zeile möglich
konstante= $1234               ;Konstantendefinition mit Gleichheitszeichen (man beachte die Abstände!)

Bei Konstantendefinitionen dürfen auf der rechten Seite alle möglichen Zahlkonstanten stehen, nur sind Ausdrücke mit weiter unten definierten Label-/Konstantennamen nicht möglich und führen zu einer Fehlermeldung. Neudefinitionen von Konstanten und Labels sind erlaubt, es wird jedoch eine Warnung ausgegeben. Dabei wird die Ausgabedatei immer noch weiter erstellt.

Der Ausdruck @ bezeichnet immer die aktuelle Stelle im Code (den angenommenen IP). In anderen Assemblern wird diese Funktion meistens durch $ dargestellt.

Die vordefinierten speziellen Labels SBYTE und SBYTE2 werden im Kapitel 4.4 (Optimierung) näher erklärt.

2.2.1. Lokale Labels

Ähnlich wie bei NASM kann man mit dem Assembler XS lokale Labels definieren. Der "Name" beschränkt sich dabei auf eine Dezimalzahl (Index), der wie bei den lokalen Labels von NASM ein Punkt vorangeht. Der Index muss grösser als 0 sein. Diese Labels habe ich vor allem deshalb eingeführt, damit man nicht mehr nichtssagende Namen (Loop1 etc.) für Ziele von Schleifen und kurzen Sprüngen definieren und erst noch aufpassen muss, dass man den Namen nicht schon vergeben hat. Diese lokalen Labels funktionieren folgendermassen:

PROC1:
mov    ax, $0E30
mov    cx, 5      ;5 Nullen ausgeben
.1:
int    $10
loop   .1
or     bl, bl     ;(sinnlos)
jnz    .2
jmp    WEIT_WEG
.2:
(...)

PROC2:                  ;Bei der Definition eines (überall gültigen) Labels beginnt die Zählung der lokalen Labels
xor    ax, ax           ;wieder von vorne bei .1
mov    di, PUFFER
mov    cx, 5
.3:                     ;Eine Reihenfolge der Indizes muss nicht eigehalten werden.
stosb
add    di, 5
loop   .3               ;3 Zeilen zurück
or     bl, bl
jz     .1
jmp    WEIT_WEG
.1:
jmp    .2               ;dies gibt einen Fehler, da .2 noch nicht definiert ist

Im Assembler wird der "Neubeginn" durch eine Variable realisiert, die vor dem Zugriff auf den Wertespeicher zur eigentlichen Zahl addiert wird. Wenn ein "richtiges" Label definiert wird, addiert das Programm den höchsten seit dem letzten "richtigen" Label definierten Index zu dieser Variable, wodurch die vorher definierten lokalen Labels "nach unten", also in negative Indizes bis .0 geschoben werden und damit nicht mehr auf sie zugegriffen werden kann. Da diese Labels aber dennoch gespeichert sind, sind sie dem Assembler im 2. Pass noch bekannt.

2.3. Kombinierte Ausdrücke in Klammern

Aus mehreren solche Zahlkonstanten kann man durch verschiedene Operatoren einen neuen Wert berechnen. Für den Assembler XS muss dieser ganze Ausdruck in runden Klammern stehen, ausserdem dürfen keine Leerzeichen in der Klammer stehen, da sie sonst als mehrere Operanden interpretiert wird. Es sind folgende Operatoren möglich:

Wichtig: Aus Spargründen wird der Ausdruck ohne Beachtung der Vorrangregeln der Operationen von links nach rechts durchgegangen, wie es ein einfacher Taschenrechner tut. Der Ausdruck (align-position%align) wird also nicht als (align−(position%align)) berechnet, sondern als ((align−position)%align). Solche weitere Klammern können nicht gesetzt werden, die Berechnung solcher Werte ist aber durch zusätzliche Hilfskonstanten möglich (siehe Beispiel unten).

Beispiele:

cmp    ax, bx
jne    (@+5)            ;Nächsten Sprungbefehl überspringen
jmp    ziel

align= 16
position= @             ;für bessere Übersichtlichkeit, da @ wie ein Operator aussieht
lowbits= (position%align)
rb     (align-lowbits)  ;Nächsten Befehl an einer durch 16 teilbaren Adresse ausrichten

3. Variablen, Anlegen von Daten

3.1. ORG und RES(B,W)

Wie bei NASM ist die ORG-Direktive nur dazu da, den vom Assembler angenommenen Wert des Programmzählers (IP) zu setzen. Man kann damit nicht in der Datei vor- und zurückspringen, wie es mit MASM möglich ist. Allerdings kann mit ORG mehrmals der Wert geändert werden, was vor allem für unterschiedliche Bereiche wichtig ist. zum Beispiel für einen Bootsektor, der vom Programm auf die Diskette geschrieben wird:

org    $100       ;DOS .COM-Datei
mov    ax, $0301
mov    bx, BOOTSECT
mov    cx, $0001
xor    dx, dx
int    $13        ;Bootsektor auf Diskette schreiben
int    $20        ;und beenden

BOOTSECT:
org    $7C00             ;hier würde NASM "Error: Program origin redefined!" ausgeben
mov    ah, $0E
mov    si, BOOTMSG       ;funktioniert ohne ORG nicht!
mov    cx, 22
AUSGABE:
lodsb
int    $10
loop   AUSGABE
(...)
BOOTMSG:
db     'Ich bin der Bootsektor'
(...)

Auch die Befehle RESW und RESB dienen dazu, den angenommenen IP-Wert zu ändern, vor allem bei der Definition von Labels, die nicht auf Codeteile weisen. Sie funktionieren genau wie bei NASM, nämlich indem sie den (bei RESW verdoppelten) Operanden zum IP-Wert addieren.

3.2. Anlegen von Variablen, Bezug im Code (Adressoperanden mit eckigen Klammern)

Die von anderen Assemblern bekannten Befehle DB und DW und die XS-spezifischen D oder DATA, RB und RW dienen dazu, Daten zu definieren, die in die Programmdatei geschrieben werden. Die Befehle haben folgende Funktion:

db     $15, 'Hallo Welt'       ;bekannte Funktion
db     'CX'                    ;hier steht "CX" in der Ausgabedatei
dw     $1234, $1235            ;bekannte Funktion
dw     'CX'                    ;Achtung: Aufgrund des inneren Aufbaus des Assemblers (DW ruft immer den Zahlenparser auf,
                                ;DB benutzt für Strings eine eigene Prozedur) wird hier "XC" in die Datei geschrieben.

d      4, 'Hallo', $FFFF       ;D erlaubt es, Words und Bytes, vor allem auch Strings, zu mischen. Strings werden hier
                                ;mit einer eigenen Prozedur behandelt. Bei Zahlen entscheidet der Wert über die Breite
                                ;(0-255: Byte;  256-65535: Word)
data   $9999, 0, 'Hallo'       ;DATA hat genau dieselbe Funktion und wird BASIC-Programmierern bekannt vorkommen.
rb     4, 'a'                  ;wiederholt den 2. Operanden mit im 1. Operanden angegebener Anzahl. Dieses Beispiel liefert also "aaaa".
rw     3, 0x1234               ;entspricht dw 0x1234, 0x1234, 0x1234
rw     2, 'ah'                 ;ergibt "haha" (Grund siehe dw)
rb     4                       ;reserviert Null-Bytes oder -Words. Dieses Beispiel ergibt dasselbe wie db 0,0,0,0
rb     3, "Langer Text"        ;UNMÖGLICH!!! (RB behandelt Strings nicht mit einer eigenen Prozedur, deshalb sind nur Bytes erlaubt.)
rb     (3+@%16*VAR)            ;Es können beliebige Zahlkonstanten verwendet werden.

Bei der Definition von Variablen mit Namen und deren Verwendung im Code muss folgendes beachtet werden:

Dies entspricht der Vorgehensweise von NASM, ausser der Beschränkung auf Addition und Subtraktion von Konstanten.

4. Eigenheiten bei Prozessorbefehlen

4.1. Präfixe

Das Segment-Override-Präfix kann auf folgende Arten angegeben werden:

es     lodsb                  ;hier die einzige Möglichkeit
es     mov ax, [$1234]        ;oder:
mov    ax, [ES:$1234]
es     mov ax, [ES:$1234]     ;Hier wird das ES-Präfix zweimal in den Code geschrieben!

Die restlichen Präfixe sind REP, REPE, REPNE und LOCK.

4.2. Zusätzliche Angaben (Operationsbreite etc.)

Um bei Operationen die Breite festzulegen, verwendet der Assembler XS Suffixe, die analog zum Fall LODSB/LODSW an den Befehls-Mnemonic angehängt werden:

mov    [VARIABLE], $525         ;Hier ist die Breite definiert, denn $525 hat nur in einem Word Platz
mov    [VARIABLE], 1            ;Hier ist Word- oder Byte-Breite möglich, der Assembler wird reklamieren.
movw   [VARIABLE], 1            ;Word-Breite festgelegt
movb   [VARIABLE], 1            ;Byte-Breite festgelegt
movw   ax, bx                   ;Diese Suffixe sind bei den meisten Operationen auch dann erlaubt, wenn sie
                                ;nicht nötig wären, aber:
pushw  [VARIABLE]               ;UNMÖGLICH!!! Der Assembler wird "Operand fehlerhaft" ausgeben.
inw    ax, <dx>                 ;ebenfalls UNMÖGLICH!!!

jmps   ShortZiel                ;Auch das funktioniert nach dem gleichen Schema
callf  [bx]                     ;FAR-Call (Intersegment)
call   [bx]                     ;NEAR-Call (Innerhalb Segment)

4.3. Port-I/O-Befehle

In Analogie zu den eckigen Klammern bei Adressen für RAM-Zugriffe werden bei IN und OUT spitze Klammern (Kleiner/Grösser-als-Zeichen) für die Portnummer verwendet. Ein Vorteil davon ist auch, dass diese Klammern schnell ins Auge fallen und so die oft massenhaft auftretenden Port-Transfer-Befehle in Gerätetreibern gut sichtbar (ev. auch durch Editor-Highlighting) machen. So sieht man sofort, an welchen Ports das Programm allenfalls kritische Ein- und Ausgaben macht:

mov    al, [$61]   ;analog zu:
in     al, <$61>
out    <(2*BASEPORT+1)>, ax        ;Wieder sind alle Arten von Zahlkonstanten erlaubt, aber bei
                                   ;solchen kombinierten Werten müssen die runden Klammern stehen.
out    <dx>, ax

4.4. Optimierung, Byte-Zahlkonstanten

Der Assembler XS ist ein 2-Pass-Assembler und zusätzlich möglichst klein und sparsam konzipiert. Deshalb muss man normalerweise selbst den Code optimieren, der erzeugt wird, vor allem muss für einen kurzen Sprung der Befehl JMPS verwendet werden, sonst wird immer ein langer Sprung generiert. Einige Dinge regelt der Assembler aber automatisch:

Die letzten beiden Punkte sind vom Wert der Zahlkonstanten abhängig. Da diese unter Umständen im 1. Pass noch nicht bekannt sind, aber trotzdem schon die Länge des Opcodes für Label-Berechnungen benötigt wird und deshalb im 2. Pass nicht mehr geändert werden darf, gelten alle Labels, ob definiert oder undefiniert, als Word-Konstanten.

Um trotzdem ein Programm optimieren zu können, ohne die genauen Adressen selbst zu berechnen, stellt der Assembler XS zwei spezielle Labels zur Verfügung, SBYTE und SBYTE2. Bei ihrer Definition wird geprüft, ob der Wert tatsächlich als vorzeichenbehaftetes (signed) Byte dargestellt werden kann; wenn nicht, wird eine Fehlermeldung ausgegeben. Beim Einsetzen in einen Maschinenbefehl kann – wie bei einer Zahl – die kürzere Opcode-Form verwendet werden. Wenn SBYTE aber zusammen mit weiteren Konstanten auftaucht (z. B. [SBYTE+bx+5] oder (SBYTE+5)), kann die Byte-Grösse nicht mehr gewährleistet werden, und es wird wieder immer die Word-Form verwendet.

Stand 30. Mai 2008, 23:00:00