INITs, Patches
Volume Number: 5
Issue Number: 6
Column Tag: Forth Forum
INITs and Patches 
By Jörg Langowski, MacTutor Editorial Staff
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
“INITs and trap patches”
This month’s program started off as an attempt to write a ‘logging’ utility, that
is, a routine that will record when applications are launched and closed. Simple, I
thought. Just patch the _Launch and _ExittoShell traps and install some code that
writes a line to a file giving the time, date and name of the application each time these
traps are called. However, I soon realized that it’s not as simple as that. Under Finder,
yes, that strategy works; but imagine my surprise when I found that _Launch is never
called when an application is started under Multi finder! This ‘feature’ seems not to be
documented in the technical notes that I have, nor in the Multifinder documentation.
If you record trap calls with TMON, you’ll find the following sequence for
launching an application under Finder:
_Launch
_LoadSeg
_LoadSeg
... (until all necessary segments are loaded)
and for exiting, _ExittoShell is called. Multifinder seems to use the same sequence of
calls, except for the first call to _Launch, which is left out.
Therefore, if we want to build a routine that logs application launches, we have to
find another indicator. One trap call is always made when an application is started:
_GetResource is called for CODE ID=0. If we patch _GetResource such that this
particular call is intercepted, we have a ‘hook’ that allows us to install any routine to
be called at application startup.
Patching traps under Multi finder
Traps cannot be easily patched in a permanent way under Multi finder. Since
different application might need different trap patches, Multifinder intercepts (I
believe) _SetTrapAddress and switches the trap patches when control is transferred
from one program to another. So, when you write a short application that patches a
trap, that patch will only be valid in the context of that application - and disappear
when the application quits again. Not very useful for a routine that logs application
launches, which will have to work independently of any particular context. So we’ll
have to install the patch before Multifinder is started, and this is best done with an
INIT.
This month’s example will show you how to write an INIT under Mach2 that
installs permanent patches to _GetResource and _ExittoShell. For the moment, all
these patches do is beep once when the application starts and when it quits. These
patches would then have to be extended to write some text to a file that gives the name
and time of the application.
I’ll also give an example of a small application that can be used to run an INIT
from a file in the same folder as the application. It uses the same mechanism as the
system itself to call the INIT. This program is based on an idea of Eric Carrasco from
the Calvacom bulletin board in Paris, who wrote a similar program in Pascal.
The INIT
The mechanism by which an INIT resource is called has been outlined already by
Bob Denny in V1#9. In case you’re missing that issue, I’ll quickly repeat it here:
First, the INIT resource is loaded into the system heap. Then, _DetachResource is
called which tells the resource manager to forget this resource. Finally, the system
jumps to the first location of the INIT resource via a JSR. When the INIT later returns
via RTS, the first two words in its code are overwritten by NOP instructions. The
reason for this procedure, which might seem a little strange, is that an INIT code might
consist of a JMP to some installation code at the end, and have the actual
memory-resident part at its beginning. That way, when the JMP at the beginning is
overwritten by NOPs, any code that jumps to the start of the INIT will now execute its
main part. The initialization part at the end can be given back to the memory manager,
once the INIT is installed.
Listing 1 shows the INIT code in Mach2. The kernel-independent code is produced
in the usual way, using the external definition words which make sure the JMP at the
first location if the INIT points to the right piece of code, and which set up the local
Forth stack.
The INIT (word INITrun) first creates a handle to its own memory block by
calling _RecoverHandle, then locks this handle. A temporary area for the Quickdraw
globals is then created in the local variable space. Remember that local variables in
Mach2 are allocated from top to bottom in memory, so that the sequence
newA5 myGlobals [ 202 lallot ]
with A5 later pointing to the address of newA5 will have the QD globals allocated
correctly below A5. If your INIT needs to use any of them, simply reference them
starting at myGlobals.
Our INIT code sets up A5 and the low memory global CurrentA5, then calls the
usual manager initialization traps necessary for setting up a dialog box. After these
calls, the environment is ready for calling the main Forth code of the INIT, myINIT in
our case. From myINIT, all toolbox traps may be used since A5 points to valid QD global
space and the managers have been initialized (oh not the menu manager, of course,
but you probably wouldn’t want to use menus from an INIT. I doubt whether it would be
possible, anyway. Try it out).
For an INIT that is supposed to stay in the system heap ‘as is’, you don’t need to
take any special action in the code to free its memory space. However, when - as in our
case - the INIT copies part of itself to a non-relocatable block in the system heap and
is not needed anymore, we want to purge the memory space after action. This is done by
calling _DisposHandle after myINIT has returned.
gINIT is the glue code that calls the standard ‘INIT interface’ INITrun. Here, the
registers are saved, stacks are set up, and CurrentA5 is restored after INITrun has
returned.
The main INIT code, myINIT, first moves the patch code into a non-relocatable
block in the system heap. It then patches _GetResource, using _SetTrapAddress, and
moves a vector to the trap’s old address into the appropriate location inside the patch.
The user is notified of the patch by an alert. The _GetResource patch itself
(GetResPatch) checks whether this trap has been called with resource type CODE,
ID=0. In that case, it beeps once. Instead of the beep, one could insert some code that
writes a line to a file which has been opened in the installation part of the INIT.
Something left as an exercise
There is also a patch to _ExittoShell, ExitPatch, which is supposed to beep when
an application quits. Now look at the code where the INIT installs that patch. We do a
_GetTrapAddress, right, but then the ugly part starts. If _ExittoShell is patched by
_SetTrapAddress at system INIT time, the patch gets installed correctly, and you hear
the beep when the Finder quits and calls the Multi finder, but then, silence. The patch
has disappeared. Multifinder patches _ExittoShell as well and probably doesn’t
conserve previous patches. I don’t know whether this is a bug or a feature (such as not
calling _Launch when starting an application); but it makes life a great deal more
difficult. Apple, can you hear me?
Here’s the workaround that I thought up, which works under System 6.0.2, and
is not guaranteed to work under a new release (no, don’t leave the room yet, PLEASE!).
When Multifinder patches _ExittoShell, it installs a JMP instruction to its patch code
in RAM. Our INIT code tests whether the pointer returned by _GetTrapAddress points to
a JMP < absolute> instruction. In that case, it patches the next two words with a pointer
to our patch code and saves the old JMP address in the patch code. If the first