Analyse der Druckerausgabe: Exceptions in Modula-2

Moderne Drucker bieten eine Option an, die es ermöglicht, empfangene Zeichen und Steuercodes in einer Hexdump-ähnlichen Weise auszugeben. Wer diese Zusatzfunktion bei seinem Drucker vergeblich sucht, kann auf ein Acces-sory zurückgreifen, das diese Aufgabe übernimmt.

Das Accessory belegt einen Eintrag namens “Drucker Hexdump” im Desk-Menü. Öffnet man die Meldebox, kann man jederzeit die Sonderfunktion an- oder ausschalten. Beim Ausschalten wird jedesmal ein CR und LF an den Drucker geschickt, um den Puffer zu entleeren. Dies ist notwendig, da die Steuerzeichen nicht ausgeführt werden und der Drucker eine unvollständige Zeile erst ausgibt, wenn er ein CR bekommt. Das Programm soll gleichzeitig ein Beispiel dafür sein, wie man die Umlenkung der Exception-Vektoren von einer Hochsprache ausprogrammieren kann.

Das Hauptprogramm

Das Hauptprogramm trägt sich in die Accessory-Liste ein und wartet zunächst auf eine Meldung vom GEM. Falls die Accessory Open-Meldung eintrifft, wird nach Setzen des richtigen Defaults die Box eröffnet. Wählt der Anwender den An-Knopf zum ersten Mal, wird mit Hilfe der BIOS-Funktion GetException der alte Trap 13-Vektor eingelesen und gesichert. Dabei ist zu beachten, daß dies der 45. Systemvektor ist. Danach wird mit SetException der neue Vektor eingetragen. Die Hilfsvariable active verhindert ein Überschreiben des gesicherten Vektors für den Fall, daß der An- Knopf ein zweites Mal betätigt wird. Schaltet man die Option wieder aus, wird der alte Exception-Vektor restauriert,so daß der Prozessor bei einem BIOS-Aufruf wieder direkt ins Betriebssystem springt.

Die Prozedur newexception

Ist die neue Funktion aktiviert, landet der Prozessor bei jedem Trap 13 am Beginn der Prozedur newexception. An dieser Stelle schafft sich der Compiler Platz auf dem Stack, um seine lokalen Variablen unterzubringen. Dabei bedient er sich des Maschinenbefehls link x,a6, wobei für x eine negative Zahl eingesetzt wird um die sich der Stackpointer (SP) erniedrigt. Vorher wird jedoch der Inhalt von Register a6 auf den Stack gebracht und der SP in a6 geschrieben. Als nächstes sichert man am besten alle restlichen Register auch auf dem Stack. Das Register a4 wird noch einmal extra gesichert, da später für den Rücksprung noch ein Register benötigt wird. An die Parameter des BIOS-Aufrufs kommt man, indem man den in a6 abgelegten SP plus 4 betrachtet. Dieser Wert wird dem Zeiger auf das Record zugewiesen, so daß elegant auf die Parameter zugegriffen werden kann. Zunächst muß nun überprüft werden, ob es sich überhaupt um einen BConOut-Aufruf handelt, der ausgeführt wird, sobald irgendein Programm eine Ausgabe tätigt. Dazu muß man die Funktionsnummer auf 3 überprüfen. Ist nun auch noch der Drucker durch den Device 0 angesprochen, verzweigt das Programm in die neue Ausgabefunktion. Die ConvertHex-Prozedur wandelt den Zeichencode in das hexadezimale Format um. Zur besseren Darstellung wird bei einstelligen Hex-Zahlen noch etwas korrigiert. Die Ausgabe kann nun nicht über Standardprozeduren erfolgen, da diese wieder rekursiv in newexception landen würden. Deshalb wird die Prozedur out benötigt. Der Zähler counter wird bei jedem Durchlauf um 1 erhöht und bewirkt bei Erreichen einer Marke das Ende einer Zeile auf dem Drucker durch Ausgabe eines CR- und LF-Codes. Nach der Ausgabe muß man für den Rücksprung sorgen. Zunächst werden alle Register bis auf a6 vom Stack geholt. Anschließend besorgt die unlk a6-Operation die gleiche Stackkonfiguration wie am Anfang der Prozedur, und rte-Befehl. erledigt den Rücksprung. Bleibt noch, einen Sprung ins Betriebssystem vorzusehen, falls kein BConOut auf den Drucker erfolgen sollte. Dazu werden auch zuerst die Register wiederhergestellt, bevor die unlk-Operation durchgeführt wird. Nun wird das Register a4 benötigt, welches die Adresse oldexception aufnimmt, um sie auf den Stack zu bringen. Danach ist nach Wiederherstellung von a4 nur noch ein rts-Be-fehl nötig, um den Sprung auszuführen. Dabei ist zu beachten, daß a4save eine globale Variable ist, damit SETREG(12,a4save) in einen einzigen move.l-Befehl übersetzt werden kann und keine Register mehr benutzt.

Die Prozedur out

Die Prozedur out gibt die Zeichen aus, indem ein BIOS-Aufruf simuliert wird. Es werden dabei die Parameter entsprechend auf dem Stack bereitgestellt, bevor ein Sprung in die alte Betriebssystemprozedur ausgeführt wird.

Als erstes muß man Platz auf dem Stack für die BlOS-Parameter schaffen. Dazu ermittelt man mit der Systemfunktion TSIZE die Länge des Records und erniedrigt den Stack um diesen Wert. Gleichzeitig wird der Zeiger bios geladen. Als nächstes werden die Parameter in die Liste eingetragen. Dabei muß man auch die Rücksprungadresse ermitteln, da am Ende der Betriebssystemprozedur ein rte-Befehl steht. Das bedeutet, daß Statusregister und Programmzähler (PC) vom Stack geladen werden, so daß man nicht mit einem jsr-Befehl einspringen kann. Zunächst wird durch einen bsr2(PC)-Befehl ein Unterprogrammaufruf simuliert, der freundlicherweise ein Speichern des PCs auf dem Stack bewirkt. Dabei wird aber lediglich der nop-Befehl übersprungen. Als nächstes wird der PC vom Stack nach a3 gebracht und nach Addition von 28 als Rücksprungadresse abgelegt. Die 28 entsteht aus dem Abstand zwischen dem bsr-Befehl und der Stelle, wo der Rücksprung landen soll. Man kann dies an den beigefügten Längenangaben der Befehle im Programm nachvollziehen. Die letzten 3 Maschinenbefehle dienen wieder zum Einspringen in die Betriebssystemprozedur. Hat diese ihre Arbeit getan, muß der Stack korrigiert werden. Der SP steht zu diesem Zeitpunkt vor biosnr, denn Status und PC wurden bereits vom Stack geholt. Deshalb wird nur len-6 zum SP addiert.

Hoffentlich ist das Prinzip klar geworden. Man kann den Rumpf der Prozeduren, insbesondere die maschinensprachlichen Teile, für ähnliche Aufgaben wie z.B. Drucker-Spooler, Drucker-Anpassung an Sonderzeichen direkt übernehmen. Es ist durch einfaches Ändern des Records möglich, auch andere BIOS-, XBIOS- oder GEMDOS-Funktionen abzufangen und zu verändern. Es sollte jedoch vermieden werden, innerhalb der neuen Exception-Proze-dur Betriebssystemfunktionen aufzurufen, da dies in der Regel zu Abstürzen führt.

Bild: So wird der Hexdump auf dem Drucker ausgegeben

Bild: So wird der Hexdump auf dem Drucker ausgegeben

IMPLEMENTATION MOOULE DumpPrt;

	(* Dies ist ein Oesk Accessory und muß *)
	(* deshalb mit GEMACCX.LNK an Stelle uon OEMX.LNK gelinkt werden ! *)

(*$A+,$T-,$S-*) (* Code optimieren, keine Bereichsüberprüfung *)

FROM GEMAESbase	IMPORT	AccessoryOpen;
FROM AESApplications IMPORT	Appllnitialise;
FROM AESEvents	IMPORT	EuentMessage;
FROM AESForms	IMPORT	FormAlert;
FROM AESMenus	IMPORT	MenuRegister;
FROM SYSTEM	IMPORT TSIZE,ADDRESS,ADR,CDE, SETREG,REGISTER, LONGWORD;
FROM BIOS	IMPORT	GetException,SetException, BConOut,PRT:
FROM M2ConVersions IMPORT	ConuertHex;
FROM Terminal	IMPORT	WriteString,Writeln;
FROM ASCII	IMPORT	CharIsPrintable;

(* So sieht der Stack aus, wenn man nach einem
BConOut-Aufruf i.d. Exception-Prozedur ankommt, *) 
TYPE stackconfig = RECORD Status : CARDINAL;
						pc : ADDRESS; 
						biosnr : CARDINAL; 
						deVice : CARDINAL; 
						dummy : CHAR; 
						char : CHAR
					END;
	biosstack = POINTER TO stackconfig;

VAR handle,counter, 
	choice.default,
	applID.menuID	:	INTEGER:
	a4saue,oldexception :	ADDRESS;
	poldexc	:	PROC;
	AlertText	:	ARRAY[0..79] OF CHAR;
	MessageBuffer	:	ARRAY[0..7] OF CARDINAL;
	actiVe	:	BOOLEAN;

	PROCEDURE out(c:CHAR); (* Ausgeben eines Zeichens 
				durch Aufruf der alten BlOS-Prozedur *)

	VAR bios:biosstack; len:ADDRESS;
	BEGIN
		len := TSIZE(stackconfig); (* Länge d.Parameterliste feststellen *) 
		bios:= REGISTER(15)-len; (* Vom akt.Stack-Pointer abziehen *) 
		SETREG(15,bios);	(* Neuen SP wieder abspeichern	*)
							(* und Parameter eintragen:	*)
		bios^.Status := 2000H; (* SuperVisor Mode nach Rückkehr *) 
		bios^.biosnr	:=	3;	(*	BConOut *)
		bios^.deuice	:=	0;	(*	Printer *)
		bios^.char	:=	c;	(*	Das Zeichen	*)
		bios^.dummy	:=	0C;	(*	Vorsichtmaßnahme *)
							(* Rücksprungadresse berechnen *)
		CODE(06102H); (* bsr 2(pc) pc -> sp *) 
		CODE(04E71H);	(* nop	+2	*)
		CODE(0265FH);	(* move.l	(sp)+,a3	+2	*)
		bios^.pc:=REGISTER(11)+28; (*	+14	*)
		SETREG(12,oldexception); (*move.l	oldexc,a4	+6	*)
		CODE(02F0CH);	(* move.l	a4,-(sp)	+2	*)
		CODE(04E75H);	(* rts	+2	*)
		(* Hier müssen wir nach der Ausgabe ankommen *) 
		SETREG(15,REGI5TER(15)+len-6); (* Stack-Korrektur *)
	END out;

	PROCEDURE newexception;
	VAR bios:biosstack; hex:ARRAY[1..2] OF CHAR;
	BEGIN	(* link a6	*)
		CODE(048E7H, 0FFFCH); (* movem.l d0-a5.-(a7) *) 
		a4save:=REGISTER(12); (* move.l a4,a4save *)
		bios:=REGISTER(14)+4; (* moue.l a6+4,bios *) 
		IF (bios^.biosnr=3) AND (bios^.device=0) THEN (* BConOut Printer? *) 
			ConvertHex(ORD(bios^.char),2,hex);
			IF hex[2] = 'N' THEN hex[2]:=hex[1];
				hex[1]:=' ' END;
			out(hex[1]); 
			out(hex[2]);
			out(' ');
			IF CharIsPrintable(bios^.char)
				THEN out(bios^.char)
				ELSE out('.') END;
			out(' ');
			IF counter=12 (* 13 Hex-Blöcke pro Zeile *)
				THEN out(15C); out(12C): counter:=0 
				ELSE out(' '); INC(counter) END; 
			CODE(04CDFH,03FFFH);
						(*	mouem.l	(a7)+,d0-a5	*)
			CODE(04E5EH); (* unlk a6	*)
			CODE(84E73H); (* rte	*)
		ELSE
			CODE(04CDFH.03FFFH);
						(*	movem.l	(a7)+,d0-a5	*)
			SETREG(12,oldexception);
						(* move.l oldexception,a4 *) 
			CODE	(04E5EH);	(*	unlk	a6	*)
			CODE	(02F0CH);	(*	move.l	a4,-(a7)	*)
			SETREG(12,a4save);
								(*	move.l	a4saue,a4	*)
			CODE	(04E75H);	(*	rts	*)
		END
	END newexception;

	PROCEDURE ConVLongs(A:LONGWORD;VAR B:LONGWORD); 
	BEGIN 
		B:=A;
	END ConVLongs;

BEGIN
	applID:=ApplInitialise(); 
	active:=FALSE;
	menuID:=MenuRegister(applID,' Drucker Hex Dump'); 
	AlertText:='[2][ Drucker Dumping|	uon |Michael Schaffner][An|Aus]';
	LOOP
		EVentMessage(ADR(MessageBuffer));
		IF MessageBuffer[0]=AccessoryOpen THEN 
			IF active THEN default:=2
				ELSE default:=1 END; 
			choice:=FormAlert(default,AlertText):
			IF (choice=2) AND active THEN 
				(* Aus *)
				ConVLongs(oldexception,poldexc); 
				SetException(45,poldexc); (* alten Vektor restaurieren *)
				active := FALSE;
				BConOut (PRT, 15C); BConOut (PRT,12C) END;
					(* CR und LF ausgeben *) 
			IF (choice=1) AND NOT active THEN (* An *)
				counter:=0;
				oldexception:=GetException(45); (* alten Trap 13 Vektor sichern *) 
				SetException(45,newexception); (* neuen Trap 13 Vektor setzen *) 
				actiVe := TRUE END;
		END;
	END;
END DumpPrt.

Michael Schaffner
Aus: ST-Computer 05 / 1988, Seite 159

Links

Copyright-Bestimmungen: siehe Über diese Seite