Trapper V2.00
=============

Geschrieben von Manfred Lippert
Email: mani@mani.de
Web:   http://www.mani.de/


Was ist Trapper?
================

Trapper ist ein Programm, das die Entwicklung von Systemerweiterungen
fr Programmierer enorm vereinfacht.

Mit Hilfe von Trapper knnen sich andere Programme effektiv und
einfach vor und hinter Systemaufrufe hngen, diese sogar ganz
ersetzen, oder dem System neue Systemaufrufe hinzufgen.

Dazu ist keine Assemblerprogrammierung mehr notwendig. Man kann sofort
jede beliebige C-Routine ins System einklinken. Andere
Programmiersprachen sind ebenfalls denkbar.


Installation
============

Trapper gehrt in den AUTO-Ordner. Zu Testzwecken kann es auch
nachtrglich vom Desktop aus gestartet werden.

Zu beachten ist, da Trapper natrlich vor allen Systemerweiterungen
gestartet werden mu, die Trapper bentigen.

Unter MagiC benutzt man zur Festlegung der AUTO-Ordner-Reihenfolge am
besten die Datei AUTOEXEC.BAT (siehe MagiC-Doku).


Freeware
========

Trapper darf beliebig - aber nur zusammen mit diesem Text und beides
unverndert - weitergegeben werden.

Trapper darf nur von Freeware-Programmen benutzt werden. Soll Trapper
von kommerziellen Programmen genutzt werden, so mu dies mit mir
abgesprochen werden: Mail an mani@mani.de.


Ich will es genauer wissen
==========================

Fr viele clevere Tools ist es notwendig, sich in bestimmte
Systemaufrufe der verschiedenen Systemschichten (Bios, Xbios, Gemdos,
VDI oder AES) einzuklinken. Dazu waren bisher immer
Assembler-Kenntnisse notwendig. Die grte Schwierigkeit besteht
darin, da die neu eingeklinkten Funktionen vollkommen reentrant sein
mssen.

Solange man sich nur _vor_ eine Systemfunktion hngt, ist das nicht
weiter schwierig: Man verbiegt den Vektor der Systemfunktion auf eine
eigene Funktion und in nach der eigenen Funktion springt man wieder an
die originale Funktion. (Am besten macht man das mit dem
XBRA-Verfahren.)

Enorme Probleme handelt man sich jedoch ein, wenn man sich _hinter_
Systemfunktionen hngen will, z.B. um Returnwerte zu manipulieren,
oder um Aktionen ausfhren zu knnen, die man nur um User-Modus machen
darf: Man mu den Rcksprung-Zeiger (der auf dem Stack liegt) auf eine
eigene Routine verbiegen und dann den Systemaufruf normal weiterlaufen
lassen. Kehrt der Systemaufruf zurck, hat man wieder die Kontrolle
und mu anschlieend natrlich wieder an die ursprngliche
Return-Adresse zurckspringen. Letzteres birgt die Schwierigkeit: Wo
merkt man sich die alte Returnadresse, damit das ganze reentrant
bleibt? Da mu man sich schon Tricks einfallen lassen ... ;-)

Jeder Programmierer, der ein tooles Tool schreiben will, das sich in
Systemaufrufe hngt, hat mit dieser Problematik zu kmpfen. Als
Nebeneffekt wird das System bei jedem solchen Tool geringfgig
langsamer und der Stackverbrauch hher - evtl. so hoch, da es zu
Systemabstrzen kommen kann...

Hier versucht Trapper in mehrfacher Hinsicht Abhilfe zu schaffen:

Anstatt sich selbst in Systemaufrufe hngen zu mssen, berlt man
dies einfach Trapper und meldet die einzuklinkenden Funktionen bei ihm
an.

Das hat folgende Vorteile:

- Man mu sich nicht mit komplizierter Assembler-Programmierung
herumschlagen. Man kann sich mit ganz normalen C-Funktionen sowohl vor
als auch hinter (!) beliebige Systemaufrufe klinken. (Auch andere
Programmiersprachen sind denkbar.) Die Entwicklung von
Systemerweiterungen wird dadurch enorm vereinfacht.

- Es hngt nur noch eine Instanz in den Systemaufrufen, wodurch
Geschwindigkeit und Stackverbrauch konstant sind.

- Man kann Trapper mitteilen, fr welchem Funktionsopcode die
eingeklinkte Funktion zustndig sein soll. Somit entfllt das
Dispatchen von Opcodes jedes einzeln eingeklinkten Programms und das
System wird nicht gebremst. Trapper findet eingeklinkte Funktionen
besonders schnell ber spezielle Algorithmen (Bume und Listen). Der
Overhead wird selbst bei enorm vielen eingeklinkten Funktionen nicht
in der Geschwindigkeit sprbar.

- Ein Programm kann sich problemlos wieder aus Systemaufrufen
ausklinken und sich beenden. Hngt das Programm selbst direkt im
Systemaufruf und verbiegt Returnadressen, so hat es das enorme
Problem, da es sich erst beenden kann, wenn alle mit verbogener
Returnadresse aufgerufenen Funktionen wieder zurckgekehrt sind. Viele
Systemaufrufe kehren aber u.U. sehr lange nicht zurck, z.B. Pexec()
oder die evnt_-Funktionen des AES. Da man ber Trapper "indirekt" in
den Aufrufen hngt, ist das Aushngen problemlos mglich.


Fr Programmierer
=================

Trapper klinkt sich ins Bios, Xbios, Gemdos, VDI und AES. Als
Schnittstelle zu Trapper wurde die Xbios-Funktion Nummer 555 mit
folgender Aufrufkonvention gewhlt:

int32 Trapper(int16 layer, int16 install, int16 opcode, void *function);

Man sollte unbedingt den Rckgabewert von Trapper testen. Falls das
Einklinken funktioniert hat, wird E_OK geliefert, ansonsten ein
anderer Wert (z.B. ENSMEM, falls kein Speicher mehr verfgbar ist).

Die Parameter:

layer: Gibt die Systemschicht an, in die eine Funktion eingeklinkt
werden soll. Folgende Werte sind mglich:

#define TRAPPER_CHECK  -1
#define TRAPPER_BIOS    0
#define TRAPPER_XBIOS   1
#define TRAPPER_GEMDOS  2
#define TRAPPER_VDI     3
#define TRAPPER_AES     4

TRAPPER_CHECK: Spezielle Funktion, um das Vorhandensein von Trapper zu
testen. "install", "opcode" sollten auf 0 gesetzt werden, "function"
ist entweder NULL oder zeigt auf folgende Struktur:

typedef struct {
	int16 version;     /* BCD coded, e.g. 0x110 for Version 1.10 */
	int16 reserved[15];
} TrapperInfo;

Ist Trapper installiert, so liefert Trapper(TRAPPER_CHECK) den Wert
E_OK (0) und fllt obige Struktur aus. Ansonsten wird ein Wert
ungleich E_OK geliefert, und man kann Trapper nicht benutzen.

TRAPPER_BIOS, TRAPPER_XBIOS, TRAPPER_GEMDOS, TRAPPER_VDI und
TRAPPER_AES geben die entsprechende Systemschicht an, in die die
Funktion "function" eingeklinkt werden soll. Je nach Schicht hat
function eine (geringfgig) abweichende Aufrufkonvention (s.u.).

install: Legt fest, was mit "function" passieren soll:

#define TRAPPER_INSTALL_CALL    0
#define TRAPPER_REMOVE_CALL     1
#define TRAPPER_INSTALL_RETURN  2
#define TRAPPER_REMOVE_RETURN   3

TRAPPER_INSTALL_CALL: Installiert die Funktion _vor_ den Systemaufruf.

TRAPPER_INSTALL_RETURN: Installiert die Funktion _hinter_ den
Systemaufruf.

TRAPPER_REMOVE_CALL und TRAPPER_INSTALL_RETURN entfernen die
betreffenden Funktionen wieder aus Trapper.

opcode: Legt den Funktions-Opcode fest, bei dem die Funktion
aufgerufen werden soll. bergibt man -1, so wird die Funktion bei
_jeder_ Funktion der Systemschicht aufgerufen.

function: Zeigt auf die einzuklinkende bzw. zu entfernende Funktion.
(Ausnahme: TRAPPER_CHECK, siehe oben.)

Je nach Systemschicht (layer) und Installation (install) gelten
folgende Funktions-Typen (Aufrufkonventionen):

typedef int32 CDECL (*BiosCallFunc)(int16 *para, int16 *call_original, int16 super_called);
typedef int32 CDECL (*BiosReturnFunc)(int32 ret, int16 *para, int16 is_super);

typedef int32 CDECL (*XbiosCallFunc)(int16 *para, int16 *call_original, int16 super_called);
typedef int32 CDECL (*XbiosReturnFunc)(int32 ret, int16 *para, int16 is_super);

typedef int32 CDECL (*GemdosCallFunc)(int16 *para, int16 *call_original, int16 super_called);
typedef int32 CDECL (*GemdosReturnFunc)(int32 ret, int16 *para, int16 is_super);

typedef void CDECL (*VDICallFunc)(VDIPB *para, int16 *call_original, int16 super_called);
typedef void CDECL (*VDIReturnFunc)(VDIPB *para, int16 is_super);

typedef void CDECL (*AESCallFunc)(AESPB *para, int16 *call_original, int16 super_called);
typedef void CDECL (*AESReturnFunc)(AESPB *para, int16 is_super);

CDECL bedeutet, da die Funktion nach C-Konvention die Parameter auf
dem Stack bergeben bekommt (in Pure-C schreibt man dafr "cdecl").
int16 ist ein 16-Bit Integer (in Pure-C "int" oder "short"), int32 ist
ein 32-Bit Integer (in Pure-C "long").

Der Rckgabewert wird wie in C blich ber das Register D0
zurckgeliefert.

Die Funktionen drfen - nach C-Konvention - nur die Register
D0,D1,D2,A0,A1 verndern. Alle anderen Register mssen nach der
Funktion wiederhergestellt werden. Alle mir bekannten C-Compiler
halten sich an diese Konvention.

"para" zeigt bei Bios, Xbios und Gemdos auf den Stackbereich mit dem
Opcode und den Parametern. para[0] ist z.B. der Opcode der Funktion.
Dahinter stehen die eigentlichen Parameter, die natrlich vom Opcode
abhngig sind.

Eine besondere Rolle spielt "call_original": Diese Variable zeigt auf
ein Flag, ob nach der Ausfhrung der Funktion die originale
Systemfunktion aufgerufen werden soll. *call_original hat bei Aufruf
der Funktion immer den Wert 1 (true), mu also im Normalfall nicht
verndert werden. Will man die originale Systemfunktion ersetzen,
setzt man *call_original auf 0 (false).

Der Rckgabewert der "Call"-Funktionen spielt normalerweise nur dann
eine Rolle, wenn *call_original auf 0 gesetzt wurde, oder wenn man dem
System eine gnzlich neue Funktion hinzufgt (es also gar keine
Originalfunktion gibt). In allen anderen Fllen sollte man einfach
E_OK (0) zurckliefern.

"super_called" (bei "Call"-Funktionen) enthlt 0 (false), falls die
Systemfunktion aus dem User-Modus heraus aufgerufen wurde und 1
(true), falls sie aus dem Supervisor-Modus heraus aufgerufen wurde. Zu
beachten ist, da man sich natrlich whrend der Ausfhrung der
Funktion immer im Supervisor-Modus befindet! Man ist ja quasi "im"
Trap.

"is_super" (bei "Return"-Funktionen) enthlt 0 (false), falls man sich
gerade im User-Modus befindet und 1 (true), falls man sich gerade im
Supervisor-Modus befindet.

Man sollte unbedingt darauf achten, da die Funktionen so wenig Stack
wie mglich benutzen.

In "Call"-Funktionen, also wenn man sich vor Funktionen hngt,
befindet man sich immer im Supervisor-Modus und benutzt also den
Supervisor-Stack (der u.U. auch dem Userstack des jeweiligen Programms
entsprechen kann, falls dieses per Super() in den Supervisor-Modus
geschaltet hat).

In "Return"-Funktionen, also wenn man sich hinter Funktionen hngt,
benutzt man normalerweise den Stack des Programms, der die
Systemfunktion aufgerufen hat. Man sollte also immer im Hinterkopf
behalten, da man "fremde" Stacks benutzt, von denen man nicht wei,
wie gro sie sind, und deshalb so wenig Stack wie mglich benutzen.


Wichtig: Ein Programm, das Trapper benutzt, sollte sich (sofern es
kein TSR ist) unbedingt in etv_term einklinken und so im Falle eines
unvorhergesehenen Programmabbruches alle in Trapper eingeklinkten
Funktionen wieder ausklinken.


Sinnvolle Bindings
==================

Das "Low Level"-Binding zu Trapper (Xbios-Funktion Nummer 555) sollte
man sich fr sein verwendetes System selbst erstellen. Das
Assemblerbinding knnte z.B. etwa so aussehen, falls die
Trapper-Funktion seine Parameter nach C-Konvention auf dem Stack
bergeben bekommt:

int32 CDECL Trapper(int16 layer, int16 install, int16 opcode, void *function);

Trapper:
	pea (a2)              // TOS does not save A2 in traps
	move.l 14(sp),-(sp)   // function
	move.l 14(sp),-(sp)   // install/opcode
	move.w 16(sp),-(sp)   // layer
	move.w #555,-(sp)     // Opcode 555
	trap #14              // Xbios-Trap
	lea 12(sp),sp         // correct Stack
	move.l (sp)+,a2       // restore A2
	rts

Bei Registerbergabe sieht das ganze natrlich geringfgig anders aus.


Um das Einklinken komfortabler zu machen, ist es sinnvoll, sich z.B.
folgende "Higher Level"-Bindings zu definieren, die entsprechend das
"Low Level"-Trapper-Binding aufrufen. Das hat auerdem den Vorteil,
da bei den bergebenen Funktionen eine Typberprfung stattfindet.

int32 TrapperCheck(TrapperInfo *info)
{
	return Trapper(TRAPPER_CHECK, 0, 0, info);
}


int32 TrapperInstallBiosCall(int16 opcode, BiosCallFunc func)
{
	return Trapper(TRAPPER_BIOS, TRAPPER_INSTALL_CALL, opcode, func);
}

int32 TrapperRemoveBiosCall(int16 opcode, BiosCallFunc func)
{
	return Trapper(TRAPPER_BIOS, TRAPPER_REMOVE_CALL, opcode, func);
}

int32 TrapperInstallBiosReturn(int16 opcode, BiosReturnFunc func)
{
	return Trapper(TRAPPER_BIOS, TRAPPER_INSTALL_RETURN, opcode, func);
}

int32 TrapperRemoveBiosReturn(int16 opcode, BiosReturnFunc func)
{
	return Trapper(TRAPPER_BIOS, TRAPPER_REMOVE_RETURN, opcode, func);
}


int32 TrapperInstallXbiosCall(int16 opcode, XbiosCallFunc func)
{
	return Trapper(TRAPPER_XBIOS, TRAPPER_INSTALL_CALL, opcode, func);
}

int32 TrapperRemoveXbiosCall(int16 opcode, XbiosCallFunc func)
{
	return Trapper(TRAPPER_XBIOS, TRAPPER_REMOVE_CALL, opcode, func);
}

int32 TrapperInstallXbiosReturn(int16 opcode, XbiosReturnFunc func)
{
	return Trapper(TRAPPER_XBIOS, TRAPPER_INSTALL_RETURN, opcode, func);
}

int32 TrapperRemoveXbiosReturn(int16 opcode, XbiosReturnFunc func)
{
	return Trapper(TRAPPER_XBIOS, TRAPPER_REMOVE_RETURN, opcode, func);
}


int32 TrapperInstallGemdosCall(int16 opcode, GemdosCallFunc func)
{
	return Trapper(TRAPPER_GEMDOS, TRAPPER_INSTALL_CALL, opcode, func);
}

int32 TrapperRemoveGemdosCall(int16 opcode, GemdosCallFunc func)
{
	return Trapper(TRAPPER_GEMDOS, TRAPPER_REMOVE_CALL, opcode, func);
}

int32 TrapperInstallGemdosReturn(int16 opcode, GemdosReturnFunc func)
{
	return Trapper(TRAPPER_GEMDOS, TRAPPER_INSTALL_RETURN, opcode, func);
}

int32 TrapperRemoveGemdosReturn(int16 opcode, GemdosReturnFunc func)
{
	return Trapper(TRAPPER_GEMDOS, TRAPPER_REMOVE_RETURN, opcode, func);
}


int32 TrapperInstallVDICall(int16 opcode, VDICallFunc func)
{
	return Trapper(TRAPPER_VDI, TRAPPER_INSTALL_CALL, opcode, func);
}

int32 TrapperRemoveVDICall(int16 opcode, VDICallFunc func)
{
	return Trapper(TRAPPER_VDI, TRAPPER_REMOVE_CALL, opcode, func);
}

int32 TrapperInstallVDIReturn(int16 opcode, VDIReturnFunc func)
{
	return Trapper(TRAPPER_VDI, TRAPPER_INSTALL_RETURN, opcode, func);
}

int32 TrapperRemoveVDIReturn(int16 opcode, VDIReturnFunc func)
{
	return Trapper(TRAPPER_VDI, TRAPPER_REMOVE_RETURN, opcode, func);
}


int32 TrapperInstallAESCall(int16 opcode, AESCallFunc func)
{
	return Trapper(TRAPPER_AES, TRAPPER_INSTALL_CALL, opcode, func);
}

int32 TrapperRemoveAESCall(int16 opcode, AESCallFunc func)
{
	return Trapper(TRAPPER_AES, TRAPPER_REMOVE_CALL, opcode, func);
}

int32 TrapperInstallAESReturn(int16 opcode, AESReturnFunc func)
{
	return Trapper(TRAPPER_AES, TRAPPER_INSTALL_RETURN, opcode, func);
}

int32 TrapperRemoveAESReturn(int16 opcode, AESReturnFunc func)
{
	return Trapper(TRAPPER_AES, TRAPPER_REMOVE_RETURN, opcode, func);
}


Fragen und Antworten
====================

F: Was passiert, wenn sich mehrere Funktionen in den gleichen
Systemaufruf (gleiche Systemschicht und gleicher Opcode) hngen?

A: Sind mehrere Funktionen in einen Systemcall eingehngt, so werden
die "Call"-Funktionen (vor Originalcall eingehngt) in umgekehrter
Reihenfolge und "Return"-Funktionen (hinter dem Systemcall) in
normaler Reihenfolge ihrer Anmeldung nacheinander aufgerufen.

Bei "Calls" werden allerdings zuerst die allgemein (mit Opcode -1) auf
Opcodes angemeldeten Funktionen aufgerufen, danach die speziell auf
den betreffenden Opcode angemeldeten Funktionen. Bei "Returns"
entsprechend umgekehrt.

Bei "Calls" gilt zustzlich: Sobald eine der Funktionen das
call_original-Flag auf 0 setzt, wird die Original-Funktion ersetzt,
also anschlieend nicht mehr aufgerufen. Als Rckgabewert gilt dann
der der letzten Funktion, die call_original auf 0 gesetzt hat. Alle
weiteren in Trapper eingeklinkten Funktionen werden aber dennoch
aufgerufen. Das gilt sowohl fr die restlich eingeklinkten "Call"-
Funktionen, als auch fr die "Return"-Funktionen.
Demnchst wird es eine alternative Einklink-Mglichkeit geben, die
sich geringfgig anders verhlt. Siehe "Ausblick".

F: Wie wird die Geschwindigkeit bei sehr vielen eingeklinkten
Funktionen gewhrleistet.

A: Fr jede Systemschicht wird ein sich automatisch optimal
ausbalancierender binrer Baum (AVL-Baum) mit allen eingeklinkten
Funktionen angelegt. So knnen zu einem Funktions-Opcode die
eingeklinkten Funktionen sehr schnell gefunden werden (O(log n)).
Daher ist es wenig sinnvoll, sich in eine Systemschicht mit Opcode -1
einzuklinken und dann selbst auf die richtigen Opcodes abzuprfen, da
das Trapper wesentlich effektiver erledigen kann.

F: Aha. Und was mache ich, wenn ich dieselbe Funktion in zwei oder
mehr Funktionen (Opcodes) verwenden will?

A: Auch dann sollte man sich nicht mit Opcode -1 einklinken, sondern
die Funktion einfach mehrmals in die gewnschten Opcodes einklinken.
Die Abfrage auf die Opcodes mu man dann zwar trotzdem in seine
Funktion einbauen, aber Trapper ruft die Funktion erst gar nicht bei
anderen Opcodes auf.


Ausblick
========

Demnchst wird man vielleicht "Calls" und "Returns" paarweise
einklinken knnen. Programme, die sich auf diese Art ber Trapper ins
System einklinken, verhalten sich dann hnlicher zu "manuell" (ohne
Trapper) eingeklinkten Programmen. Setzt bei einem solchen
"Call-Return"-Paar die Call-Funktion das Flag *call_original auf 0
(false), so werden alle vorher eingeklinkten "Call-Return"-Paare nicht
aufgerufen - eben wie in Real Life. ;-)

Die bisherige Einklink-Mglichkeiten in Trapper bleiben jedoch aus
Kompatibilittsgrnden erhalten. Im Gegenteil - sie haben dann immer
noch ihre sinnvolle Daseinsberechtigung: Auf die jetzige Art
eingeklinkte Funktionen werden auf jeden Fall aufgerufen. Als "Paar"
eingeklinkte Funktionen knnen durch andere Paare ersetzt werden.
Beides kann sinnvoll sein. Um Funktionen komplett zu ersetzen, ist die
kommende Paar-Mglichkeit sinnvoller.


History
=======

V2.00, 18.3.2000
----------------

- Mechanismus zum Einklinken "hinter" die Traps wurde vollkommen
umgeschrieben. Der Stackverbraucht ist jetzt wesentlich geringer, der
Code einfacher, sauberer und schneller. Trapper sollte jetzt berall
laufen.

V1.42, 1.2.2000
----------------

- Nullpointer-Bug behoben, der auf Original-Atari-Hardware zu
Abstrzen fhren konnte.

V1.41, 27.7.1999
----------------

- Ist Trapper bereits installiert, so liefert es jetzt als Returnwert
0 statt -1 zurck, damit MagiC keinen "Schweren Fehler"-Alert mehr
zeigt.

V1.4, 29.4.1999
---------------

- Bug behoben, durch den Trapper unter Umstnden Abstrze auf
Original-Ataris verursachte.

- Code etwas optimiert, Trapper wurde ca. 4 KB krzer.

V1.3, 2.3.1999
--------------

- Debug-Session unter SingleTOS gemacht und dabei noch zwei Bugs
ausgetrieben. Trapper sollte jetzt berall sauber laufen.

V1.21, 1.3.1999
---------------

- Selten auftretendes Problem beim Einklinken per XBRA behoben.

V1.2, 25.2.1999
---------------

- Workaround fr fehlerhafte Programme, deren Startup-Code keinen
Stack einrichtet (meistens TSR-Programme).

V1.1, 19.2.1999
---------------

- Blden Bug behoben. Dadurch luft jetzt z.B. auch der Pure-Debugger
bei installiertem Trapper.

- Traps knnen bereits im AUTO-Ordner getraced werden (auer AES und
VDI).

- Die Calls werden jetzt in umgekehrter Reihenfolge ihrer Anmeldung
aufgerufen. (Returns wie gehabt.)


V1.0, 29.1.1999 bis 6.2.1999
----------------------------

- Erste Version


Viel Spa mit Trapper,
Manfred Lippert
