Author: Jim Merritt (rev. Jim Mensch)
Year: 1986
... discusses a method to install a custom debugger or debugging stub within the Apple IIGS system.
Apple II
Technical Notes
_____________________________________________________________________________
Developer Technical Support
Apple IIGS
#1: How to Install Custom BRK and /NMI Handlers
Revised by: Jim Mensch & Jim Merritt November 1988
Written by: Jim Merritt October 1986
This Technical Note discusses a method to install a custom debugger or
debugging stub within the Apple IIGS system.
_____________________________________________________________________________
Introduction
This Technical Note discusses a particular method that you may use to install
a custom debugger or debugging stub within the Apple IIGS system. The
strategy and techniques described here should be of special interest to those
who wish to operate the Apple IIGS as a slave to a debugger that resides on
another machine.
Typically, an interrupt handler should pass control to a debugger or debugging
stub whenever the processor executes a BRK instruction, or when an interface
card triggers a non-maskable interrupt (/NMI). To simplify the design of the
debugger, the Apple IIGS Monitor should be responsible for the following:
o saving all machine state information in locations that the
debugger can access
o setting the machine to a known state
o passing control to an arbitrary debugger
o restoring the remembered machine state upon regaining control from
the debugger
o resurrecting the interrupted process
The Monitor is designed to provide all of the services above for the BRK
instruction, but only the third for /NMI interrupts. In addition, Apple II
family systems are generally intolerant of /NMI interrupts. In this Technical
Note we concentrate on the means by which you can install your own custom BRK
handler, although we also briefly examine /NMI considerations.
Dealing With BRK
A BRK interrupt handler may reside at any address in memory. The Monitor
passes control to your code by executing a JSL instruction; consequently, your
routine must terminate with an RTL instruction. To install your BRK handler,
simply load it into memory, call the Miscellaneous Tool Set GetVector routine
to fetch the address of the current BRK handler, put that address in a safe
place, then supply the address of your handler to the Miscellaneous Tool Set
SetVector routine. To deactivate your handler, restore the previous handler
address using SetVector as follows:
;
; NOTE: All Listings are in APW assembler format.
;
INSTMYBRK anop ;Example code to install user's BREAK handler.
PushLong #0 ;Space for function call result.
PushWord #$1C ;We want BREAK vector address.
_GetVector ;Make the call using standard macro.
; The stack now holds address of the current break handler.
PLA ;Get and save low word of address...
STA SBRKADR
PLA ; ...and now high word.
STA SBRKADR+2
PushWord #$1C ;We want to change BREAK vector address.
PushLong #MYHANDLR ;Address of user's BRK handler.
_SetVector ;Make the call using standard macro.
; Custom handler is in place, now go off and do whatever we like...
DEACMYBRK anop ;Example code to deactivate the BRK handler.
PushWord #$1C ;We want to change BREAK vector address.
PushLong SBRKADR ;The previous BRK handler address.
_SetVector ;Make the call using standard macro.
Upon entry to your code, the machine will be in eight-bit native mode.
Specifically, the m and x bits will be set (forcing eight-bit accumulator,
memory access, and index registers), the processor will be running at the
normal (1 MHz) speed, all memory shadowing will be enabled, and both the
direct page and data bank registers will be reset to zero. The same
conditions must hold when your BRK handler returns control to the Monitor.
While your code is active, however, it is free to affect the machine state in
arbitrary ways, including (but not limited to) widening the registers,
increasing the clock rate, and disabling shadowing. Before returning control
to the Monitor, your break handler must also clear the processor's carry flag,
as an indication that the BRK was indeed serviced by an external handler.
(Note: The default BREAKVECTOR points to a "no-op" handler that simply sets
the carry flag to indicate that there is no external handler available, and it
then executes an RTL.)
When a BRK occurs, the processor saves the machine's state in the BRK.VAR
area, and you may obtain this address with the Miscellaneous Tool Set GetAddr
routine as follows:
PushLong #0 ; space for result
PushWord #9 ; we want BRK.VAR address
_GetAddr ; make the call using standard macro
; The stack now holds the address of the BRK.VAR area, expressed as a long
word (four bytes).
Coping With /NMI
Handling /NMI interrupts is, by far, a trickier proposition than fielding BRK
instructions. For example, the user-definable /NMI jump-vector, /NMI
($0003FB), only has room in its three-byte JMP-absolute instruction for a two-
byte address. Because of this size limitation, at least the "front end" of
any /NMI handler must reside in bank $00. In addition, the Monitor does not
"condition" the system in any way before transferring control through the /NMI
hook, so the system could be in native mode, emulation mode, or any hybrid
mode (with any screen condition) upon entry to your handler. (Note: Although
the 65816 processor provides for separate /NMI vector addresses in native and
emulation modes, the Apple IIGS implementation of these two vectors pass
control to the same user hook at $0003FB.) The processor only saves minimal
machine state information when an /NMI occurs; if the handler needs to
preserve more than the program counter and status register (which are saved
automatically), then it must do so explicitly. Because the 65816 assumes any
program running in emulation mode has its program bank register in bank zero,
it will not save the program bank register for any program running in
emulation mode outside of bank zero. Code which runs in this manner will
always crash if it makes any attempt to return from the interrupt. Finally,
/NMI interrupts can create havoc with disk access and other aspects of the
system; consequently, the only way you can safely use /NMI interrupts is as a
one-way "escape hatch" to emergency debugging code.
Here are some ground rules for /NMI interrupt handlers.
o On entry, store any interesting registers or machine state in RAM
space owned by the handler.
o Determine whether the processor is in emulation mode or native
mode.
o Take appropriate action, depending upon the processor mode.
o Under no circumstances try to return from the interrupt! Restart
the system instead.
To install an /NMI handler, load it into some free RAM in bank $00, put the
two-byte address currently at location /NMI+1 in a safe place, then replace it
with the address of your handler. To deactivate your handler (assuming
nothing has yet invoked it), simply restore the previous handler address to
/NMI+1.