(c)  Copyright 1991 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice,
and is provided "as is" without warranty of any kind, either expressed
or implied.  The entire risk as to the use of this information is
assumed by the user.




Using SetFunction() in a Debugger


By Ewout Walraven



The Amiga OS consists of a set of libraries (and devices), which reside 
in ROM or on disk.  These libraries provide a set of routines which are 
shared by the Amiga tasks (hence the name shared library). 

The way in which an Amiga library is organized allows a programmer to 
change where the system looks for a library routine.  Exec provides a 
function to do this: SetFunction().  The SetFunction() routine redirects 
a library function call to an application-supplied function (Although 
this article doesn't address it, SetFunction() can also be used on Exec 
devices).  The SetPatch utility uses SetFunction().  SetPatch is a 
program which replaces some OS routines with improved ones, primarily to 
fix bugs in ROM libraries. 

Normally, programs should not attempt to ``improve'' library functions.  
Because most programmers do not know exactly what system library 
functions do internally, OS patches can do more harm than good. However, 
a useful place to use SetFunction() is in a debugger.  Using 
SetFunction(), a debugger can reroute library calls to a debugging 
function.  The debugging function can inspect the arguments to a library 
function call before calling the original library routine (if everything 
is OK).  Such a debugging function doesn't do any OS patching, it  
merely inspects.  

SetFunction() is also useful for testing an application under conditions 
it does not encounter normally.  For example, a debugging program can 
force a program's memory allocations to fail or prevent a program's 
window from opening.  This allows a programmer to find bugs that only 
arise under special circumstances.  Some programs that use SetFunction() 
for debugging purposes are IO_Torture, Memoration and MungWall.  A real 
watchdog is Wedge, which, as its name implies, allows you to install a 
wedge for practically every function of a standard library and inform 
you about the register values passed to the function.  These types of 
debugging tools helped debug release 2.0 of the OS and found bugs and 
1.3 dependencies in commercial applications.

Although useful, SetFunction()ing library routines poses several 
problems.  First of all, the wedge routine will have to be re-entrant, 
like all Exec library functions.  Secondly, there is always a problem 
with removing the wedge.  If another task has SetFunction()ed the same 
library routine as the debugger (a very real possibility), it is not 
normally possible to remove the first wedge, since the other task 
depends on the presence of your task's code.  This would force your task 
to hang around, waiting for the other task(s) to remove their wedges.  
You also need to know when it is safe to unload your debugging code.  
Removing it while another task is executing it will quickly lead to a 
hopelessly crashed system.

For those of you who might be thinking about writing down the ROM 
addresses returned by SetFunction() and using them in some other 
programs: Forget It.  The address returned by SetFunction() is only good 
on the current system at the current time.  Blindly jumping into ROM 
will cause your programs to break.



Exec Library Structure

When a library is opened for the first time, a library node structure, a 
jump table, and a data area are created in RAM.

The library node structure address is the base address of the library.  
OpenLibrary() returns this base address.  The library's jump table, 
which directly precedes the library node in RAM, consists of six byte 
long entries containing a jump instruction (JMP) to a corresponding 
library function.  The jump table is initialized when Exec opens the 
library.  Each function's entry in the jump table (also known as a 
vector) is always a constant (negative) offset from the library base.  
These fixed negative offsets are known as Library Vector Offsets (LVO).  
Note that the first four function vectors are reserved for use by Exec. 
They point to standard library functions for opening, closing, and 
expunging the library, plus there is space reserved for a fourth 
function vector.  The base address of a library is determined 
dynamically when the library is loaded into RAM.  See the Exec 
introduction chapter in the ROM Kernel Manual: Libraries and Devices for 
more information on libraries.

The SetFunction() routine replaces an LVO address with a new address 
which points to the wedge routine.  SetFunction() returns the old vector 
address, which the wedge routine can use to call the original library 
function from within the wedge.  Note that if another task 
SetFunction()s the same library function, SetFunction() returns the 
address of your debugging routine (to the second task)  as the old 
vector.  At that point your task can no longer exit since that would 
mean that that other task has an invalid pointer to your function and 
will most likely crash the system when it tries to use your function. 

There is a way around this problem.  Instead of SetFunction()ing a 
library function with the address of your wedge code, build your own 
jump table and use the addresses of its entries as arguments to 
SetFunction() calls.  This method allows you to unload your code when 
you want to because if another task SetFunction()s your routine, that 
task will get a pointer to a jump table entry, not your routine.

Now when you want to exit, all you need to do is replace the entries in 
your jump table which point to your functions with the original function 
vectors which were returned by SetFunction().  By not freeing the memory 
allocated for the jump table your task can exit any time, regardless of 
other tasks which SetFunction()ed the same library routines.  The other 
task(s) will never know what happened.

The next time the debugger is executed, it looks for the jump table it 
left behind and replaces the entries in it with pointers to its own 
functions.  Incidentally, this is an easy way to provide a mechanism to 
determine if your debugging program has already been installed.



Caveats

There are some things to keep in mind when using SetFunction().  The 
scheme described above can force a second task to hang around forever if 
it SetFunction()ed a routine before you, since your debugger will not 
normally release its handle on the second task's function.  Whenever 
possible, install jump table based debuggers before any other 
SetFunction()ing program (but after SetPatch of course).

Some debuggers interpret the return address of the caller.  When a 
debugger jumps (JMP) to (what it thinks is) the original function, there 
will be no problem.  However, if a debugger performs a JSR to a second 
debugging function which interprets the return address, the second 
debugging function will receive the first debugging function's return 
address (the one that performed the JSR) rather than the return address 
of the application that called the library function in the first place.  
This can confuse the second debugger.  Two good examples of this are 
Scratcher and MungWall, which both SetFunction() FreeMem().  MungWall 
looks at the return address of the caller.  Since Scratcher calls the 
old FreeMem() function with a JSR instruction, it would mislead MungWall 
if run after it.  Preferably, debuggers that interpret the return 
address should be started after other debuggers. 


Although it is not common, some library functions call other library 
functions and depend on certain scratch registers to contain valid 
values.  SetFunction()ing one of these functions is likely to change the 
values in these scratch registers, leading to problems.  Because these 
dependencies are not always documented, you might innocently run into 
one.  Scratcher is an excellent tool for finding such dependencies.

In the past, some system functions did not have a JMP vector in their 
entry in the LVO table.  Instead, the actual function was in the jump 
table.  SetFunction() will not work on such a function.  

Any debugging routine should be careful not to call the function it has 
patched with SetFunction(), either directly or indirectly.  Doing so 
will likely cause a stack overflow and crash the machine.  This may seem 
a bit obvious until you consider how easy it is to indirectly call a 
system routine.   Many system functions are not atomic.  They have to 
call lower level system functions.  If you call a higher level system 
functions in the debugging code and you have SetFunction()ed one of the 
routines the high level function uses, the machine will probably crash 
from a stack overflow.

Using SetFunction() on disk-based libraries and devices requires a 
little extra care.  Unlike a ROM library, libraries (and devices) loaded 
into RAM can be expunged when memory gets low.  To prevent the system 
from expunging a library (or device) you have SetFunction()ed, either 
keep the library (or device) opened, or use SetFunction() to patch the 
library's expunge function.

Remember that when you install a wedge, another program can call it 
almost instantly.  Because of this, the wedge should be completely set 
up before you install it.

Note that dos.library is now a standard library and can be 
SetFunction()ed as of V36.  Before V36 you would have to Forbid(), get 
the six original bytes of the entry in the function vector table, 
install the new vector, perform a SumLibrary() and then Permit().

If it is necessary to put debugging code into a Forbid() or Disable() 
state, keep it in that state for as little time as possible to limit 
system slowdown.  Remember that you cannot Disable() for more than 250 
microseconds.  Be sure to read the Disable()/Enable() Autodocs before 
using them.



An Example Debugger

The usage of SetFunction() is shown by the example debugging program at 
the end of this article, ISpy.  ISpy uses a semaphore to gain access to 
its jump table.  This jump table contains pointers to the wedge 
routines.  When executed, each wedge routine puts a shared lock on the 
semaphore to indicate that the code is being executed.  To get an idea 
of who is calling the debugger entries, ISpy uses a little assembler 
stub to load a4 with the address of the stack of the caller and calls a 
C routine where the actual (simple) argument checking is done.  When 
ISpy is signalled to exit, it tries to get an exclusive lock on the 
semaphore in a Forbid()en state.  If this succeeds, it can safely assume 
its code is not being executed at the moment and can therefore place the 
original function vectors in the jump table and exit, leaving the 
semaphore behind.  This semaphore is also used to check whether ISpy is 
already installed.  If so, the new instance will exit immediately.  
Because of the use of shared semaphore locks, this program will only run 
with V37.  By using a global counter (which is incremented each time a 
function is entered and decremented when it is exited) ISpy can be 
adapted to V33.  Because of the way ISpy is set up, it is very easy to 
add argument checking front ends for functions, and have multiple 
versions of ISpy for different libraries.



Memoration and Scratcher by Bill Hawes.  IO_Torture by Bryce Nesbitt.  
Wedge and DevMon by Carolyn Scheppner.  MungWall by Ewout Walraven 
(inspired by Memwall by Randell Jesup and MemMung by Bryce Nesbitt).


=======================================================

;/* Execute me to compile with SASC 5.10a
lc -b1 -cfist -d0 -O -v -j73 ispy.c
asm -iINCLUDE: ispy_stubs.asm 
blink from lib:c.o ispy.o ispy_stubs.o LIB lib:amiga.lib lib:lcnb.lib lib:debug.lib SC SD ND DEFINE __main=__tinymain
quit
**      ISpy. AmigaMail SetFunction() example.
**
**      Copyright (c) 1991 Commodore-Amiga, Inc.
**          All Rights Reserved
**
*/


#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <exec/semaphores.h>
#include <dos/dos.h>
#include <libraries/gadtools.h>
#include <string.h>
#include <dos.h>

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }  /* Disable Lattice CTRL/C handling */
int chkabort(void) { return(0); }
#endif

#define ASM __asm __saveds
#define REG(x) register __## x

#ifdef PARALLEL
#define zprintf dprintf
extern VOID     dprintf(STRPTR,...);
#else
#define zprintf kprintf
extern VOID     kprintf(STRPTR,...);
#endif

#ifdef DEBUG
#define D(x) x
#else
#define D(x) ;
#endif

/* Local protos */
VOID            main(VOID);
BOOL            InstallWedge(VOID);
BOOL            RemoveWedge(VOID);
struct JumpTable *GetJumpTable(UBYTE *);

/* Assembler stubs will return a pointer to the caller's stack in a4.
 * The only system function at this moment using a4 is the workbench.library
 * AddAppIconA() function.
 */
#define ACALLER (0)             /* StackPtr[0] = ACaller, StackPtr[1] = saved A6 in stub */
#define CCALLER (2)             /* StackPtr[2] = CCaller */

/* The number of 'replacement' functions */
#define NUMBEROFFUNCTIONS (4)

/* prototypes for the functions to be SetFunction()'ed. */

/* intuition.library */
struct Screen  *(*ASM oldOpenScreen) (REG(a0) struct NewScreen *, 
                                      REG(a6) struct Library *);
struct Window  *(*ASM oldOpenWindowTagList) (REG(a0) struct NewWindow *, 
                                             REG(a1) struct TagItem *, 
                                             REG(a6) struct Library *);
struct Screen  *ASM newOpenScreen(REG(a0) struct NewScreen *, 
                                  REG(a4) ULONG *, 
                                  REG(a6) struct Library *);
struct Window  *ASM newOpenWindowTagList(REG(a0) struct NewWindow *, 
                                         REG(a1) struct TagItem *, 
                                         REG(a4) ULONG *, 
                                         REG(a6) struct Library *);

/* exec.library */
VOID(*ASM oldFreeMem) (REG(a1) VOID *, 
                       REG(d0) ULONG, 
                       REG(a6) struct Library *);
VOID ASM        newFreeMem(REG(a1) VOID *, 
                           REG(d0) ULONG, 
                           REG(a4) ULONG *, 
                           REG(a6) struct Library *);

/* graphics.library */
VOID(*ASM oldSetFont) (REG(a1) struct RastPort *, 
                       REG(a0) struct TextFont *, 
                       REG(a6) struct Library *);
VOID ASM        newSetFont(REG(a1) struct RastPort *, 
                           REG(a0) struct TextFont *, 
                           REG(a4) ULONG *, 
                           REG(a6) struct Library *);

/* Assembler Stubs */
extern          OpenScreenStub();
extern          OpenWindowTagListStub();
extern          FreeMemStub();
extern          SetFontStub();

/* The LVO's to use from amiga.lib */
extern          LVOOpenScreen;
extern          LVOOpenWindowTagList;
extern          LVOFreeMem;
extern          LVOSetFont;

extern struct ExecBase *SysBase;
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

/* Use a table and an array to make it a little more generic and easier to
 * add functions.
 */
struct LVOTable
{
    LONG            lt_LVO;
    struct Library *lt_LibBase;
    ULONG           lt_oldFunction;
    ULONG           lt_newFunction;
};

struct LVOTable LVOArray[] =
{
    {&LVOOpenScreen, (struct Library *) &IntuitionBase, 
        &oldOpenScreen, 
        &OpenScreenStub},
    {&LVOOpenWindowTagList, (struct Library *) &IntuitionBase, 
                        &oldOpenWindowTagList, &OpenWindowTagListStub},
    {&LVOFreeMem, (struct Library *) & SysBase, &oldFreeMem, &FreeMemStub},
    {&LVOSetFont, (struct Library *) & GfxBase, &oldSetFont, &SetFontStub},
};

struct JumpTable
{
    struct SignalSemaphore jt_Semaphore;
    UWORD           pad_word;
    struct Task    *jt_Owner;
    UBYTE           jt_Function[NUMBEROFFUNCTIONS * 6];
};

/* Strings */
/* The name this JumpTable/Semaphore will get. */
static UBYTE   *JTName = "ISpy-MiscJumpTable";

static UBYTE   *VersTag = "\0$VER: ISpy 37.4 (21.3.91)";
static UBYTE   *VerTitle = "Ispy 37.4";
static UBYTE   *Copyright = "Copyright (c) 1991 Commodore-Amiga, Inc.\n";
static UBYTE   *CBreak = "CTRL-C or BREAK to exit...\n";
VOID 
main(VOID)
{

    Write(Output(), (STRPTR) VerTitle, strlen((STRPTR) VerTitle));
    Write(Output(), Copyright, strlen(Copyright));

    if (SysBase->LibNode.lib_Version > 36)
    {
        if (IntuitionBase = OpenLibrary("intuition.library", 37))
        {
            if (GfxBase = OpenLibrary("graphics.library", 37))
            {
                if (InstallWedge())
                {
                    Write(Output(), CBreak, strlen(CBreak));
                    Wait(SIGBREAKF_CTRL_C);
                    RemoveWedge();
                }
                CloseLibrary(GfxBase);
            }
            else
                Write(Output(), "Couldn't open graphics.library\n", 31);
            CloseLibrary(IntuitionBase);
        }
        else
            Write(Output(), "Couldn't open intuition.library\n", 32);
    }
    else
        Write(Output(), "Requires at least Kickstart 2.04\n", 33);
}


BOOL 
InstallWedge(VOID)
{
    struct JumpTable *jumptable;
    ULONG          *addressptr;
    UCOUNT          i, j;

    Forbid();

    /* Get pointer to JumpTable. Create it if necessary */
    if (jumptable = GetJumpTable(JTName))
    {
        /* Try to get exclusive lock on semaphore, in case it already existed. */
        if (AttemptSemaphore((struct SignalSemaphore *) jumptable))
        {
            /* Make sure nobody else has function addresses in the jumptable */
            if (jumptable->jt_Owner == NULL)
            {
                jumptable->jt_Owner = FindTask(0);
                /* Don't want to disable any longer than necessary */
                Disable();

                for (i = 2, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
                {
                    /* Replace addresses in the jumptable with my own. */
                    addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i);
                    (*((ULONG *) LVOArray[j].lt_oldFunction)) = (ULONG) * addressptr;
                    *addressptr = (ULONG) LVOArray[j].lt_newFunction;
                    D(zprintf("setting table to Function: 0x%lx\n", *addressptr));
                }
                Enable();
            }
            else
                Write(Output(), "Already running.\n", 16);
            ReleaseSemaphore((struct SignalSemaphore *) jumptable);
        }
        else
            Write(Output(), "Can't lock table.\n", 18);
    }
    else
        Write(Output(), "Can't create jumptable\n", 23);
    Permit();
    return ((BOOL) jumptable);
}

BOOL 
RemoveWedge(VOID)
{
    struct JumpTable *jumptable;
    ULONG          *addressptr;
    UCOUNT          i, j;

    Forbid();

    if (jumptable = GetJumpTable(JTName))
    {
        D(zprintf("jumptable @ 0x%lx\n", jumptable));

        /* Check if this task owns this jumptable */
        if (jumptable->jt_Owner == FindTask(0))
        {

            /* Get the semaphore exclusively.
             * Depending on what got SetFunction()'ed this could take some time.
             * Also note that shared locks are used to indicate the code is
             * being executed and that shared locks can jump ahead of queue'ed
             * exclusive locks, adding to the waittime.
             */
            ObtainSemaphore((struct SignalSemaphore *) jumptable);

            Disable();

            /* Restore old pointers in jumptable */

            for (i = 2, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
            {
                addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i);
                *addressptr = (*((ULONG *) LVOArray[j].lt_oldFunction));
                D(zprintf("setting table to oldFunction: 0x%lx\n", *addressptr));
            }

            Enable();

            jumptable->jt_Owner = NULL;
            ReleaseSemaphore((struct SignalSemaphore *) jumptable);
        }
    }

    Permit();

    return (TRUE);
}

struct JumpTable *
GetJumpTable(UBYTE * name)
{
    struct JumpTable *jumptable;
    ULONG          *addressptr;
    UWORD          *jmpinstr;
    UBYTE          *jtname;
    UCOUNT          i, j;

    /* Not really necessary to forbid again, just to indicate that I don't
     * want another task to create the semaphore while I'm trying to do the
     * same. Here GetJumpTable() is only called from InstallWedge(), so it
     * will just bump the forbid count.
     */
    Forbid();

    if (!(jumptable = (struct JumpTable *) FindSemaphore(name)))
    {
        if (jumptable = AllocMem(sizeof(struct JumpTable), MEMF_PUBLIC | MEMF_CLEAR))
        {
            if (jtname = AllocMem(32, MEMF_PUBLIC | MEMF_CLEAR))
            {

                for (i = 0, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
                {
                    jmpinstr = (UWORD *) ((UBYTE *) jumptable->jt_Function + i);
                    *jmpinstr = 0x4EF9;

                    addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i + 2);
                    *addressptr = (ULONG) SetFunction(
                        (struct Library *) (*((ULONG *) LVOArray[j].lt_LibBase)), 
                        LVOArray[j].lt_LVO, 
                        (VOID *) ((UBYTE *) jumptable->jt_Function + i));
                }

                jumptable->jt_Semaphore.ss_Link.ln_Pri = 0;

                strcpy(jtname, name);
                jumptable->jt_Semaphore.ss_Link.ln_Name = jtname;
                AddSemaphore((struct SignalSemaphore *) jumptable);
                /* In 1.2/1.3 AddSemaphore() didn't work properly.
                ** Under 1.2/1.3, change it to:
                ** InitSemaphore(jumptable);
                ** Forbid();
                ** Enqueue(&SysBase->SemaphoreList, jumptable);
                ** Permit();
                */
            }
            else
            {
                FreeMem(jumptable, sizeof(struct JumpTable));
                jumptable = NULL;
            }
        }
    }

    Permit();

    /* If succeeded, you now have a jumptable which entries point to the original
     * library functions. If another task SetFunction()'ed one or more of those
     * already, that task can never go away anymore.
     */
    return (jumptable);
}


/* Note: if you'd want this to work with 1.3, you wouldn't/couldn't lock the
 * semaphore, but instead would have to use a global to in- and decrement.
 * When exiting, you'd spin around the global counter, waiting for it to reach
 * zero. At that point you'd Disable(), reset the pointers in the jumptable and
 * Enable() again.
 */


struct Screen  *ASM 
newOpenScreen(REG(a0) struct NewScreen * newscreen, 
              REG(a4) ULONG * stackptr, 
              REG(a6) struct Library * base)
{
    struct SignalSemaphore *jt;
    struct Screen  *screen = NULL;

    /* Find the semaphore to lock shared, indicating the routine is being run. */
    /* For speed reasons you may want to cache the pointer to the semaphore
    ** in a global variable */
    if (jt = FindSemaphore(JTName))
    {
        /* Lock shared in 2.0. In 1.3 you'd increment a global counter */
        ObtainSemaphoreShared(jt);
        /* Simple test for valid argument. Could check all the fields too. */
        if (newscreen != NULL)
        {
            screen = (*oldOpenScreen) (newscreen, base);
        }
        else
        {
            ULONG           ACaller = stackptr[ACALLER];
            ULONG           CCaller = stackptr[CCALLER];

            Forbid();           /* To make sure the output isn't garbled */
            zprintf("OpenScreen(NULL) by `%s' (at 0x%lx) from A:0x%lx C:0x%lx SP:0x%lx\n",
                    SysBase->ThisTask->tc_Node.ln_Name, 
                    SysBase->ThisTask, 
                    ACaller, 
                    CCaller, 
                    stackptr);
            Permit();
        }
        /* Release shared lock. In 1.3  you'd decrement the global pointer */
        ReleaseSemaphore(jt);
    }

    return (screen);
}

struct Window  *ASM 
newOpenWindowTagList(REG(a0) struct NewWindow * newwindow, 
                     REG(a1) struct TagItem * tags, 
                     REG(a4) ULONG * stackptr, 
                     REG(a6) struct Library * base)
{
    struct SignalSemaphore *jt;
    struct Window  *window = NULL;


    if (jt = FindSemaphore(JTName))
    {
        ObtainSemaphoreShared(jt);
        if (newwindow != NULL || tags != NULL)
        {
            window = (*oldOpenWindowTagList) (newwindow, tags, base);
        }
        else
        {
            ULONG           ACaller = stackptr[ACALLER];
            ULONG           CCaller = stackptr[CCALLER];

            Forbid();
            zprintf("OpenWindowTagList(NULL,NULL) by `%s' (at 0x%lx) from A:0x%lx C:0x%lx SP:0x%lx\n",
                    SysBase->ThisTask->tc_Node.ln_Name, 
                    SysBase->ThisTask, 
                    ACaller, 
                    CCaller, 
                    stackptr);
            Permit();
        }
        ReleaseSemaphore(jt);
    }

    return (window);
}


VOID ASM 
newFreeMem(REG(a1) VOID * memptr, 
           REG(d0) ULONG size, 
           REG(a4) ULONG * stackptr, 
           REG(a6) struct Library * base)
{
    struct SignalSemaphore *jt;

    if (jt = FindSemaphore(JTName))
    {
        ObtainSemaphoreShared(jt);
        if (memptr != NULL && size != 0L)
        {
            (*oldFreeMem) (memptr, size, base);
        }
        else
        {
            ULONG           ACaller = stackptr[ACALLER];
            ULONG           CCaller = stackptr[CCALLER];

            Forbid();
            zprintf("FreeMem(0x%lx,%ld) by `%s' (at 0x%lx) from A:0x%lx C:0x%lx SP:0x%lx\n",
                    memptr, size,
                    SysBase->ThisTask->tc_Node.ln_Name, 
                    SysBase->ThisTask, 
                    ACaller, 
                    CCaller, 
                    stackptr);
            Permit();
        }

        ReleaseSemaphore(jt);
    }
}

VOID ASM 
newSetFont(REG(a1) struct RastPort * rp, 
           REG(a0) struct TextFont * font, 
           REG(a4) ULONG * stackptr, 
           REG(a6) struct Library * base)
{
    struct SignalSemaphore *jt;


    if (jt = FindSemaphore(JTName))
    {
        ObtainSemaphoreShared(jt);
        if (rp != NULL && font != NULL)
        {
            (*oldSetFont) (rp, font, base);
        }
        else
        {
            ULONG           ACaller = stackptr[ACALLER];
            ULONG           CCaller = stackptr[CCALLER];

            Forbid();
            zprintf("SetFont(0x%lx,0x%lx) by `%s' (at 0x%lx) from A:0x%lx C:0x%lx SP:0x%lx\n",
                    rp, font,
                    SysBase->ThisTask->tc_Node.ln_Name, 
                    SysBase->ThisTask, 
                    ACaller, 
                    CCaller, 
                    stackptr);
            Permit();
        }
        ReleaseSemaphore(jt);
    }
}







===================================================================









**
**      ISpy stubs
**
**      Copyright (c) 1991 Commodore-Amiga, Inc.
**          All Rights Reserved
**
**
	INCLUDE "exec/types.i"

	SECTION CODE

	XREF	_SysBase

	XREF    _LVOForbid
	XREF    _LVOPermit

	XREF	_newOpenScreen
	XREF	_newOpenWindowTagList
	XREF	_newFreeMem
	XREF	_newSetFont

	XDEF	_OpenScreenStub
	XDEF	_OpenWindowTagListStub
	XDEF	_FreeMemStub
	XDEF	_SetFontStub


_OpenScreenStub:
	movem.l	a4,-(sp)
	lea		4(sp),a4
	jsr		_newOpenScreen
	movem.l	(sp)+,a4
	rts

_OpenWindowTagListStub:
	movem.l	a4,-(sp)
	lea		4(sp),a4
	jsr		_newOpenWindowTagList
	movem.l	(sp)+,a4
	rts

_FreeMemStub:
	movem.l	a4,-(sp)
	lea		4(sp),a4
	jsr		_newFreeMem
	movem.l	(sp)+,a4
	rts

_SetFontStub:
	movem.l	a4,-(sp)
	lea		4(sp),a4
	jsr		_newSetFont
	movem.l	(sp)+,a4
	rts

	END
