...In zwei Übersetzungsprogramme für Assembler-Quellprogramme, die im NASM-Dialekt geschrieben wurden und deren eines
NASM-Dialekt übersetzt und deren anderes einen neuen Dialekt übersetzt, den ich am Ende dieses Textes beschreibe.
Das Programm, das den NASM-Dialekt übersetzt, wird im folgenden "ASMn" genannt und ist in einiger Hinsicht vereinfacht,
weil es nur unter ASMOS arbeiten soll und nur Programme für dieses Betriebssystem übersetzen können soll. Es ist dafür
gedacht, bereits vorhandene, in NASM geschriebene Programme mit nur wenigen Änderungen unter ASMOS assemblieren
zu können. Falls die Quellkompatibilität nicht nötig ist, sollte man den Abkömmling "ASMat" benutzen, den ich weiter
unten beschreibe.
Für ASMn gilt, dass eine grosse Anzahl von Ausdrücken nicht übersetzt bzw.ignoriert wird, die von NASM erkannt werden.
Und es gibt einige Dinge, die in ASMn möglich sind, nicht aber in NASM...
Eine detaillierte Gegenüberstellung der Kommandos findet man in der Datei "TSOURCE", die auch zur Testübersetzung und
Verifizierung der Codes taugt.
...ist ein einziges standalone-Program, das in einem einzigen Durchlauf assembliert.
Die Ausgabe des Übersetzungsprogramms sind ein Listing und ein Binary, das sofort mit den normalen Menüfunktionen unter
ASMOS zum Laufen gebracht werden kann. Die Fehlerausgabe erfolgt im Listing, indem eine Zeile geschwärzt wird, undzwar
exakt ab dem Ausdruck oder Teilausdruck, der nicht mehr erkannt wird. Normalerweise wird trotz solcher Fehlererkennung
weiter gelistet. Nur ein doppelt definiertes Label, ein negativer Adresswert beim Vervielfachen ("TIMES") oder eine
zu lange Adresse im Kommando "loop" führen zum sofortigen Abbruch der Übersetzung.
Die Präprozessorstufe ist so einfach wie möglich gehalten - sie existiert nicht. Da in ASMOS Ausschneiden&Kopieren
sehr einfach zu machen sind und eine Kopie beliebige Zeichen 00h-FFh enthalten kann, braucht man nur die ohnehin
vorhandenen Funktionen und weder "include" noch irgendwelche "Makros".
Da man eine Codeübersetzung für den real mode höchstens noch für die Programmierung eines Bootloaders braucht (der
eigentlich schon geschrieben wurde), enthält ASMn nichts, was real mode- oder V86-Programmierung erlaubt. ASMn
arbeitet also mit implizitem, unabänderlichem "USE 32".
Da die einzige Umgebung, in dem die Quellprogramme arbeiten können sollen, ASMOS ist, gibt es weder irgendwelche
besonderen Linker-header zur Wahl noch irgendwelche Optionen beim Start von ASMn - nach der Initialisierung ist
ASMn ein Menüpunkt unter F9.
Der ASMn-assembler erkennt sehr wenige "expressions", und diese sind auf wenige Zwecke in Definitionen beschränkt:
(arithmetische Anweisungen sind bis auf einen Spezialfall ausgeschlossen!):
: + * -
Ein Label kann mit Postfix ":" oder ohne wie in NASM geschrieben werden. Es kann auch ein Kommando in der gleichen
Zeile folgen.
ASMn beginnt das Assemblieren einer Zeile, indem es zunächst ein Semikolon ";" sucht und dabei "whitespace"
(Zwischenräume =20h oder =00h) überspringt. Neben diesen besonderen drei Zeichen wird jedes andere zunächst als
Teil eines Mnemoniks oder des Ausdrucks "ALIGN 4" (einem "standard macro" in NASM, das in ASMn eine Pseudo-Mnemonic
ist) bewertet. Ähnlich wie NASM und andere Assembler, wird erst nach einer Nichterkennung irgendeines Mnemoniks
der Name eines Labels als Sinn der Zeichenkette angenommen.
Wenn "ALIGN 4" oder "align 4 " gefunden wird, füllt ASMn mit NOPs (=90h) bis zur nächsten durch 4 teilbaren Byteadresse.
Hinter einem Label wird wie gesagt zunächst kein Mnemonik gesucht, sondern eines der folgenden sieben Zeichen:
DT, DQ, DD, DW, DB (gross oder klein geschrieben, um den Typ zu definieren)
oder einer der zwei quasi-ops "*" (ähnlich "TIMES") oder "I" (ähnlich "INCBIN").
Alle anderen Zeichen werden dagegen als Teil eines Mnemoniks genommen.
Falls der Typ DB ist, können wie in NASM statt Ziffern 'text...' oder "text..." folgen. Und ebenso wie im NASM-Stil kann
ein Name eines Labels eine Adresse repräsentieren, wenn der Typ DD ist.
Nur die Typen DT, DQ und DD dürfen Werte mit einem Minuszeichen als Präfix definieren. Während aber der Typ DD auch
"Integer" (also reelle Zahlen) definieren kann, müssen die Typen DQ und DT grundsätzlich Fliesskommawerte definieren,
die ich am Ende dieses Textes genauer erkläre. Die Definition diese Werte erfolgt in der (glücklicherweise) allgemein
akzeptierten Form.
Da die Anweisungen "TIMES" und "INCBIN" in AMSn als Typangaben gehandelt werden, gibt es keinen Zweck für "$" oder "$$".
Wenn der Typ eines Labels "*" ist, führt ASMn ein Füllanweisung ähnlich "TIMES" aus. Die Anweisung hat dann noch einen
Zählerwert für Wiederholung zu enthalten - Das sieht etwa so aus:
Anders als NASM übersetzt ASMn ja keinen gemischten Quellcode für sowohl real als auch protected mode. Für einige, wenige
Zwecke (Bootloader) ist gemischter Code in einer Quelltextdatei aber wünschenswert.
Ein Übersetzungsprogramm für beide Modi wäre sehr kompliziert wegen nötiger Verzweigungen an beinahe jeder Ecke. Ich habe
deshalb die Aufgabe auf zwei Programme verteilt mit dem zusätzlichen Effekt, dass jedes für sich seine Aufgabe schneller
und mit weniger Speicherplatzbedarf erledigt. Die Verknüpfung verschiedener Codes ist unter ASMOS sehr einfach, weil man
im Editier-Modus 0 eine binäre Kopie erzeugen und einfügen kann. Diese Aufgabe muss also nicht im Übersetzungsprogramm
gelöst werden!
Das praktische Vorgehen dabei und die Feinheiten der Programmierung für den real mode entnimmt man den Kommentaren
in ASMnr und der Originaldatei dieses Textes. Es gibt sehr deutliche Unterschiede zum protected mode!
Dieser Abkömmling entstand, weil ich während der Arbeit an ASMn sah, dass man das Programm nicht nur etwas einfacher machen
konnte, indem man den Quelldialekt ändert, sondern dass man so vor allem mal die Erzeugung der Quelle vereinfachen kann.
Dieses Programm mit seinem Dialekt ist dann vorzuziehen, wenn man keine Kompatibilität zu anderen Betriebssystemen braucht.
Der Dialekt von ASMat ist nur unter ASMOS zu haben.
Einige Vorteile seien kurz hervor gehoben. Die anderen erscheinen beim Umgang mit dem Assembler und seiner Heimat ASMOS.
-> wie ASMn auch, selektiert ASMat zuerst nach den am häufigsten verwendeten Mnemoniks - "mov" und Sprünge...
-> ASMat selektiert nach Labels aber nicht erst (wie alle anderen Assembler), wenn fast dreihundert Mnemoniks nicht zutrafen.
Weil Label nicht gerade selten in einem Programm vorkommen, ergibt sich allein dadurch ein sehr grosse Beschleunigung der
Übersetzung. Ausserdem hat man bei der Namensbildung nicht mehr zu bedenken, ob irgendwas am Namen als Kommando verkannt
werden kann.
-> ASMat enthält weitere Vereinfachungen in der Schreibweise von Typen und Operandenteilen.
-> Weil beinahe jedes Assemblerprogramm bis zu 50% "mov"-Kommandos enthält, habe ich als Alternative zu den drei
Buchstaben den einen nach links weisenden Winkel eingebaut, der rechts von der linken Schift-Taste ohne Akkordgriffe
gesetzt werden kann. Ausserdem habe ich die Kleinschreibung zum Zwang gemacht und alle Bezeichner eliminiert,
die einem Programmierer das Schreiben sauer machen können.
Die drei Dimensionen eines Programmes: Label, Kommandos und Kommentar werden nach einer sehr einfachen Regel geschrieben:
Besondere Umgangsformen sind für die Programmierung des Arithmetik-Prozessors ("Floating Point Unit"=FPU) nötig, die nicht
allein mit der Kenntnis der Mnemonics zu leisten sind. Deshalb erkläre ich hier möglichst kurz Fliesskommaformat und
Bedeutung der Kommandos, die neben dem Arbeitsspeicher Register nicht innerhalb der CPU, sondern der FPU adressieren.
Ausführlicher sind die folgenden Angaben in der Originaldatei enthalten.
Die FPU ist ab CPUs vom Typ >= i486 Teil der CPU und kann mit eigenen Registern auch parallel zur CPU arbeiten. Um die
Synchronisation zu gewährleisten und Fehler im Rechengang zu melden, kann die FPU selbst Unterbrechungen veranlassen. Sie
tut das aber nicht wie die CPU und auch nicht über den Interrupt-Controller, sondern im Zusammenhang mit dem eigenen
Statusregister.
Weil die FPU eine eigenständige Recheneinheit ist, die einstmals auch in separatem Gehäuse untergebracht wurde, hat sie ihren
eigenen Ablauf von Reset und Initialisierung. Der wird allerdings im wesentlichen über das Steuer-Register cr0 in der CPU
erledigt.
Ursprünglich enthält die FPU acht 80-stellige Datenregister, die als Stapel mit einem FPU-internem Stapelzeiger adressierbar
sind, der die Bits 11,12,13 im Statusregister der FPU belegt, auf das unterste belegte Element (=TOP) zeigt und abwärts
"wächst".
In neueren FPUs sind absolut adressierbare und anders benamte Operanden-Register und auch neue Kommandos sind zu haben,
die aber aber in keinem meiner Übersetzungsprogramme erkannt werden.
Ein wesentlicher Teil der Konstruktion ist ein Operandenformat, das eine binäre Repräsentation von Komma, Mantisse (die
in diesem Zusammenhang auch "Signifikant" genannt wird) und Exponent erlaubt. Dieses Format ist unter "IEEE 754" genormt
und im Internet zu finden.
Während Mantisse und Exponent mit dreierlei Stellenzahl vorkommen können und nur innerhalb der jeweiligen Stellenzahl variieren
können, kann das Komma in der Mantisse "fliessen".
Damit ist gemeint, dass die Stelle, nach der das Komma erscheint, nicht direkt durch eine binäre Repräsentation wie z.B.das
Vorzeichen in der CPU definiert ist, sondern durch Definition des Exponenten zustande kommt. Dieser hat ebenfalls keine Stelle
für ein Vorzeichen, sondern ist mittels eines fest eingebauten "Bias" um Null herum definierbar. Jeweils die niedere Hälfte der
Dualziffer, die den Exponenten definiert, ist dadurch als negativer Exponent gegeben.
Initialisierung und Steuerung werden über drei Register erledigt:
cr0 in der CPU intialisiert und steuert das Zusammenspiel zwischen CPU und FPU und stellt die FPU ein.
Steuer-Register in der FPU, nur implizit mit Kommandos ;fldcw;, ;fstcw; und ;finit; zu erreichen
Status-Register in der FPU, nur implizit mit Kommandos ;fstsw; und ;fnstsw; zu erreichen
Für den laufenden Betrieb ist natürlich das 16-stellige Status-Register unverzichtbar, das in den Stellen 0-5 die FPU-flags
enthält, die mit Unterbrechungsanforderungen verknüpft sind und über das Steuerregister maskiert und damit von der Wirkung abgeschnitten
werden können. Diese Stellen zeigen die "Ausnahmen" (="exceptions") an, die bei der Interruptbehandlung als reparierbar zu betrachten
sind, also nicht zum Systemzusammenbruch führen sollten. Der Interrupt-Behandler muss auch unbedingt das entsprechende flag in der FPU
löschen! Es wird nämlich nicht automatisch zurückgesetzt und würde ungelöscht nicht aufhören, Ausnahmen zu fordern.
Die Programmierung eines geeigneten Interrupt-Behandlers für den Interrupt 10h (16) ist also unbedingt nötig, bevor auch nur ein
FPU-Kommando gegeben wird! Auch die Behandlung eines Interrupts 7 ("FPU nicht verfügbar / Taskumschaltung") ist eventuell sehr
wichtig. Der Interrupt 6 schliesslich würde gegeben, wenn neuere FPU-Kommandos (mmx usw...) gegeben aber nicht verarbeitbar sind.
In den Bits 8,10,14 werden dagegen die Zustände gezeigt, die denen im Status-Register der CPU entsprechen.
Schliesslich wird in den Bits 11,12,13 die aktuelle Adresse des Stapelzeigers dargestellt, der die Operanden-Register der FPU adressiert.
Operanden können auch im Arbeitsspeicher liegen, nicht aber in CPU-Registern. Der richtige Platz sind aber FPU-Register, die
als Stapel organisiert sind:
Während die Adresse des Stapelzeigers ein Register absolut adressiert, kann ein Register in Kommandos nur relativ zu dieser
Adresse adressiert werden. Der Name der Register ist dabei in den meisten Assembler-Dialekten "st" mit einem Postfix von 0...7.
Da das Postfix aber relativ auf das vom Stackzeiger augenblicklich adressierte Register bezogen wird, hat also immer das den
Namen "st0" was den "TOP" des Stapels bezeichnet!
Da der Stackzeiger inkrementiert oder dekrementiert werden kann, kann man die Relation verschieben, macht also aus ein und
dem selben Register "st7" oder "st1" und hat TOP verschoben. Somit kann man den relativ adressierbaren Stapel mit seiner
tatsächlichen Basisadresse =TOP durch den Stapel rotieren lassen, was raffiniert ausgenutzt werden kann.
Die FPU kann Zahlen grundsätzlich nur im Fliesskommaformat verwenden, undzwar mit 80 Stellen. Operanden für einfache und
doppelte Genauigkeit werden immer entsprechend umgewandelt! Dabei wird das auch in der CPU benutzte Dualziffernsystem
zur Repräsentation von Exponent und Mantisse benutzt.
Auf jeden Fall ergeben sich Genauigkeitsprobleme in der FPU wegen der begrenzten Stellenzahl für Mantisse und Exponent.
Es gibt also eine grösste und eine kleinste darstellbare Zahl.
Die Struktur der drei möglichen Formate, die mit Angabe ihrer "Präzision" unterschieden werden, ist zwar einigermassen
einfach, nicht jedoch die Wandlung in Dezimalziffern, die weitere Ungenauigkeiten bringt.
Der von mir in ASMn/ASMat vorgestellte Algorithmus zur Wandlung dezimaler Konstantendefinitionen in das Fliesskommaformat
ist neu und arbeitet präziser als andere bekannte Algorithmen in Hochsprachen. Er enthält schöne Beispiele für den ansich
einfachen Umgang mit Werten, die in mehr als 64 Stellen definiert sind, falls multipliziert, dividiert oder potenziert
werden muss. Einen wichtigen Teil des Rechenganges erledige ich in 128 Stellen, womit kein Hochsprachen-Programmierer
umgehen kann. Deshalb unterscheiden sich die mit ASMn/ASMat übersetzten Konstanten eventuell in den letzten Stellen
der 64 von denen, die NASM oder andere Übersetzungsprogramme bei gleicher Definition übersetzen.
Wer mit dem Gebrauch von Zehnerpotenzen vertraut ist, hat auch schon ein Komma fliessen gesehen, allerdings sicherlich ohne
den blumigen Ausdruck dafür...
Wir kennen die Zehnerpotenzen als 10=10exp2, 100=10exp2 usw... und können diese als Multiplikator von Dezimalziffern benutzen.
Dabei ist, was links von "exp" steht, die Mantisse. Rechts davon steht der Exponent, der die Potenz ausdrückt (und wenn's geht
hoch gesetzt geschrieben wird).
Dann ist 123*10exp1=12,3*10exp2=1,23*10exp3=0,123*10exp4.....und dabei ist das Komma von rechts nach links geflossen.
Völlig analog fliesst das Komma (das von Amerikanern und anderen als Punkt geschrieben wird) bei Dualziffern, die in der FPU
wie der CPU bestens durch Spannungen an Transistoren repräsentiert werden können (0V für 0 dual oder 5V für 1 dual ).
Aber wenn nun das Komma fliesst, multipliziert bzw. dividiert man nicht mit dem Faktor 10 wie im Dezimal-system, sondern nur
mit dem Faktor 2 .In beiden Systemen ist das aber ein Inkrementieren bzw. Dekrementieren des Exponenten.
Nun kann klar sein, dass man mit der Wahl des Exponenten das Komma in einer Mantisse setzen kann, wenn man wie in einer FPU
nur eine begrenzte, als Genauigkeit ausgedrückte Zahl von Stellen hat.
Den richtig definierten Exponenten muss ein Programmierer aber garnicht einschätzen, weil die FPU nämlich selber das Komma
fliessen lassen kann, indem der Exponent inkrementiert bzw.dekrementiert wird. Dabei wird eine Anordnung von Ziffern
benutzt, die in der Norm IEEE-754 festgeschrieben wurde. Damit werden die Mathematik reeller Zahlen und maschinelle
Gegebenheiten sehr elegant zu einer Regel gemacht.
Grundsätzlich wird jede Ziffer in eine Form gebracht, die nur noch Nachkommastellen enthält. Auch wenn man Konstanten in
einem Assemblerprogramm anders formulieren kann, muss bei der Übersetzung der Ausdruck aufbereitet werden. Damit erübrigt
sich schon mal eine binäre Repräsentation des Kommas, denn natürlich wird in der FPU nicht mit Dezimalziffern, sondern
Dualziffern umgegangen. Dabei gibt es immer eine führende 1 ,die allein vor dem Komma steht, wenn die FPU damit umgeht.
Eine Definition von 1234 wird also zu 1,234*10exp3
Weil die binäre Repräsentation dieser Zahl mit der Basis 2 statt 10 umgeht, muss natürlich der Multiplikator anders
aussehen. Dann sieht das Beispiel so aus: (1,234*5exp3)*2exp3
Damit kann der Exponent bleiben, wie er ist, die Mantisse aber ist mit 5exp3 zu multiplizieren. Und natürlich müssen die
Dezimalziffern in Dualziffern gewandelt werden. Der Ausdruck "2exp" dagegen ist in die FPU eingebaut.
Weil bei dezimaler Konstantendefinition stets die potenzierte 5 im Spiel ist, die bei negativen Exponenten als Divisor der
Mantisse auftritt, sind die gewandelten Dualziffern nur ausnahmsweise "glatt". Weil irgendwelche irrationalen Brüche
natürlich rationaler werden müssen, um in die FPU zu passen, müssen Stellen abgeschnitten werden, die tatsächlich definiert
wurden. Was immer dann damit gerechnet wird, ist also ein bisschen daneben, wenn die Mantissendefinition nicht glatt durch
alle Fünfen teilbar ist, die der Exponent gebietet!
Aus diesem Grunde mache ich in ASMat die hexadezimale Definition von Fliesskommawerten mit Typ "h" möglich.
Nach der Wandlung in Dualziffern gibt es eine führende 1, die exakt mit dem Exponenten der Zweierpotenz ausgedrückt ist,
und die deshalb auch nur gedacht werden kann. Sie wird bei allen Werten mit einfacher oder doppelter Genauigkeit erst
nach der Wandlung in erweiterte Genauigkeit ausgepackt. Diese versteckte führende 1 kennzeichnet "normalisierte" Mantissen,
die bei gegebener Stellenzahl doppelt so viele Nachkommastellen haben können. Stets wird diese 1 als einzige Vorkommastelle
abgehandelt.
Nur wenn der Exponent den kleinstmöglichen Wert hat, wird "denormalisiert", was das Fehlen der ansonsten impliziten 1
bedeutet. Diese Denormalisierung macht natürlich eine andere Behandlung des Resultates nötig und wird deshalb mit
Unterbrechungsanforderung laut gemacht. Weil das viel Zeit kostet, werden normalisierte Mantissen nicht bei erweiterter
Genauigkeit benutzt. Am besten sollte man sie auch ausserhalb der FPU benutzen, wenn Speicherplatz kein Problem
darstellt.
Ähnlich pfiffig ist das Problem des Vorzeichens für den Exponenten gelöst worden. Das sagt, wenn es ein Minuszeichen ist,
dass mit der potenzierten Zahl nicht multipliziert wird, sondern dividiert. Für die Darstellung von Nullkommafastnix ist das
also dringend nötig! Deshalb wird je nach Genauigkeit ein Versatz (="bias") zum Exponenten addiert. Den wahren Exponenten
erhält man also stets erst durch Subtraktion des bias. Der ist bei einfacher Genauigkeit =127, bei doppelter =1023 und bei
erweiterter =16383
Wer diese Zahlen in Dualzifferndarstellung kennt, wird gleich das Gefühl haben, dass hier was fehlt - und tatsächlich!
Diese Exponenten, die nur aus Nullen oder Einsen bestehen, sind besonderen Zwecken geweiht. Sie stellen die Werte für
unendlich dar oder NICHT-Zahlen (=Not a Number =NaNs). Es sind nämlich ausser Ziffern auch andere, nicht als Ziffern
darstellbare Resultate zu erwarten.
Ausser solchen fremdartigen Werten sind neben abgeschnittenen (nicht definierten) Stellen ausserdem noch Rundungsfehler
zu berücksichtigen, die Resultate nicht wirklich exakt machen. "Unendliche" Werte können tatsächlich exakte endliche sein,
die nur zu klein oder zu gross geraten sind und deshalb nicht mehr in die begrenzte Zahl von Stellen (insbesondere des
Exponenten) passen.
Die fremdartigen Werte werden von der Unterbrechungsbedingung, z.B "ungültige Operation"=IE begleitet.
Bei Definitionen ist zu beachten, dass das "e" in der üblichen Darstellung nicht nur den bezifferten Exponenten definiert,
sondern für einen Multiplikator steht, der eine mit dem Exponenten potenzierte 10 ist: 1.e3 ist also nicht =1 sondern =1000
p.p.
Diese Fassung der Datei ist verkürzt! - Also nochmal lesen, wenn die Quellen ausgepackt sind...