;
; From Gertjan Klein - Chaining into INT 21..
;
comment ;

 > newint:
 >     pushf
 >     call dword ptr cs:[oldint]
 >     retf    2
 > oldint:
 >     dw 0,0

  Unfortunately, the above may _appear_ to work fine, but it has a rather
serious bug. If the interrupt handler using the above code is the first
in the chain (i.e., the last one loaded), and the PC runs in real mode,
this code results in interrupts being off most of the time. When an
interrupt is issued, the CPU pushes the current flags on stack, disables
interrupts, and calls the far address 0:(intno * 4). Therefore, the
above pushf pushes the flags with interrupts disabled! As the original
flags, which most likely had interrupts enabled, get discarded by the
retf 2, interrupts are still disabled when the int 21h returns to the
caller.

  A quick fix for this problem could be to insert a sti instruction
before the retf 2, making sure interrupts are enabled on return of the
int 21h. This will do in 99% of all cases I presume, perhaps even all.
It still doesn't satisfy me, though: if the intercepted int 21h was
issued with interrupts disabled, it gets them back enabled - which would
not happen if only DOS (which merely manipulates the CY flag on stack)
was present in the int 21h chain. I doubt if software exists that breaks
on this, though - but a decent interrupt handler should be as invisible
as possible.

  I've tested an alternative solution that doesn't have any of the above
objections. As far as I can tell it is as close to perfect as possible,
apart from it being a little more complicated then the other solutions.
I've tested it using SoftICE, code below.

  Gertjan

;
;Sample code examining how to chain int 21h from a TSR.
;Assembly tested with TASM 2.0.
;Copyright 1995 G. Klein, donated to the public domain.

        .model  tiny
        .code
nul     equ     $
        org     100h

BROKENNESS      EQU     0
;0 For somewhat complicated, but perfect (I hope) solution;
;1 For easy fix
;2 For your original suggestion

start:
        jmp     init

oldaddr label   dword
oldoffs dw      ?
oldseg  dw      ?

handler:
IF      BROKENNESS EQ 2

        pushf                           ;Simulate interrupt
        call    cs:[oldaddr]            ;Call previous handler
        retf    2                       ; and return to caller

ENDIF
IF      BROKENNESS EQ 1

        pushf                           ;Simulate interrupt
        call    cs:[oldaddr]            ;Call previous handler
        sti                             ;Enable interrupts
        retf    2                       ; and return to caller

ENDIF
IF      BROKENNESS EQ 0

        push    ax                      ;Save AX
        push    bp                      ; and BP
        mov     bp,sp                   ;Make stack addressable through BP
        mov     ax,[bp+8]               ;Get original flags in AX
        xchg    ax,[bp+2]               ;Convert 'push ax' above to pushf of
                                        ; original flags, and restore AX
        pop     bp                      ;Restore BP
        call    cs:[oldaddr]            ;Call previous handler
        retf    2                       ; and return to caller

ENDIF

init:
paragraphs      equ     (($ - nul) + 15) / 16

        mov     ax,3521h                ;Get int 21h vector
        int     21h
        mov     [oldoffs],bx            ;Save offset
        mov     [oldseg],es             ; and segment
        mov     ax,2521h                ;Set new vector
        mov     dx,offset handler       ; to our handler
        int     21h
        mov     ax,3100h                ;Terminate and stay resident
        mov     dx,paragraphs           ; with this many paragraphs
        int     21h

        end     start

