home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fujiology Archive
/
fujiology_archive_v1_0.iso
/
!MAGS
/
INSIDENF
/
IINFO54.ZIP
/
IINFO54.MSA
/
TEXT_CMDMENTS.TXT
< prev
next >
Wrap
Text File
|
1991-08-29
|
26KB
|
585 lines
A Programmer's Eleven Commandments for Coexistent Vector Stealing
-----------------------------------------------------------------
Or, Tried and True Techniques Used by the CodeHeads for Successfully
Intercepting Vectors in the Midst of Numerous ST Vector Thieves.
Copyright 1990 John Eidsvoog and Charles F. Johnson
(CodeHead Software)
Last revised: Wednesday, February 14, 1990 5:47:44 pm
We have prepared this document in the interest of attaining and
furthering compatibility between resident programs and accessories for
the Atari ST. Since the TOS operating system has no provisions for
managing its interrupt and trap vectors, ST developers who need to
intercept these vectors are forced to use the "trial and error" system
to determine what works.
This is a very dangerous situation. More and more programs are
appearing which enhance the ST's GEM operating system by patching into
the vectors which handle system calls. Many of these programs work
perfectly as long as no other resident programs are used, or as long as
certain combinations of programs are used. But when these programs are
released into the "real world," the conflicts quickly start showing up.
At CodeHead Software we've encountered more than our share of these
types of problems, since almost all of our commercial products intercept
one or more of the ST's system vector(s). From this boiling witches'
brew of potential pitfalls, we've managed to distill some pragmatic
methods that can alleviate most, if not all of the conflicts.
If you follow these guidelines when programming Atari ST applications
which require the interception of system vectors, you will be compatible
with _most_ of the programs currently in use. At the very least, your
code will be compatible with all of the CodeHead Software products. If
any program has general compatibility problems with other resident
programs or accessories, it's very likely that the offending program is
breaking one of the following Eleven Commandments:
--------
I.
--------
Always fall through to the previous address when your routine has
completed its function. (The only exception to this rule is if your
code replaces an entire system call; in this case, you'll probably want
to terminate your routine with an RTE. Be aware that if you do this,
any program which was previously installed in that vector will not "see"
this call come through.) The "fall through" can be accomplished by
storing the previous vector address two bytes past a JMP instruction;
this approach solves any possible problems with pushing the return
address on the stack (see Commandment V below), or destroying an address
register to do an indirect JMP.
There are some cases where it doesn't make sense to fall through to a
previous routine, such as when you replace the Alt-Help vector which
performs a screen dump. Even here, however, it's a good idea to make
allowances for other programs which may use the Alt-Help vector for
purposes other than a screen dump...such as the Templemon and AMON
debuggers. AMON avoids conflicts with other programs in the Alt-Help
vector by requiring the user to press the left shift key in addition to
Alternate and Help.
Another special case where falling through makes no sense is the ST's
vertical blank queue list, which allows you to install a routine to be
executed as a subroutine from the main system VBI. There are eight
entries in the default queue list, and the correct way to install a
routine in one is to search the list for a zero longword. When your VBI
queue routine is finished, it may remove itself by clearing its entry in
the list. (This is why it makes no sense to fall through to a previous
queue entry -- that entry should have been zero when you grabbed it.)
Even this mechanism is subject to abuse, however; an unfortunate number
of programs simply stuff an address into one of the queue slots, without
checking first to see if that slot has been taken. (A good example of
this kind of vector abuse is the first version of STARTGEM.PRG.)
Remember: when using the vertical blank queue list, always search the
list for a zero entry in which to install your routine.
With more and more programs appearing that replace entire operating
system functions, compatibility is going to become even more
problematic. For example, clashes will occur because Program A needs to
"see" a certain call being made, but Program B is intercepting the call,
handling it, and returning to the caller. In this scenario, Program A
will just stop doing anything since it will never see the call for
which it's watching. Keep this in mind when you're writing code
intended to replace an entire system call; and be sure to test your code
with as many other resident vector-grabbers as possible.
--------
II.
--------
Never replace a vector after grabbing it, unless you're in a controlled
situation where there is no chance that another program could intercept
the same vector and fall through to your code. Here's an example of
what can go haywire if you do replace a vector at the wrong time:
An early public domain ST program had a feature to select DESKTOP.INF
files for different resolutions. The program grabbed the trap #1 vector
(GEMDOS) and then used the Ptermres() call to make itself resident.
Then, as a resident program, it monitored all GEMDOS calls, looking for
the Fopen() call for the filename DESKTOP.INF. When that call was
detected, the program replaced the system's filename ("DESKTOP.INF")
with either LOW.INF, MEDIUM.INF, or HIGH.INF depending on the current
resolution. Then it made the big mistake -- to remove itself, our
example program took the address that it originally found in the trap #1
vector (when it first ran) and stored it back into the vector.
Why is this such a big mistake? Because other programs that can run
AFTER our example program may also need to grab the trap #1 vector. If
this happens, the next program to install itself in trap #1 will be CUT
OUT of the chain of fall-throughs when our example program replaces the
vector. If you're lucky, the only ill effect will be that one of your
TSR's will suddenly stop working. If you're unlucky, the system will
crash or hang. (It all depends on what the program that got cut out of
the chain was doing with that vector.)
Oh, and by the way, our unnamed example program has since been updated
to fix this thorny problem. The fix was simple; the program now remains
in the trap #1 vector after replacing the system's DESKTOP.INF filename.
After doing its job, the code does nothing but fall through to the
previous vector.
If you are a resident program and you want to remove yourself, do it by
setting a flag to bypass your code and fall through (see Commandment I.)
Remember that some other program may run after yours and grab the same
vector; in this case, the other program will be falling through to your
code. If you remove yourself by replacing the original vector address,
you'll also be removing everything else that ran after you.
--------
III.
--------
Don't use a "magic cookie" (the infamous Diablo emulator mistake). That
is, if you are trying to find another program (or yourself), don't look
for a "magic" word near the address in the vector that the program
steals. This technique will fail as soon as some other program grabs
the same vector; and this is exactly how the Diablo emulator (for the
SLM804 laser printer) breaks. The Diablo emulator consists of two
separate programs -- one that goes in an AUTO folder (the emulator code
itself), and a configuration program that installs as a desk accessory.
The AUTO program grabs the BIOS vector, so that it can redirect printer
output to the laser via the DMA port. The desk accessory configuration
program tries to find the AUTO program (every time it's activated) by
looking for a "magic cookie" stored by the AUTO program in the location
immediately before its BIOS interception code. Problem: if another
program intercepts the BIOS vector AFTER the Diablo emulator AUTO
program, the configuration accessory is unable to find the AUTO program
(because the "magic cookie" is not where the accessory thinks it should
be).
There are a number of ways to reliably find another program. One of the
easiest is to make a "fake" call to one of the trap routines with an
undefined function code. The ST's BIOS and XBIOS will ignore calls with
undefined function codes, and simply return with no ill effects if the
program you're searching for is not present. We suggest using unusual
function codes, such as $4857 (for example), so that your code will not
conflict with future additions to the BIOS or XBIOS functions. The
receiving program can then return whatever kind of information you need
from it (you've got lots of registers to use).
Here's an example (in assembly language) of some code that uses an
undefined BIOS call to detect the presence of another program:
*-------------------------------------------------------------------
*
* The "target" program (the program being searched for) must intercept
* the trap #13 vector and examine the stack after each trap #13 call
* to see if the magic word function number is present. If it is, the
* target program should load the return value into d5 and perform an RTE.
*
moveq #0,d5 ; Clear d5 in preparation
move #$4857,-(sp) ; Magic word - undefined BIOS call
trap #13 ; Call BIOS
addq #2,sp ; Correct the stack
tst.l d5 ; If d5 is still zero, we didn't find anyone
beq.s notfound ; If non-zero, it's a returned value
move.l d5,returned ; Save the returned value somewhere
*-------------------------------------------------------------------
(NOTE: The version of TOS (1.6) that will be supplied with Atari's STE
and TT machines has a new feature called the "Cookie Jar," which does
not suffer from the problems described here. It provides a documented
address where programs can search for "magic cookies"; it's a nice
solution. Our only complaint with the "Cookie Jar" is that we wish it
had been implemented three years ago.)
--------
IV.
--------
Do not try to monitor and maintain a vector from a vertical blank or
other timed interrupt (in other words, don't keep watching it and
replacing it if it changes). Think for a moment about what happens
if two programs do this at the same time. (Ouch.) This extremely bad
practice may seem to work when no other programs are using the same
vector, but you will definitely have coexistence problems down the road.
Don't do it.
--------
V.
--------
Do not use the (system) stack from an interrupt or trap vector. There
is _very_ little stack headroom available in the location used by the
operating system. A system stack overflow will cause crashes that can
be extremely difficult to diagnose.
If you need to save registers during some vector-handling code, it's
best to save them in a location in your own program, instead of on the
system stack. For example:
*-------------------------------------------------------------------
movem.l d0-a6,-(sp) ; Don't do this!
*-------------------------------------------------------------------
movem.l d0-a6,regsave ; Do this instead.
*-------------------------------------------------------------------
--------
VI.
--------
Always restore all registers and the status register when your routine
is finished. Don't even assume that you can destroy D0 or A0 because
some programs (believe it or not) actually rely on them to return from a
trap unchanged. (The exceptions to this rule are the BIOS and XBIOS
vectors; the dispatching routines for these vectors always trash
register A0, so it's safe to use A0 in a BIOS or XBIOS routine without
saving it.)
--------
VII.
--------
Don't alter the processor state. That is, don't 'rte' into your own
code in order to be in USER mode because other programs down the line
may expect the machine to be in SUPERVISOR mode.
--------
VIII.
--------
When intercepting frequently called traps (such as trap #2), always use
optimized assembly language routines to eliminate a slowdown in system
operation. Don't make the "GDOS mistake".
--------
IX.
--------
Never assume something simply because it always "seems to be." This
includes using "hard" addresses specific to a particular ROM, assuming
that certain vectors will be pointing to ROM routines, assuming that 8
bytes into the GEM base page is pointing into the OS, or making _any_
decision based on an empirical condition.
--------
X.
--------
Use the source code provided below for maintaining the trap #2 vector
from a resident program. This somewhat oblique method is required
because the operating system stuffs its own address into the trap #2
vector (with no regard for what is there) after running a TOS program,
and possibly at other times as well. (Yes, we are aware that this
routine breaks Commandment IX.) The routine which handles trap #13 in
this code also demonstrates a method to remain compatible with
68010/68020/68030 processors, by checking a new BIOS variable Atari has
documented.
--------
XI.
--------
Commandment XI may be the most difficult one to follow. Have the
wisdom to know when it's necessary to break any of the other
commandments, and the responsibility to think through the
consequences if you do. Some of these rules should _never_ be
broken; others can be bent once in a while, as long as you
carefully consider all the ramifications. Above all, just as in
any other endeavor, you have to learn the rules and understand the
reasons for their existence before you can get away with breaking
them.
*****************************************
* *
* Intercept the trap #2 vector *
* *
* Code by Charles F. Johnson *
* *
* Includes ideas, techniques and *
* refinements by Bob Breum, *
* Chris Latham, and John Eidsvoog *
* *
* Last revision: 06/26/88 12:13:32 *
* *
*****************************************
.TEXT
* ------------------------
* Program initialization
* ------------------------
move.l #prog_end,d6 ; Get address of end of this program
sub.l 4(sp),d6 ; Subtract start of basepage - save in d6
move.l #not_auto,addrin ; Try to do an alert box
move #1,intin
move.l #f_alrt,aespb
move.l #aespb,d1
move #$C8,d0
trap #2
tst intout ; If intout is zero, we're in \AUTO
beq.s .start1
cmp #1,intout ; Install?
beq.s .0 ; Yes, continue
clr -(sp) ; Pterm0
trap #1 ; outta here
.0: pea prg_start(pc) ; Steal trap #2 right away if run from desktop
move #38,-(sp) ; Supexec
trap #14
addq #6,sp
move #1,prgflg ; Set flag indicating desktop load
bra.s .start2
.start1:
pea title ; Print title message
move #9,-(sp)
trap #1
addq #6,sp
.start2:
dc.w $A000 ; Don't you just love Line A?
move.l a0,line_a ; Save the address of the Line A variables
pea set_bios(pc) ; Appropriate the Trap #13 vector
move #38,-(sp)
trap #14
addq.l #6,sp
clr.w -(sp) ; Terminate and Stay Resident
move.l d6,-(sp) ; Number of bytes to keep
move #$31,-(sp) ; That's all folks!
trap #1 ; We are now happily resident in RAM
* -------------------------------
* Desktop vector initialization
* -------------------------------
prg_start:
move.l $88,t2_vec ; Set my fall throughs
move.l $88,aesvec
move.l #my_trap2,$88 ; Steal trap #2 (GEM)
rts
* -----------------------
* Steal the BIOS vector
* -----------------------
set_bios:
move.l $B4,t13adr ; Set Bios fall through
move.l #my_t13,$B4 ; Steal trap #13 (BIOS)
rts
* ------------------------
* Trap #13 wedge routine
* ------------------------
my_t13:
btst #5,(sp) ; Was the trap called from super or user mode?
beq.s t13_ex ; If from user mode, bail out
lea 6(sp),a0 ; Pointer to function code on stack
tst $59E ; See what _longframe has to tell us
beq.s notlng ; If _longframe is zero, it's a 68000
lea 8(sp),a0 ; Advance past the vector offset word
*** This section is based on the assumption that the OS always calls
*** BIOS setexec() immediately after obnoxiously grabbing back the trap
*** #2 vector with no warning whatsoever. Yes, this is an empirical
*** condition, which violates Commandment IX. (But there's no other
*** way to prevent that no-good, thieving TOS from ripping off the
*** vector while you aren't looking.)
notlng: cmp.l #$050101,(a0) ; Setexec call for critical error vector?
bne.s t13_ex ; Nope, exit
tst prgflg ; On the desktop? Or are vectors already set?
beq.s first_time ; No, skip ahead
do_crit:
move.l #my_trap2,$88 ; Pilfer trap #2
move.l $404,d0 ; Get current crit vector
move.l 4(a0),d1 ; Get address we're setting it to
bmi.s t13_x1 ; If minus, return old vector in d0
move.l d1,$404 ; Set that vector
t13_x1: rte ; We only get here if we're last in the chain
first_time:
tst.l 4(a0) ; Reading the vector?
bmi.s t13_ex ; Yes, let the system take care of it
move.l $4F2,a1 ; Get address of OS header (could be in RAM)
move.l 8(a1),a1 ; Get pointer to base of OS from header
cmp.l 4(a0),a1 ; Is the crit error routine below the OS?
bhi.s t13_ex ; Yes, bail out
move.l $14(a1),a1 ; Get address of end of OS (GEMDOS parm block)
cmp.l 4(a0),a1 ; Is it above the OS?
blo.s t13_ex ; Yes, exit stage left
*** This is a very important part of the code. In order to maintain the
*** correct vector chaining order when running at \AUTO time, it's necessary
*** that each program first fall through to the BIOS and RETURN TO ITS OWN
*** CODE, grabbing the trap #2 vector on the way back. This way, the order
*** that each program intercepts trap #2 is the same as the order in which
*** they run from the AUTO folder.
move #1,prgflg ; Set the 'first-time'/'desktop' flag
move.l 2(sp),retsav ; Save return address
move.l #t13_2,2(sp) ; Replace it with my own
t13_ex: jmp $DEADBEEF ; Go to the Bios and come back,
t13adr = t13_ex+2 ; maintaining the correct chaining order
t13_2: bsr prg_start ; Grab the trap #2 vector on the way back
move.l retsav(pc),-(sp) ; And return to the caller
rts
retsav: dc.l 0
*--------------------------------------------------------------------------
The techniques described here have worked successfully for us, both in
our CodeHead Software products and our individual projects. However, we
do not wish to appear as the final and absolute authorities on this
subject. If you can find any flaws in our scheme, or perhaps enlighten
us with a more efficient trick, we can be easily reached. The quickest
way to get a reply is to leave a message in the CodeHead Category (#32)
on GEnie or leave GEnie mail to C.F.JOHNSON or J.EIDSVOOG1. You may
also call CodeHead Software at (213) 386-5735.
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
NOTES ON THE GERMAN "XBRA" PROTOCOL
For quite some time we've been hearing rumors about a new "standard"
protocol devised in Germany, which supposedly can prevent some of the
problems with conflicting vector-grabbers. It's called the "XBRA"
protocol -- here's how it works:
When a program needs to intercept a trap or interrupt vector, it should
put the previous vector address four bytes before the beginning of its
routine, preceded by two longwords. The first longword before the
address should be a unique identification code for your application.
The second longword before the previous vector address should be the
magic longword "XBRA" ($58425241). So, in assembly language, the code
would look something like:
*----------------------------------------------------------------
dc.l 'XBRA' ; Magic longword signifying XBRA protocol
dc.l 'BRAT' ; Unique (hopefully) 4-byte ID
oldvec: dc.l 0 ; Put the previous vector address here
my_vector_routine: ; Your vector-handling code starts here
*----------------------------------------------------------------
In order for this protocol to really work, the vector interception code
should also use the previous vector address stored in the XBRA structure
to fall through to the previous routine. This way, if it's necessary to
restructure the fall-through chain, any vector interception code will
automatically start falling through to the new address.
*----------------------------------------------------------------
move.l oldvec(pc),-(sp) ; One way to fall through to the
rts ; address in an XBRA structure
*----------------------------------------------------------------
move.l oldvec(pc),jump+2 ; Another way to fall through:
jump: jmp $ADEADBEE ; by modifying a JMP instruction.
; This uses more memory, and may
; not work on a 68030 (without
; tweaking), but it doesn't use
; the system stack.
*----------------------------------------------------------------
The main use of XBRA seems to be to allow programs to unhook themselves
from a vector chain; it provides a method whereby programs can walk
through the chain of vectors, unhook themselves (or unhook other
programs!) if necessary, and even restructure the whole chain. Again,
it would have been nice if the XBRA protocol were proposed three years
ago; if even one program in the chain is not following XBRA, the whole
scheme is useless. And since there are _many_ programs that don't use
XBRA, the scheme is of little use in the real ST world at the present.
Still, it doesn't take much effort to implement the XBRA protocol, so it
may be a good idea to use it in any future vector-grabbing programs. If
all programs used XBRA, _some_ of the problems with conflicting vector
thieves could be eased. (Why does XBRA remind us of Esperanto, the
United Nations-sponsored "international language" that was going to make
it possible for all mankind to live in peace?)
(NOTE: In our opinion the XBRA protocol could be improved, by adding a
JMP instruction to the XBRA structure immediately before the previous
vector address. If the structure looked like this:
*----------------------------------------------------------------
dc.l 'XBRA'
dc.l 'BRAT'
jump: dc.w $4EF9 ; 680x0 absolute JMP instruction
oldvec: dc.l 0 ; Put the previous vector address here
my_vector_routine: ; Your vector-handling code starts here
*----------------------------------------------------------------
then a program could simply branch to the label "jump" to fall through
to the previous vector-handling routine.
We must _emphasize_, however, that this is merely an observation on our
part. Don't use this suggested extension to XBRA in your code, since
the XBRA protocol does NOT support it as of this date.)
It should be pointed out that XBRA is not a panacea; the "Eleven
Commandments" we've outlined here are still valid, even if you do employ
the XBRA protocol in your code. In fact, since so many programs already
exist that do not use XBRA, it's even more important not to rely on the
XBRA protocol to solve your problems for you.
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
***********************************************************
* *
* This document is Copyright 1990 CodeHead Software. *
* All Rights Reserved. *
* *
* May be freely distributed as long as this ASCII text *
* file is complete and unaltered in any way. This *
* document MAY NOT be reprinted or used for commercial *
* purposes without express written permission from *
* CodeHead Software. *
* *
* If you wish to reprint this document, contact us at *
* the phone number given above for permission. *
* *
***********************************************************
<<<<*>>>><<<<*>>>><<<<*>>>>