[comp.sources.atari.st] v01i071: break -- break out of programs with a keypress

koreth@ssyx.ucsc.edu (Steven Grimm) (08/25/88)

Submitted-by: uunet!mcvax!philmds!leo (Leo de Wit)
Posting-number: Volume 1, Issue 71
Archive-name: break

The following program, when placed in the AUTO folder or started by hand,
lets you interrupt any program (except the desktop).
The corresponding binary is sent to the moderator of comp.binaries.atari.st.
The program was assembled and linked with the assembler and linker from the
GST-C compiler.

For correspondence conceirning this program (bugs, questions etc.) try

      L. J. M. de Wit
      Nachtegaallaan 7
      5731XP Mierlo
      Holland
      e-mail: ..!mcvax!philmds!leo
              (or perhaps  ..!hp4nl!philmds!leo)

------------------------------ starts here -----------------------------------
******************************************************************************
*                                                                            *
*    break.asm  version 1.0 of 20 August 1988   (C) L.J.M. de Wit 1988       *
*                                                                            *
* This software may be used and distributed freely if not used commercially  *
* and the originator (me) is mentioned.                                      *
*                                                                            *
******************************************************************************
*
* NAME
*    break - stop current program on receipt of interrupt character
*
* SYNTAX
*    break [-e|-d|-z] [-i|-c<code>]
*
* DESCRIPTION
*
*    After installing break, any program can be interrupted.
*    This is achieved by 'extending' the existing keyboard interrupt
*    routine: after executing the old code the break character check
*    is done.
*
*    The various flags have the following meaning:
*       e(nable) : the current break character will end the program.
*                  the code returned is -32, what seems to be the standard
*                  value for programs interrupted by ^C in GEMDOS.
*       d(isable): no actions are done; this restores the old behaviour
*       z(ero)   : the current break character will be discarded (made 0)
*                  in the input buffer; this can be used to disable ^C.
*       i(nput)  : the break character is prompted for. Combinations with
*                  shift, control and alternate keys are also allowed.
*                  Useful for specifying the break character interactively.
*       c(ode)   : specifies the break character as a hexadecimal code.
*                  The hex code must follow the 'c' flag immediately.
*                  Useful for specifying the break character from a script.
*    Of the flags e,d and z only one should be used; e is the default.
*    Also, of the flags i and c only one should be used; control-delete is
*    the default. This is done on purpose; you can always change it to ^C
*    if you want to (or whatever key you like).
*     
*    The break program can be reused indefinitely, because a next invocation
*    is not made memory resident; it only modifies parameters in the first
*    invocation (the resident one).
*
*    The program can be placed into the \AUTO folder to be installed
*    automatically, or activated 'by hand'. If placed in the \AUTO folder, it
*    should of course have a .PRG extension (break.prg); as \AUTO folder
*    programs don't get arguments, the break will be enabled and the break
*    character is control-delete in this case.
*
* BUGS/SHORTCOMINGS
*    A nice extension would be the possibility to catch the interrupt from
*    a user program; this could be achieved by using a new trap. As this
*    implies restoring the old interrupt catch routine when the program
*    exits, and maybe also core dumps could be added to the action of an
*    (other) interrupt character, such a more general signal mechanism is
*    not added (yet). Gives me time to think of a nice implementation 8-).
*
* JOKE
*    Gimme a break, huh?!
*

      module  break
      section s.ccode

* character codes
tab      equ   9
lf       equ   10
cr       equ   13

* GEMDOS & (X)BIOS stuff
gemdos   equ   1
bios     equ   13
xbios    equ   14
ptermres equ   $31
setexc   equ   5
cconws   equ   9
crawcin  equ   7
super    equ   $20
pterm0   equ   0
pterm    equ   $4c
iorec    equ   14

* divers
bpaglen  equ   $100
textlen  equ   12
datalen  equ   20
bsslen   equ   28
intrupt  equ   -32               * Code returned by interrupted programs
curproc  equ   $602c             * Contains base page start of current process

* iorec struct offsets; the buffer ptr. itself has offset 0
ibufsize equ 4
ibufhead equ 6
ibuftl   equ 8
ibuflow  equ 10
ibufhi   equ 12


brkinit
      move.l   4(sp),a0
      adda.w   #$80,a0           * a0 points to argument string start
      moveq.l  #0,d0
      move.b   (a0)+,d0          * Get string length &
      clr.b    (a0,d0.w)         * ensure '\0' termination
nxtbyt
      move.b   (a0)+,d0
      beq      nxtdone           * '\0' char is end of string
      cmp.b    #'A',d0
      blt.s    arge
      cmp.b    #'Z',d0
      bgt.s    arge
      or.b     #$20,d0           * Convert uppercase to lower
arge
      cmp.b    #'e',d0           * enable
      bne.s    argd
      st       enabled           * enabled = TRUE
      bra.s    nxtbyt
argd
      cmp.b    #'d',d0           * disable
      bne.s    argi
      sf       enabled           * enabled = FALSE
      bra.s    nxtbyt
argi
      cmp.b    #'i',d0           * input
      bne.s    argc
      move.l   a0,-(sp)
      pea.l    inpmsg
      move.w   #cconws,-(sp)
      trap     #gemdos           * Prompt for break char
      addq.l   #6,sp
      move.w   #crawcin,-(sp)
      trap     #gemdos           * Read the char
      addq.l   #2,sp
      move.l   d0,breakcode      * and store it
      pea.l    retmsg
      move.w   #cconws,-(sp)
      trap     #gemdos           * Print a cr/lf
      addq.l   #6,sp
      move.l   (sp)+,a0
      bra.s    nxtbyt
argc
      cmp.b    #'c',d0           * code of break char in hex
      bne.s    argz
      moveq.l  #0,d1             * d1 will contain the code
argcnxt
      move.b   (a0)+,d0
      bne.s    argcnz            * Not end of arg string yet
      move.l   d1,breakcode      * If end, store break char
      bra      nxtdone           * and goto end of arg string interpretation
argcnz
      cmp.b    #' ',d0           * End of number found
      beq.s    argcend
      cmp.b    #tab,d0           * this one too
      beq.s    argcend
      cmp.b    #'0',d0           * Now follows a series of tests and
      blt.s    userr             * conversions to find the hex digit's value.
      cmp.b    #'a',d0
      blt.s    argcnlc
      sub.w    #32,d0            * convert lower case to upper
argcnlc
      cmp.b    #'F',d0
      bgt.s    userr
      cmp.b    #'9',d0
      ble.s    argcnum
      cmp.b    #'A',d0
      blt.s    userr
      subq.l   #7,d0             * Make up for diff between '9' and 'A'
argcnum
      sub.b    #'0',d0           * '0' is the base: 0
      asl.l    #4,d1             * Multiply by 16
      or.b     d0,d1             * OR in the 4 low digits of d0 into d1
      bra.s    argcnxt
argcend
      move.l   d1,breakcode      * Store the break key code
      bra      nxtbyt
argz
      cmp.b    #'z',d0
      bne.s    argsep
      st       zeroed            * Set the 'zeroed' flag
      bra      nxtbyt
argsep
      cmp.b    #' ',d0           * Accept space
      beq      nxtbyt
      cmp.b    #tab,d0           * and tab
      beq      nxtbyt
      cmp.b    #'-',d0           * and hyphen as separators (not strictly)
      beq      nxtbyt
userr
      pea.l    usemsg            * If error in arg string show usage message
      move.w   #cconws,-(sp)
      trap     #gemdos
      addq.l   #6,sp
      move.w   #1,-(sp)
      move.w   #pterm,-(sp)
      trap     #gemdos           * and terminate with error status

nxtdone
      move.w   #1,-(sp)
      move.w   #iorec,-(sp)
      trap     #xbios
      addq.l   #4,sp
      move.l   d0,iop            * Save pointer to iorec struct
      move.l   #-1,-(sp)
      move.w   #$46,-(sp)
      move.w   #setexc,-(sp)
      trap     #bios
      addq.l   #8,sp
      move.l   d0,oldvec         * Save old keyboard interrupt vector
      lea.l    breakey,a0
      sub.l    a0,d0
      move.l   d0,diff           * Difference between start addr of old and new
      clr.l    -(sp)
      move.w   #super,-(sp)
      trap     #gemdos           * Supervisor mode
      addq.l   #2,sp
      move.l   d0,(sp)           * Save supervisor stack pointer on stack
      lea.l    magic,a0          * a0 points to 'magic' string in this prog
      move.l   diff,d0
      lea.l    (a0,d0.l),a1      * a1 points (perhaps) to 'magic' in old
      move.l   #(magicend-magic-1),d0 * # chars in 'magic' string minus one
chkmag
      cmp.b    (a0)+,(a1)+       * Check strings for equality
      dbne     d0,chkmag
      beq.s    mageq             * If old prog DID contain magic string; else:
magne
      move.w   #super,-(sp)
      trap     #gemdos           * Back to user mode (sp was still on stack)
      addq.l   #6,sp
      clr.l    diff              * First incarnation: will be made resident
      pea.l    breakey           * breakey will be new keyboard int. vector
      move.w   #$46,-(sp)
      move.w   #setexc,-(sp)
      trap     #bios             * Set it
      addq.l   #8,sp
      move.l   4(sp),a0          * basepage start
      move.l   #bpaglen,d0       * base page length
      add.l    textlen(a0),d0    * + text length
      add.l    datalen(a0),d0    * + data length
      add.l    bsslen(a0),d0     * + bss length
      clr.w    -(sp)             * return value: 0 for success
      move.l   d0,-(sp)          * # bytes to keep
      move.w   #ptermres,-(sp)   * keep process
      trap     #gemdos           * stops here...

mageq
      move.w   #super,-(sp)      * A next incarnation of break in this branch.
      trap     #gemdos           * Back to user mode (sp was still on stack)
      addq.l   #6,sp
      move.l   diff,d0
      lea.l    enabled,a0
      move.b   (a0),(a0,d0.l)    * Copy 'enabled' into old image
      lea.l    zeroed,a0
      move.b   (a0),(a0,d0.l)    * Do this too for 'zeroed'
      lea.l    breakcode,a0
      move.l   (a0),(a0,d0.l)    * And also for the current break char
      move.w   #pterm0,-(sp)
      trap     #gemdos           * Exits here with 0 as status.

breakey
* The new interrupt routine
      pea.l    breaketc          * Return to breaketc after executing old code
      move.w   sr,-(sp)          * This is because old code ends with 'rte'
      move.l   oldvec,-(sp)      * Push old vector onto stack
      rts                        * and jump to it
breaketc
      movem.l  d0-d1/a0-a2,-(sp)
      tst.b    enabled
      beq.s    breakend          * If not enabled do nothing
      move.l   iop,a0
      move.w   ibuftl(a0),d0
      cmp.w    ibufhead(a0),d0
      beq.s    breakend          * If empty keyboard buffer do nothing
      moveq.l  #2,d0
      move.l   curproc,a1
testbp
      tst.l    (a1)              * At end of list?
      beq.s    breakend          * Cur. prog was desktop or so; don't break
      move.l   36(a1),a1         * Back link to previous process
      dbra     d0,testbp
      moveq.l  #0,d0
      move.w   ibufhead(a0),d0   * d0 is used as index into char buffer
      move.l   breakcode,d1
      move.l   (a0),a1           * a1 points to character buffer
testbuf
      cmp.w    ibuftl(a0),d0
      beq.s    breakend          * Checked them all
      addq.l   #4,d0             * Increment index in buffer
      cmp.w    ibufsize(a0),d0   * If at buffer size
      bne.s    notatend
      moveq.l  #0,d0             * wrap it (circular buffer)
notatend
      cmp.l    (a1,d0.w),d1      * Is char at this index the break char?
      bne.s    testbuf           * No; check next
      tst.b    zeroed            * Yes; is 'zeroed' flag set?
      beq.s    notzeroed
      clr.l    (a1,d0.w)         * Make the break char in the buffer 0;
      bra.s    testbuf           * this eliminates any special meaning
notzeroed
      move.w   ibuftl(a0),ibufhead(a0) * Clear the entire buffer
      move.l   #stop,22(sp)      * Return will be to the routine 'stop'
breakend
      movem.l  (sp)+,d0-d1/a0-a2
      rte

stop
      move.w   #intrupt,-(sp)    * Code for interrupted programs
      move.w   #pterm,-(sp)
      trap     #gemdos           * and stop here

   section  s.data

iop         dc.l  0              * Pointer to iorec struct
breakcode   dc.l  $0053001f      * Default is control_delete
diff        dc.l  0              * Difference in byte distance old <-> new
oldvec      dc.l  0              * previous val of interrupt vector
enabled     dc.b  $ff            * 'enabled' is default true.
zeroed      dc.b  0              * 'zeroed' is default false.
magic       dc.b  'BREAK MARKER',0 * magic string that identifies this prog.
magicend
inpmsg      dc.b  'Input the break code by hitting the key(s) : ',0
usemsg      dc.b  'break: usage: break [-e|-d|-z] [-i|-c<code>]',cr,lf,0
retmsg      dc.b  cr,lf,0

   end