Anhang: Beispiele und aktuelle Ergänzungen
* SQ: Atari-QL, SMSQ, SMSQ/E
Der Leser wird gebeten, eigene Kontrollen vorzunehmen, rsp. nach Konsultation der Original-Dokumente Mängel ggf. beim Verursacher zu reklamieren.Die sporadisch in den Text aufgenommenen Hinweise auf die SQ-Eigenheiten sind nur ein paar erste Angelpunkte, die bei Bedarf überprüft werden sollten. Vor allem, da so wenige Details bekanntgemacht werden, war das Wenige allein schon der Sicherheit wegen äußerst kritisch einzuordnen.
Dem ließe sich leicht abhelfen: Durch Verzicht auf alberne Geheimhaltungspraktiken...Wer diesbezüglich Erhellendes beizutragen vermag und geneigt ist, das allein im Interesse der QDOS-"Gemeinde" öffentlich zu machen, ohne jede Aussicht auf direkten finanziellen Gewinn, wird händeringend eben darum gebeten.
* program(ier)beispiele
* Befristet intallierte entfernbare System-Erweiterungen
Ein Beispiel soll beschreiben, wie Systemerweiterungen in Gestalt von Device-Handlern und Interrupt-Aufrufen an einen Job gebunden werden können, wodurch sie dann ohne weitere Vorkehrungen selbstätig mit dessen Aufgabe in geordneter Form aus dem System verschwinden.Es wird dazu ein kleiner Timer eingerichtet, der nur als kontinuierlich weitergezählter Langwortinhalt einer memorystelle existiert. Deren besondere Lage erlaubt äußerst schnelle Ausführung und entsprechend geringe Zeitbelastung des System durch diesen Aufruf.
16 Bytes werden für Zähler und Systemliste reserviert:
timem dc.l 0,0 hierher zeigt später a3, CH.LXINT linktime dc.l 0,0 Positionen CH.LPOLL, CH.APOLL
Die Interrupt-Routine kann an beliebigem Ort stehen, wobei die Lagetimem = linktime - 8die besonders einfache Adressierung möglich macht:
timer addq.l #1,(a3) rts
Eine Freigabe-Routine ist bereitzustellen:
timeunl movem.l a0/a1,-(sp) lea linktime(pc),a0 moveq #29,d0 mt.rpoll trap #1 movem.l (sp)+,a0/a1 rts
Aktiviert wird die Interruptroutine derart:
timelnk lea linktime(pc),a0 lea timer(pc),a1 move.l a1,4(a0) moveq #28,d0 mt.lpoll trap #1 rts
Nun wird ein Teilprogram eingerichtet, das die sichere Entfernung der betreffenden Handler- oder Interrupt-Routinen aus den Systemlisten vorbereitet. Dazu nutzt man die Vorgänge bei der automatischen Freigabe von Heap-Bereichen, die einem Job zugewiesen wurden. Die geschieht durch das QDOS immer dann, wenn ein Job aufgegeben wird. Es ist daher "nur" dafür zu sorgen, daß das entprechende Teilprogram zuvor die zuätzlich erforderlichen Freigabeproceduren durchläuft. QDOS selbst entnimmt bei jeglicher Form eines Freigabe-Aufrufs die zugehörige Adresse dem Linkblock des betreffenden Handlers; bei Heap-Zuweisungen ebenso.Ein Heap-Bereich hat in den ersten 16 Bytes einen Header, wie z.B. auch die QDOS-Kanaltabellen. Dort steht auf Offset 4 die Linkadresse CH.LIO des Handlers. Mit Offset 12 dazu ist auf Position CH.ACLOS die Adresse der Freigabe-Routine verzeichnet. Hier besorgt sie die Rückgabe des reservierten Bereichs an den "common heap". Dieser Tabellenkopf ist nun anzupassen:
Man merkt sich die auf Position 4 (CH.DRIVR) vorgefundene Handleradresse. Dort trägt man nun eine Adresse ein, die um 12 niedriger ist als eine neue Tabellenposition (entspr. CH.LIO). An dieser Stelle wird dann die Adresse eines kurzen Umleitungsprograms eingetragen, das ROM-tauglich seinerseits Platz im gerade reservierten Heap finden kann. Dieses kleine Hilfsprogram sorgt für den Sprung z.B. in die oben angegebene Routine zur Entfernung des Timers aus der Interrupt-Liste und danach in die ursprügliche "close"- rsp. Freigabe-Routine, wie sie aus der in CH.DRIVR angegebenen Handleradresse und daraus über CH.LIO und CH.ACLOS ermittelt wurde. Das sieht etwa so aus:
Standard Heap-Header:
[ heap-len ][ handler ][ ownerjob ][ system ][ reservierung ]Die Basisadresse des nutzbaren Bereichs liefert mt.alchp in a0.
Es wird nun geändert:[ xx ][ adr1 ][ xx ][ xx ][ jsr timeunl jmp (12(hdl)) ][ adr2 ]wo (adr1) die adr2-12 enthält, die auf den jsr-Befehl zeigt.
linkdev moveq #-1,d2 "aktueller" job ist besitzer moveq #16,d1 für 2 abs.l sprungbefehle + adresse moveq #24,d0 mt.alchp trap #1 move.l d0,d3 ggf. bei memoryüberlauf bne.s jobremv ? den job ganz aufgeben move.l a0,a1 memorybasis merken move.w jsrcode(pc),(a0)+ lea timeunl(pc),a2 ablage: jsr timeunlk move.l a2,(a0)+ move.w jmpcode(pc),(a0)+ move.l -12(a1),a2 ablage: jmp memclose move.l 12(a2),(a0)+ move.l a2,d3 prüfung (notwendig bei smsq!) bne.s nsmsq ? adresse vorhanden move.w $0c2,a2 sonst mm_rechp move.l a2,-4(a0) verwenden nsmsq move.l a1,(a0) neuer "close"-pointer zeigt move.l a1,-12(a1) nun auf mem+12 = a1+12, wo rts der hilfsaufruf verzeichnet ist jobleave moveq #0,d3 keinen fehlercode weitergeben jobremv moveq #-1,d1 selbst moveq #5,d0 mt.frjob trap #1 rts nur, wenn was schief ging .... jmpcode jmp $11223344 ... zum beispiel, wenn der code jsrcode jsr $11223344 für jmp... und jsr... unbekannt ist
Sind alle diese Vorbereitungen abgeschlossen, erfolgt der Aufruf: .
.... program jsr linkdev(pc) jsr timelnk(pc) .... program jmp jobleave(pc)
Der Job wird nun unter allen Umständen stets das System geordnet zurücklassen. Auch die Auflösung durch Befehle wie QR oder RJOB wirkt einwandfrei.
* Basic-Parameter
Ergänzend ein Verfahren, mit dem der Name einer Variablen geholt werden kann. Vorausgesetzt wird die Registerbelegung wie für die Vectoren zum Holen der sonstigen Basic-Parameter. Zurückgegeben werden die Werte entsprechend dem Aufruf ca.gtstr. Einsprung "nam_vec" erlaubt schrittweise das Holen mehrerer Namen. Auf dieselbe Weise können die Aufrufadressen residenter Funktionen und Proceduren ermittelt werden. Einige dieser Routinen verlangen D7 := 0 (z.B. PRINT und BEEP).Die reichlichen Sicherheitsabfragen müssen nicht immer erforderlich sein. Sie sind als Schutz bei smasheden Daten gedacht, und evtl. auch nützlich für den Fall mangelhafter Dokumentation des Systems. Namen werden in Zusammenhang mit einer möglichst sicheren Kontrolle gegen Fehler in der Länge auf 127 Bytes begrenzt.
* REIN: a3 a5 a6 Basic-Register der Namen und der Jobbasis
* RAUS: d0 Fehlercode d3 Anzahl (1/accu) a1 String-Basisadresse
* VERDIRBT: d1 d2 d3 d4 a0 a2 und paßt a3 bei Erfolg an
nam_ftch moveq #0,d3 erstmaligen aufruf vorbereiten nam_vec moveq #6,d0 err.nf vorbereiten moveq #15,d2 typenmaske and.b 0(a3,a6.l),d2 aus dem use-byte beq.s nam_fnd ? übergabe kann gelten subq.b #1,d2 beq.s get_nich ? ausdruck subq.b #8,d2 bgt.s get_nich ? verwendung rätselhaft nam_fnd move.l a3,a0 copie zum suchen move.w 2(a0,a6.l),d1 name-index not.l d1 gegen -1 absichern beq.s get_nich ? doch ein ausdruck not.l d1 wert wiederherstellen ext.l d1 vereinfacht die prüfung lsl.l #3,d1 des ermittelten ptr ** Rom-Versionen vor JS ohne Addition aus bv.ntbas von 24(a6) ** nam_tab add.l 24(a6),d1 position in der name-table btst #0,d1 bei smasheden daten bne.s get_nich ? adressenfehler vermeiden move.l d1,a0 move.w 2(a0,a6.l),d1 ptr zum namen bmi.s get_nich ? weil es den namen nicht gibt move.w d1,a0 ptr sichern move.l 32(a6),d1 basis der name-list add.l a0,d1 ptr auf den namen moveq #0,d4 register freimachen move.b 0(a6,d1.l),d4 count.b holen ble.s get_nich ? zu lang oder leer move.l d4,d1 übertragen addq.l #3,d1 dazu count.w der ablage and.w #-2,d1 geradzahlig move.w d3,-(sp) accu kellern move.w $11a,a2 mit bv.chrix jsr (a2) basic-memory reservieren move.w (sp)+,d3 accu zurückholen moveq #3,d1 erneute längenermittlung add.l d4,d1 geschieht schneller als die and.w #-2,d1 stackablage move.l $58(a6),a1 zum alten wert sub.l d1,a1 neue länge im übergaberegister move.l a1,$58(a6) auf bv.rip fixieren move.l a1,a2 arbeiscopie move.w d4,0(a2,a6.l) länge eintragen subq.w #1,d4 zähler add.l 32(a6),a0 bv.chrix kann ptr verändert haben nam_put move.b 1(a0,a6.l),2(a2,a6.l) addq.l #1,a2 byteweise addq.l #1,a0 dbra d4,nam_put alle zeichen übertragen addq.l #8,a3 parm-ptr justieren addq.w #1,d3 anzahl eins weiter moveq #-1,d0 und ohne fehler get_nich not.l d0 ggf. err.om wiederherstellen rts Damit kann ein universeller Aufruf, der statt eines String automatisch den Namen holt, wenn er als Variable nicht definiert ist, so aussehen: stg_parm moveq #0,d3 zähler vorbereiten stg_nxtn move.l 4(a3,a6.l),d2 werte-ptr not.l d2 beq.s stg_name ? vari ohne wert tst.w d3 wenn schon namen da sind bne.s inp_rdy ? besser nicht zum vector stg_name bsr.s nam_vec > den namen als string holen beq.s stg_nxtn ? weiter (ver)suchen tst.w d3 anzahl beq.s inp_tst ? weiter mit 'mi' bei fehler inp_rdy moveq #0,d0 fehlercode war schlußsignal bra.s inp_oke - zur auswertung inp_vec move.w $0116,a2 ca.gtstr als jsr (a2) > regulärer aufruf inp_tst tst.l d0 fehlercode prüfen bne.s inp_err ? es gab einen fehler inp_oke .... .... auswertung und inp_err .... .... fehlerbehandlung
* System-Erkennung
Es gibt verschiedene Emulatoren des QL in den verschiedensten Systemen. Sinn solcher programe ist es u.a. auch, den QL so nachzubilden, daß die Tatsache der Emulation aus dem "Innern" nicht erkennbar wird. Andrerseits gibt es dort im allgemeinen wesentliche Lücken, aufgrund derer manches QL-program entweder nur in besonderer Variante oder garnicht einsetzbar ist. Solche programe scheitern meist an einer dem Anschein nach wohl vorhandenen, nicht aber originalgetreu nachgebildeten Funktion des Rechners.Zur Prüfung gibt es verschiedene Mittel, wie auch verschiedene Kriterien. Unabdingbare und entscheidende Voraussetzung ist allerdings, daß für die Emulation die QDOS-Konventionen und Entsprechendes der diversen Systemerweiterungen (TK2, etc) strikt beachtet wurden.
Wer ein Nicht-Standard-System nachbildet, sollte insbes. die gerade zur Unterscheidung der Varianten eingerichteten Variablen nutzen, ebenso, und auch bei allen anderen Emulationen, die weiteren eindeutig festgelegten Hilfmittel - oder die Nachbildung hinreichend exakt machen (wie z.B. QLAY).
Dies unbeachtet zu lassen, stiehlt den durch die zu erwartende Fehlfunktion gequälten Anwendern unnötig an solch untaugliche Produkte vergeudete Zeit. "Alpha"- & "Beta"-Ausreden sind da nicht sonderlich glaubhaft.
Dieser Test ist einfach, denn jedes System, in dem die Trap FS.XINF (3/79) fehlerfrei ausgeführt werden kann, gilt als "level 2" Filesystem, wo die zusätzlicche Funktionalität grundsätzlich gegeben ist, und Ergänzungen zur Ur-Version mittels der zurückgegebenen Daten überprüft werden können.
1. Direkt:
Vorhandensein des wirklichen I.P.C. kann mit dem dazu vorgesehenen Aufruf TEST.CMD der Trap MT.IPCOM (1/17) festgestellt werden: Nur, wenn der I.P.C. entweder physisch vorhanden und in Ordnung ist, oder die Emulation ihn ohne jede Ausnahme vollständig und korrekt nachbildet, wird das Parameterbyte der Definition gemäß unverändert zurückgegeben.
Einmalige Wiederholung der Abfrage mit einem anderen Parameterbyte genügt, um zufällige Ergebnisse auszuschließen.2. über Systemvariable
Liefert das Uhrenregister über mehrere Sekunden hinweg stets denselben Wert, ist sicher irgendeine Emulation in Betrieb, und der IPC ist nicht vorhanden oder äußerst mangelhaft nachgebildet.
Von Experimenten mit speziellen Processorbefehlen, die nur auf dem einen oder dem anderen Gerät ausführbar sind und sonst in eine Ausnahem-Trap führen, wird dringend abgeraten. Die Folgen sind oft unangenehmer, als beim Betrieb des betr. ungeeigneten programs (programe z.B., wo direkt codiert an den Cache-Zuständen herumgefummelt wird).
Es gibt sichere Alternativen! Als da sind:
1. Der Cache-Test mit SMS.CACH (1/47) gibt zum einen Aufschluß über den Status der Processorcache, zum andern aber auch, ob überhaupt ein geeigneter Processor installiert ist. Bei 6800x oder 6801x kommt ERR.BP zurück, da es dort dergleichen nicht gibt. Auch die üblichen Emulatoren werden, sofern sie nicht eine SQ-Vaiante abbilden, im allgemeinen die ERR.BP-Rückgabe auslösen.
Die Trap arbeitet beim Lesen ohne jede Ausweirkung auf das Sytem, kann also sicher zum Test benutzt werden.2. Systemvariable
Die Posten SYS.PTYP ($A1) und SYS.MTYP ($A7) geben recht detaillierte Aukunft über Processor-, Bildschirm- und Geräte-Typ, sofern sie denn vom System bedient werden - was leider nicht in allen anwendbaren Fällen zutrifft.
Mehr dazu an entsprechender Stelle im Tabellenteil.Wer ein Nicht-Standard-System nachbildet, sollte diese Variablen nutzen, und sie ggf. durch Anmeldung an geeigneter Stelle "offiziell" machen. Er vergibt sich damit nichts!
Sie unbeachtet zu lassen, beraubt den durch die zu erwartende(!) Fehlfunktion gequälten Anwender nur der an solch untaugliche Erzeugnisse vergeudeten Zeit. Und es ist sicherlich auch schade um den Rest der ansonsten vielleicht ganz vortrefflichen Arbeit, deren Wert durch Fehlen eines kleinen aber bedeutsamen Details zunichte gemacht wird.
Ungeeignete Vorgehensweise führt gerade hier in vielen existierenden programen leicht zu Fehlern.
So ist etwa von vornherein offensichtlich, daß insbes. die Größe einer cdt kaum zur Unterscheidung brauchbar ist. Gerade dieser Wert hat aus der Sicht einer Anwendung in weitestem Sinne nicht das geringste mit einem bestimmten Kanaltypen zu tun.
Wenn dennoch aus der Größe der CON-cdt auf Vorhandensein des PIF oder einer anderen CON-Erweiterung geschlossen wird, mag das angehen, und es ist wegen der u.U. gänzlich nutzlosen Prüfroutinen des PIF mitunter das einzige Mittel.
Handlervergleich mit Kanal 0 ist aber für CON-Kanäle, falls nicht gerade ein Wahnsinniger ausgerechnet diesen wichtigen QDOS-Kanal geschlossen oder umdefiniert hat, die schellere und zuverlässigere Prüfung.Und ist zwar Lesen der Bildparameter mit z.B. SD.CHENQ (3/11) möglich, oder liefert es ERR.NC (-1), der Handler aber nicht derselbe wie der des Kanals 0, so hat man es mit einem SCR-Kanal zu tun.
Als weiteres Mittel, das insbes. in größeren oder über einen längeren Zeitraum zu betreibenden programen probat ist, bietet sich vorübergehendes Öffnen eines Prüfkanals der gewünschten Art an, memoryn dessen Handleradresse in den eigenen Variablen, und fortan Analyse der fraglichen Kanäle anhand einer so gewonnenen Sammlung.
Soll aber z.B. die exakte Länge 160 als Kriterium für einen Directory-Device-Kanal herhalten, so ist das nicht nur verfehlt, sondern geht auch weit an der längst bekannten Dokumentation vorbei (im Original heißt es "mindestens 160 Bytes..."). Geeignete programierung vorausgesetzt, ist die dadurch gewonnene Auskunft zudem völlig überflüssig. Wird sie aber aus welchem Grunde auch immer dennoch benötigt, liefern die Handler-Listen des Systems die gesuchten Angaben, ohne daß irgendwelche Daten "gewußt" werden müßten, und das in völlig systemunabhängiger Form.
Gerade die Unterscheidung zwischen Kanälen des einfachen und des Directory-Typs ist aufgrund der vielen im System verfügbaren Angaben außerordentlich einfach, sicher und von der aktuellen Konstellation unabhängig durchführbar.
Das geht hin bis zur vollständigen Bestimmung ganzer Kanalnamen, die gerade für Directory-Devices mit Hilfe allein der verschiedenen Systemvariablen möglich ist.
Mehr auch hierzu an entsprechender Stelle im Tabellenteil.
Es ist gelegentlich erforderlich, zu wissen, ob das Betriebssystem im RAM liegt, oder im ROM rsp. einem hardwaremäßig schreibgeschützten Adressenbereich. Das läßt sich unabhängig vom betr. Gerät und dem eingesetzten Processor mit folgender Codepassage in Erfahrung bringen:
trap #0 (supv) move.l sp,d2 leerer stack moveq #0,d0 prüf-flag "System im ROM" move.w #8,a2 bus-error vector move.l (a2),-(sp) lesen geht immer pea (a2) vector move.l sp,d1 fehler-stack merken pea berr(pc) vector und zur prüfung move.l (sp),(a2) springt ggf. in exception <berr> cmp.l (sp)+,(a2)+ seq d0 ? 'ne' bei code im ROM move.l d1,sp bus-error-trap restaurieren move.l (sp)+,a2 ROM oder nicht ist move.l (sp)+,(a2) hier nun unbedeutend berr move.l d2,sp ursprünglicher stackptr and.w #$dfff,sr (user)