← Back to Technotes

#1: How to Install Custom BRK and /NMI Handlers

Author: Jim Merritt (rev. Jim Mensch)
Year: 1986

... discusses a method to install a custom debugger or debugging stub within the Apple IIGS system.

View raw text file

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.