[comp.os.msdos.programmer] Chaining interrupt handlers in Turbo C

dandrews@bilver.uucp (Dave Andrews) (04/18/91)

/*
 *  A week or so ago, someone asked:
 *    "how can I chain to the next interrupt handler in Turbo C?"  
 *
 *  Shortly after the post appeared, I needed the same function.
 *  I see from _Undocumented DOS_ that MSC has the "_chain_intr"
 *  builtin that does what you want.  Unfortunately, TC doesn't
 *  have an equivalent function (see _Undocumented DOS_ page 379).
 *
 *  Here is my solution to the problem.  It is not particularly
 *  elegant, and is HIGHLY dependent on the code TC++ 1.0 generates
 *  for INTERRUPT function epilogs.  I've only tested it on a
 *  '286 machine in the large model.  YMMV and all that.
 *
 *  Just before exit, the stack contains saved registers, CS/IP
 *  and flags.  I shift the stack, duplicating the CS/IP/flags
 *  (6 bytes) and modify the topmost CS/IP to point to the previous
 *  interrupt handler.  Then I let TC's generated exit code restore
 *  registers and IRET to the previous handler.  When the previous
 *  handler does its own IRET, it returns control to my original
 *  caller.
 *
 *  Note that the chain_intr macro destroys a number of registers
 *  rather indiscriminately.  Use chain_intr() just before your
 *  INTERRUPT function executes a return.  The stack should be
 *  the same as it was on entry, i.e. don't use chain_intr()
 *  inside a subordinate function call.
 *
 *  Hope this helps.
 *
 *  - David Andrews      bilver!dandrews
 */
 
#include <dos.h>
 
#define chain_intr(old)                                                  \
  asm {                                                                  \
    push ds;                   /* Need DS for access to "old" pointer */ \
    mov  si,sp;                /* -> current top of stack             */ \
    sub  sp,6;                 /* -> new top of stack after shift     */ \
    mov  di,ss;                /* Setup DS, ES for move               */ \
    mov  ds,di;                /*   DS:SI is source of move           */ \
    mov  es,di;                /*                                     */ \
    mov  di,sp;                /*   ES:DI is destination of move      */ \
    mov  cx,12;                /* Moving 24 bytes (12 words)          */ \
    rep  movsw;                /* Shift stack 6 bytes                 */ \
    pop  ds;                   /* Restore DS                          */ \
    cli  ;                     /* Protect stack                       */ \
    add  sp,22;                /* Zap IRET address to prior handler   */ \
    push DWORD PTR _##old;     /*     "                               */ \
    sub  sp,18;                /*     "                               */ \
    sti  ;                     /* Unprotect stack                     */ \
    }
 
void interrupt (*oldfunc) ();  /* Object of getvect(), setvect()      */
 
void interrupt first () {      /* Sample handler - installed first    */
 
  puts("First");
  }
 
void interrupt second () {     /* Sample handler - installed second   */
 
  puts("Second");
  chain_intr (oldfunc);        /* Example use of "chain_intr" macro   */
  }
 
void main() {
 
  setvect(255, first);         /* Install "first" as INT 255 handler  */
  oldfunc = getvect(255);      /* getvect() its address               */
  setvect(255, second);        /* Install the second INT 255 handler  */
  geninterrupt(255);           /* Call the second INT 255 handler.    */
                               /* It will execute and pass control to */
                               /* the previous handler (whose address */
                               /* is stored in "oldfunc").  Expected  */
                               /* output is:                          */
                               /*   Second                            */
                               /*   First                             */                                                                                                                                      
 };

few@gupta.portal.com (Frank Whaley) (04/25/91)

In article <1991Apr17.214023.22334@bilver.uucp> dandrews@bilver.uucp (Dave Andrews) writes:

> *  A week or so ago, someone asked:
> *    "how can I chain to the next interrupt handler in Turbo C?"

And Dave responded with a very interesting version of chain_intr().  I
never would have thought of his method.  Unfortunately there are a
couple of problems.

  Interrupts are improperly chained if the interrupt handler function
  has local variables.  Turbo C (2.0, I can't speak for TC++ or BC++)
  only sets the BP register when the interrupt function uses local
  variables, so it cannot be used in all cases to unroll the stack.
  The best way to write handlers with local data:
    void interrupt handler()
    {
      if ( somethingWeDo )
        doIt();
      else
        chain_intr(original);
    }
      
  Dave's macro can only be used before a return, either stated or
  implied.  So:
    if ( notForMe )
    {
      chain_intr(original);
      return;  /*  required  */
    }
    ...

I've attached a new version of what I use.  It still suffers from not
working in handlers with local variables, but it can be called anywhere
and is implemented as a function.

	PAGE	60, 132
TITLE	chain	24-Apr-91	Chain interrupt				|

;-----------------------------------------------------------------------|
;									|
;	Turbo C Library  --  ChainIntr					|
;		Chain interrupt						|
;									|
;-----------------------------------------------------------------------|

	;  sizes
LCODE	=	0
    IFDEF __MEDIUM__
LCODE	=	1
    ENDIF
    IFDEF __LARGE__
LCODE	=	1
    ENDIF
    IFDEF __HUGE__
LCODE	=	1
    ENDIF

	;  Code Segment
    IF LCODE
CHAIN_TEXT	Segment Byte Public 'CODE'
	Assume	CS:CHAIN_TEXT
    ELSE
_TEXT	Segment Byte Public 'CODE'
	Assume	CS:_TEXT
    ENDIF

	PAGE
;-----------------------------------------------------------------------|
;	Chain interrupt							|
;									|
;	SYNOPSIS							|
;		void ChainIntr(void (interrupt *target)());		|
;									|
;	DESCRIPTION							|
;		Chain to another interrupt handler, passing original	|
;		(or possibly modified) register values.			|
;									|
;	RETURNS								|
;		This function returns to the original source of the	|
;		interrupt.						|
;									|
;-----------------------------------------------------------------------|

	Public	_ChainIntr
    IF LCODE
_ChainIntr	Proc	Far
    ELSE
_ChainIntr	Proc	Near
    ENDIF

	POP	AX			;  clear return addr
    IF LCODE
	POP	AX
    ENDIF

	POP	BX			;  get target addr
	POP	AX

	MOV	BP,SP			;  store target addr
	XCHG	AX,[BP + 16]
	XCHG	BX,[BP + 14]

	POP	BP			;  restore saved registers
	POP	DI
	POP	SI
	POP	DS
	POP	ES
	POP	DX
	POP	CX
	RETF				;  return to target

_ChainIntr	EndP

    IF LCODE
CHAIN_TEXT	EndS
    ELSE
_TEXT	EndS
    ENDIF

	END				; of chain.asm
-- 
Frank Whaley
Software Engineer
Gupta Technologies
few@gupta.com

dandrews@bilver.uucp (Dave Andrews) (04/29/91)

In article <1991Apr24.204727.27737@gupta.portal.com> few@gupta.portal.com (Frank Whaley) writes:
>In article <1991Apr17.214023.22334@bilver.uucp> dandrews@bilver.uucp (Dave Andrews) writes:
>
>> *  A week or so ago, someone asked:
>> *    "how can I chain to the next interrupt handler in Turbo C?"
>
>And Dave responded with a very interesting version of chain_intr().  I
>never would have thought of his method.  Unfortunately there are a
>couple of problems.
>
>  Interrupts are improperly chained if the interrupt handler function
>  has local variables.  Turbo C (2.0, I can't speak for TC++ or BC++)
>  only sets the BP register when the interrupt function uses local
>  variables, so it cannot be used in all cases to unroll the stack.

Indeed, this is a problem, but I only (obviously!) tested with global
variables.  I generally hesitate to use local variables in a procedure
of type INTERRUPT, because I don't know how big a stack is being
provided by the caller.

Outside of this problem, there is a "feature" in the code that found
its way into the posting.  I don't move enough of the stack (to quote
Maxwell Smart, "Missed it by THAT much").  Replace:
    mov  cx,12
by:
    mov  cx,13

Frank, is your ChainIntr function equivalent to the MSC _chain_intr
builtin?

Thanks for your comments and code!

- David Andrews
  bilver!dandrews