###   Projekte und Informationen rund um den KC85   ### 

Programmieren mit der SYSLIB 2

oder: Wie man einen PRL-Linker bastelt

von Jörg Linder

Was macht ein Redakteur der KC-News, wenn er nicht gerade an einer neuen Ausgabe werkelt? Er denkt sich neue Standards aus und quält überdies auch noch unschuldige Assembler. So geschehen in den letzten Wochen in einer Kleinstadt namens Seelow. Im Artikel "Neue Speicherverwaltung ..." habe ich behauptet, daß es ganz einfach sei, eine PRL-Datei auf eine bestimmte Basisadresse zu linken. Hier nun der Beweis:

Bevor ich näher auf den Quelltext eingehe, noch ein paar Bemerkungen zum Format der PRL-Dateien. Der Header ist recht übersichtlich aufgebaut, denn ca. 99,22 % sind mit Nullbytes gefüllt. Für sämtliche Berechnungen stehen nur drei Größen zur Verfügung. Dies sind die Länge des Codes (im Header gespeichert), der orginale Offset des Codes von 0100h und der Beginn des Codes in der PRL-Datei (nach dem Header von 256 Bytes Länge).

Vorbemerkungen

Um nicht das berühmte Fahrrad ein zweites Mal zu erfinden, habe ich mich vor allem bei der Dateiarbeit auf die Routinen der SYSLIB verlassen. Diese sind langjährig erprobt und liefern (im Gegensatz zu meinen Programmierversuchen) garantiert die definierten Funktionen. Eine Besonderheit stellt der externe Bezug $MEMRY dar. Dabei handelt es sich nicht um eine Routine, sondern um ein spezielles Symbol, das der Linker mit der Adresse des ersten freien Bytes nach dem gelinkten Code belegt.

Da ich beim Programmieren nicht wissen kann, wie groß das Programm letztendlich wird (zumal einige SYSLIB-Routinen hinzukommen) habe ich zwei Möglichkeiten, die Adresse zum Laden der PRL-Datei festzulegen. Zum einen könnte ich eine ausreichend hohe Adresse (z. B. 1000 h) annehmen und die Datei dorthin laden. Zum anderen ließe sich durch einen ersten Assembler-/Linkerlauf die endgültige Größe des Programms feststellen und eine darauffolgende freie Adresse beim zweiten Lauf verwenden.

Wenn anschließend nochmal der Quelltext überarbeitet werden muß (wie das bei mir fast immer der Fall ist), kann jedoch für beide geschilderten Möglichkeiten nicht mehr garantiert werden, daß die Adresse noch zulässig ist. Warum sollte also nicht einfach der Linker diese Berechnung für mich übernehmen? Mit $MEMRY tut er das. Will man im Programm auf diese Adresse zugreifen, geht dies z. B. über LD HL,($MEMRY).

So, bevor es richtig losgeht, kann man sich noch zwischen einer deutschen und einer englischen Version entscheiden. Dank .ACCEPT-Anweisung können SLR-Besitzer dies während des Assemblerlaufs tun. Alle anderen müssen im Quelltext das Symbol "German" dementsprechend setzen. Ansonsten kann die Quelldatei unberührt bleiben. Übrigens habe ich zwei Versionen auf die Beilagen-Diskette kopiert, um fehlerfreies Arbeiten mit weniger intelligenten Assemblern und Linkern zu ermöglichen.

Mit folgenden Kommandozeilen wird die ausführbare .COM-Datei erzeugt:

ASM/M80 und LINK/L80

ASM PRL11=PRL11
LINK /P:100,PRL11,SYSLIB/S,PRLLNK11/N/E

SLR180 und SLRNK+

SLR180 PRL11/R
SLRNK+ /A:100,/J,PRL11,SYSLIBS/S,PRLLNK11/N/E

Aufbau des Programms

Der Quelltext ist recht linear aufgebaut. Ein kurzer Überblick ist daher schnell gegeben. Nach ein paar einleitenden Maßnahmen (Stack retten, Parameter prüfen) wird die eingegebene Basis-/Endadresse vom FCB2 geholt und in einer Arbeitszelle gespeichert. Diese Aktion muß unbedingt vor der ersten Dateioperation mit dem FCB1 durchgeführt werden, weil dabei die Daten des FCB2 überschrieben werden.

Anschließend wird versucht, die angegebene Datei (auf FCB1) zu öffnen. Schlug dies fehl, wird der standardmäßige Dateityp "PRL" eingesetzt und ein erneuter Versuch gestartet. Die Datei wird sektorweise in den DMA-Puffer gelesen und von dort auf die durch $MEMRY bezeichnete Adresse umkopiert. Nun geht's mit dem eigentlichen Linkvorgang los.

In Abhängigkeit von der Größe des Codes in der PRL-Datei und der gewählten Basis- oder Endadresse wird der Offset berechnet. Dies ist der wichtigste Schritt für ein fehlerfreies Resultat. Dabei muß unbedingt beachtet werden, daß der Code in der PRL-Datei bereits einen originalen Offset von 0100h hat und nur auf den Beginn einer Page gelinkt werden kann. Zur Kontrolle werden die berechneten Adressen auf den Bildschirm ausgegeben.

Sind die Zeiger (engl. Pointer) korrekt geladen, tritt die kurze Routine zum Relozieren in Aktion. Danach wird die Zieldatei mit dem Dateityp "CIM" sektorweise umkopiert und geschrieben. Sollte sie schon existieren, hat der Anwender die Möglichkeit, sie zu überschreiben. Danach wird der Stack wiederhergestellt und das Programm ohne Warmstart verlassen. Fertig!

Besonderheiten

Das war natürlich der fehlerfreie Weg. Anhand der möglichen Meldungen kann man erkennen, daß eine Vielzahl von Fehlern erkannt und angezeigt werden. In solch einem Fall wird das Programm nach der Bildschirmausgabe beendet. Zum größten Teil werden die Fehler übrigens von den SYSLIB-Routinen erkannt.

Die Fehlerbehandlung bzw. die Zeichenkettenausgabe soll noch etwas näher beleuchtet werden. Ich habe mich für die Adressierung der Zeichenketten über eine Tabelle entschieden. Dies ermöglicht den schnellen Zugriff auf unterschiedliche lange Meldungen, die trotzdem nahtlos aneinandergereiht im Programm stehen. Sofern der zur Verfügung stehende Platz nicht überschritten wird, können sie sogar im fertigen Programm frei editiert und angepaßt werden. Es muß anschließend nur die Tabelle überarbeitet werden.

Und so funktioniert die Subroutine: Anhand der im Register A übergebenen Nummer (Hexzahl) wird in der Tabelle nachgeschaut, wo sich die Zeichenkette befindet. Das Registerpaar DE wird mit der Adresse geladen und die entsprechende Routine des BDOS aufgerufen. Weil die Subroutine auch in anderen Teilen des Programms benötigt wird und dort keine Register verändern darf, werden über PUSH die Register gerettet und nach Ausführung mit POP wiederhergestellt.

Die paar Zeilen für das Relozieren sind kaum der Rede wert. Als Zeiger auf die Codebytes und die Bit Map werden die Registerpaare HL und DE benutzt. BC beherbergt indes die Länge des Codes (aus dem Header entnommen). Leider kann diese Zahl nicht ohne weiteres verwendet werden. Zunächst wird sie durch 8 geteilt und der Rest gespeichert, denn ein Byte hat genau 8 Bits - Codebytes und Bit Map - alles klar?

Für das Relozieren werden nun die Bytes der Bit Map sozusagen Bit für Bit untersucht und je nach Zustand (0 oder 1) der Offset zum dazugehörigen Codebyte addiert. Dies erledigt die Subroutine RELOZI, die entsprechend der Zahl im Registerpaar BC aufgerufen wird. Danach muß nur noch der Rest abgearbeitet werden. Dazu wird ebenfalls die Subroutine RELOZI verwendet.

Nutzen

Äh, ja, wozu taugt der PRL-Linker eigentlich? Zuallererst einmal zeige ich damit, daß ich die Hoffnung nicht aufgegeben habe und vielleicht doch jemanden zum Programmieren mit der SYSLIB anstiften kann. Eventuell bastelt auch momentan ein anderer Club an einem neuen System und kann den Linker dabei gebrauchen...

Doch eigentlich war es für mich nur wichtig, hiermit zu demonstrieren, daß man mit einer Handvoll Bytes die Basisadresse von PRL-Dateien festlegen kann. Von dieser Seite sollte es also für das neue Speichermanagement des Grundgeräte-RAMs kein Problem mehr geben. Sobald wir einen Standard festgelegt haben und ZAS entsprechend erweitert wurde, kann aus dem PRL-Linker ein einfaches Ladeprogramm entwickelt werden. Man könnte sogar eine Routine analog denen der SYSLIB ableiten und somit für jeden Programmierer zugänglich machen.

Jetzt haben der Assembler, die SYSLIB und natürlich auch Ihr erstmal Ruhe vor mir! Doch wer weiß, womöglich fällt mir wieder etwas ein ...