Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[FAQ] Writing System Extensions 3/3

0 views
Skip to first unread message

Brian Stern

unread,
Apr 14, 1995, 3:00:00 AM4/14/95
to
[19] How do I patch a trap?

Here is some sample code for a head patch of Alert, a stack-based
trap:

TrapPtr gOldAlertTrapAddress;

/****InstallPatch***************************************************/

void
InstallPatch (void)
{
gOldAlertTrapAddress = GetToolTrapAddress( _Alert );
SetToolTrapAddress( (long) AlertPatch, _Alert );
}

/****AlertPatch***************************************************/

pascal void
AlertPatch( short alertID, ProcPtr filterProcPtr )
{
SetUpA4();

MyAlert( alertID) ; //Do our thing

//store the correct alert addr
//in A0 while we can still access globals via A4

asm { move.l gOldAlertTrapAddress, A0 }

RestoreA4();

asm {
unlk A6 //match the link generated by C
jmp (A0) //jump to _Alert
}
}

There are a number of details to note here. This patch is of course
part of a code resource that is loaded at INIT time and detached as
described in an earlier section. The routine InstallPatch must be
called at INIT time.

The routines GetToolTrapAddress and SetToolTrapAddress allow you to
get and set the addresses of Tool Traps. The similar routines
GetOSTrapAddress and SetOSTrapAddress allow you to manipulate the
addresses of OS traps. The routines NGetTrapAddress and
NSetTrapAddress allow you to manipulate the addresses of either,
although they call glue code. I recommend that you use the
GetXTrapAddress and SetXTrapAddress calls. There are two obsolete
calls: GetTrapAddress and SetTrapAddress. Don't use them.

The prototype for this patch is declared as 'pascal void' while the
prototype for Alert is 'pascal short'. Because this is a head patch
it will not be returning a result; the result will be returned from
the real Alert trap. The pascal keyword is used to indicate pascal
calling conventions. It is not strictly required in all cases but
does no harm.

The Think C routines SetUpA4 and RestoreA4 are called to allow access
to global variables by A4 addressing. In this case
gOldAlertTrapAddress is the only global variable we are addressing,
unless any are used inside MyAlert. Note that this variable is moved
to A0 while access to global variables is still available. In some
cases one might move a global variable to a local variable, which
doesn't rely on A4 addressing. A4 could then be restored and the old
trap address could be loaded into A0 later in the code.

Because there is a parameter list the compiler generates a Link A6
instruction at the start of this function. In order to restore the
stack a matching Unlk A6 must be placed at the end of the function.
Since we are exiting by the jmp (A0) we must insert the Unlk A6
ourselves. The compiler does generate an Unlk A6 and an RTS at the
end of this function, but they will never be executed. The presence
of the Link A6 instruction is dependent on the particular compiler
you use and on its rules for generating a stack frame. It is a good
idea to disassemble the code for your trap patches to see whether a
stack frame has been generated in order to determine if you need to
insert the Unlk A6 instruction. If you don't match the Link A6 with
an Unlk A6 the stack will be screwed up.

It is essential that the stack look exactly the same on exit from a
head patch as it does on entry. If not you will surely crash. (If
you had good reason you could modify the value of a parameter on the
stack, but that's another story.) This patch saves and restores A4
but does modify A0 and A1 (SetUpA4 uses A1). In general, with head
patches of stack-based traps you can modify A0, A1, D0, D1, and D2,
but not any other registers. You may need to look at the
disassembled code to be sure that all your registers are properly
saved and restored.

The design of the patch as shown here, with the patch code calling a
separate function to perform the actual functionality of the patch is
a good design to follow with all but the simplest of patches.

You might think that you need to call StripAddress on the address of
the patch routine before passing this address to SetToolTrapAddress.
This is not necessary unless the address is actually a handle. If
you were to load a code resource and pass its entry point to
SetToolTrapAddress then it would need to be stripped.

--
[20] How do I patch a register-based trap?

Here is the code for a sample register-based trap patch:

TrapPtr gMountVolAddress;

/****InstallPatch**************************************************/

void
InstallPatch(void)
{
gMountVolAddress = GetOSTrapAddress( _MountVol );
SetOSTrapAddress( (long) MountVolPatch, _MountVol );
}

/****MountVolPatch**************************************************

This is a register-based trap that has A0 set to point to its
parameter
block on entry. The prototype for MountVol is:

pascal OSErr PBMountVol( ParmBlkPtr paramBlock )

This patch beeps when a floppy or CD-ROM is inserted or when a
harddrive is mounted by the Finder.

*******************************************************************/

pascal void
MountVolPatch(void)
{
//Save some registers
//Save A0 since it's trashed by SetUpA4
//D1 contains the trap word

asm { movem.l a0/d0-d1, -(sp) }

SetUpA4(); //Allow access to global variables

SysBeep( 5 ); //The guts of our head patch

//store the correct MountVol addr
//in A1 while we can still access globals via A4
asm { move.l gMountVolAddress, A1 }

RestoreA4(); //Restore previous value in A4

asm { //Restore the registers
movem.l (sp)+, a0/d0-d1
jmp (A1) //jump to _MountVol
}

}

This head patch is similar in structure to the patch to Alert with a
few differences. The prototype uses no parameters and has no return
value. The single parameter is passed through A0. This patch doesn't
do anything with this value but it could be moved to a local variable
and then used to reference the fields in the parameter block if
desired. Access to global variables is by the same A4 mechanism as
in the Alert patch. Note that _MountVol is an OS trap so
GetOSTrapAddress and SetOSTrapAddress are used to set up the patch.

Since A0 is used to pass the parameter to this trap we jump to the
real _MountVol trap through A1.

Obviously A0 must be saved and restored in this patch. OS traps
expect to find the trap word in D1 so it must be saved and restored
as well. Three registers, A0, D0, and D1, are saved onto the stack
with the movem instruction, and restored at the end of the patch.

Think C doesn't generate a 'Link A6' at the start of this function
because there are no parameters and no local variables. Because of
this no 'Unlk A6' is needed at the end of the function.

--
[21] Can you show me a tail patch?

Here is another example of a patch to a register-based trap. This
sample is a tail patch and is dependent on the CodeWarrior
environment. Because CW allows you to specify that parameters are
passed in registers this trap patch requires no assembly.

extern pascal OSErr (*Old_MountVol)( ParmBlkPtr pb : __A0 ) : __D0;

pascal OSErr My_MountVol( ParmBlkPtr pb : __A0 ) : __D0
{
OSErr err;
long saveA4 = SetCurrentA4();

err = Old_MountVol( pb );
DoSomthingFunc();

SetA4( saveA4 );
return err;
}

--
[22] How do I patch a selector-based trap?

Here is a sample patch to PrGlue. This trap is the front end for all
the Printing Manager routines. Its selector is pushed on the stack

typedef struct
{
long Selector;
THPrint hPrint;
} PrJobDialogStack;

TrapPtr PrGlueAddress;

/****InstallPatch****************************************************
/

void
InstallPatch(void)
{
PrGlueAddress = GetToolTrapAddress( _PrGlue );
SetToolTrapAddress( (long) PrGluePatch, _PrGlue );
}

/****PrGluePatch****************************************************

This is a stack-based trap with a long word selector also pushed
onto the stack. On entry the selector is at 4(A7). The return
address is at 0(A7). After the 'Link A6' the selector is at 12(A7).

*******************************************************************/

#define kSelectorOffset 12
#define kPrJobDialogSelector 0x32040488

pascal void
PrGluePatch(void)
{
PrJobDialogStack *StackPtr;

//Get address of the stack frame
//and save it in a local variable

asm {
lea kSelectorOffset(A7), A0
move.l A0, StackPtr
}

SetUpA4(); //Allow access to global variables

//Check the selector
if ( StackPtr->Selector == kPrJobDialogSelector )
{
SysBeep( 5 );

//Pass hPrint to our function to do something
DoSomethingFunc( StackPtr->hPrint );
}

//Store the correct PrGlue addr
//in A0 while can still access globals via A4
asm { move.l PrGlueAddress, A0 }

RestoreA4(); //Restore previous value in A4

asm {
unlk A6 //match C's Link A6
jmp (A0) //jump to _PrGlue
}

}

In order to access the selector and the parameters for PrGlue we use
a pointer to a struct. Once the pointer is initialized correctly we
can access the selector and any parameters from C easily.

According to NIM: PPC System Software it is not safe to patch
selector-based traps with PPC native code. All patches of selector-
based traps on the PowerMac should be written in 68K code.

--
[23] How do I patch a trap on the PPC?

See NIM 'PowerPC System Software' for a more complete discussion.
There is also a new book by Tom Thomson called 'Power Macintosh
Programming Starter Kit' that has examples of how to patch traps on
the PowerMac.

Patching traps on the PowerMac is similar to patching on the 68K
architecture. Of course you must generate a UniversalProcPtr for
each of your patches in the system heap, and these are then passed to
the SetXTrapAddress routines. Since code fragments have their own
globals the use of A4 or A5-based mechanisms for accessing global
variables isn't needed. In order to call the previous trap you need
to call CallUniversalProc or CallOSTrapUniversalProc and return its
result from your patch. As a result all patches on the PowerMac are
tail patches.

You cannot safely patch a selector-based trap in native code. See
NIM PPC for an explanation of this. Unless or until this changes you
shoud do all patching of selector-based traps in 68K code.

You may find an application called 'Traps Check' useful. This app
supplies a report about all the traps on a Powermac, indicating
whether each trap is emulated or native. Another way to do this is
to drop into MacsBug and disassemble from the address of the trap
you're interested in (e.g., 'il CopyBits' ). For traps that are
native you'll see a routine descriptor that begins with the
MixedModeMagic trap (AAFE). This of course won't tell you if the trap
has been patched. You can identify a patch by whether it's in RAM or
ROM, from its address. Determining whether a patched trap is PPC
native or not may take some additioinal sleuthing. You can find Traps
Check at: ftp://sumex-aim.stanford.edu/info-mac/dev/traps-check-
10.hqx

Here is a sample PowerMac trap patch for GetResource:

enum {
uppGetResourceProcInfo= kPascalStackBased
| RESULT_SIZE(SIZE_CODE(sizeof(Handle)))
| STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ResType)))
| STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(short)))
};

UniversalProcPtr gGetResourceUPP;
UniversalProcPtr gGetResourcePatchUPP;

/****InstallPatch**************************************************/

void
InstallPatch(void)
{
THz saveZone = GetZone();
SetZone( SystemZone() ); //put UPP in syszone

gGetResourceUPP = GetToolTrapAddress( _GetResource );

gGetResourcePatchUPP =
NewRoutineDescriptor( (ProcPtr) GetResourcePatch,
uppGetResourceProcInfo, GetCurrentISA() );

SetToolTrapAddress( gGetResourcePatchUPP, _GetResource );

SetZone( saveZone );

}


/****GetResourcePatch*********************************************/

Handle
GetResourcePatch( ResType theType, short theID )
{
Handle result;

//We don't need no 'DUMB' resources
if ( theType == 'DUMB' )
result = NULL;
else
result = (Handle) CallUniversalProc( gGetResourceUPP,
uppGetResourceProcInfo, theType, theID );

return result;

}

This patch as written is PPC only for illustrative purposes. In real
life the NewRoutineDescriptor and CallUniversalProc would be #defined
in a header file and would compile correctly for both 68K and PPC
code. You can find examples of how to do this in the Universal
header files.

--
[24] Can I write a fat trap?

//Under construction

--
[25] Tips?

If your patch isn't called you may have guessed wrong on whether it's
a ToolTrap or an OSTrap. The high bit of the second byte of the trap
word is set for ToolTraps. The following function can be used to get
the correct trap address for both ToolTraps and OSTraps.

pascal void * GetCurrentTrapAddress( unsigned short trapWord )
{
if ( trapWord & 0x0800 )
return GetToolTrapAddress ( trapWord & 0x07FF );
else
return GetOSTrapAddress ( trapWord & 0x07FF );
}


If the machine crashes after leaving your patch you have probably
munged the stack or not saved and restored all the registers that you
must.

The Finder patches a number of traps when it loads in a way that
prevents earlier trap patches from functioning. If your patch
doesn't appear to be called it may be one of these patches.

--
[26] What other sources of information are available?

Knaster 'How to Write Macintosh Software'
Knaster and Rollin, 'Macintosh Programming Secrets'. These books on
Mac programming has some excellent info on trap patching.

Tom Thomson 'Power Macintosh Programming Starter Kit' This book has
some example code for writing trap patches and extensions on the
PowerMac.

The Extension Shell package at:
ftp://sumex-aim.stanford.edu/info-mac/dev/src/extension-shell-13.hqx
Dair's email address has changed to: dair....@ucl.ac.uk.

Usenet Macintosh Programmers Guide
ftp://sumex-aim.stanford.edu/info-mac/dev/info/usenet-mac-prog-guide-
msw.hqx

All of the Apple Tech Notes have been made available on Apple's web
server: http://www.info.apple.com/dev/technotes/Main.html

--------------------comp.sys.mac.programmer.info---------------------
comp.sys.mac.programmer.info is primarily for distributing FAQs,
tutorials, news, and similar documents related to programming the
Macintosh. To post, email csmp...@xplain.com
-----------------------about MacTech Magazine----------------------
PO Box 250055, Los Angeles, CA 90025, 310-575-4343, Fax:310-575-0925
For more info, anonymous ftp to ftp.netcom.com and cd to /pub/xplain
or email to the following @xplain.com : custservice, editorial,
adsales, marketing, accounting, pressreleases, progchallenge,
publisher, info

0 new messages