[comp.os.msdos.programmer] Possible Intr bugs

andyross@ddsw1.MCS.COM (Andrew Rossmann) (08/25/90)

  In working on my INFOPLUS program, I came across a problem with Turbo
Pascal 5.5 that may also be present in other programming languages. It
deals with making your own interrupt calls (Intr(intno, regs) in Turbo
Pascal). Turbo Pascal does not really do an interrupt, but instead, gets
the address of the interrupt from the interrupt table, pushes it on the
stack, and then does a RETF!! This code works great normally, but may fail
under protected or Virtual 8086 mode, where a control program can trap
interrupts and act on them itself. Windows 3.0 DPMI driver does this.
  What follows is some assembly code for Turbo Pascal 5.5, that might be
adaptable to other languages (I'd assume Turbo C might work similarly if
not identically.) that offers true interrupts.

  Andrew Rossmann
  andyross@ddsw1.MCS.COM


;--------------------------------------------------------------------
;       AltIntr.ASM
;       extracted from Infoplus 1.30 by Andrew Rossmann
;
;               ALTINTR         - calls interrupts with a true INT call
;               ALTMSDOS        - calls DOS with a true INT call
;
;       Andrew Rossmann  [andyross@ddsw1.MCS.COM]
;       Aug. 25, 1990
;--------------------------------------------------------------------
        public  ALTINTR, ALTMSDOS

; This is coded in a very generic assembly format for best compatibility with
; all assemblers.
; Make sure you have a {$L altintr} in your program. You MUST define both
; ALTINTR and ALTMSDOS!!

CODE    segment byte

;   AltIntr is an alternative to the Intr function. The standard Intr function
; does not do a true Interrupt!! Instead, it gets the address of the interrupt
; from the interrupt table, loads all the registers, and then does a RETF!!!
; The address of a return routine has been pushed on the stack so that it
; returns to TP and unloads the registers. This was probably done because
; Intel saw to it that all interrupt numbers must be immediate, and Borland
; didn't want to use self-modifying code.
;   Now, normally, the above procedure works perfectly fine, except under 1
; condition. When the CPU is under protected or Virtual 86 mode. When in those
; modes, a program with higher privileges can trap an interrupt and act on it.
; I found this out the hard way by going nuts wondering why I couldn't detect
; DPMI drivers or Windows!! My ALTernative INTerrupt functions identically to
; Borlands, but uses self-modifying code to generate a true interrupt.
;   NOTE: The MsDos routine is ALSO affected by this problem. It just stuffs
; a 21h into the stack, and calls Intr!!! So you can use ALTMSDOS instead!
;   If you need a proc far, increase the increments in the two equates by 2.
;
; Pascal format: procedure AltIntr(intno: byte; var regs: registers); external;
;  You can have AltIntr always be used with this following code:
; procedure Intr(intno:byte; var regs: registers);
;   begin
;   AltIntr(intno, regs);
;   end;
;
;   You can always access the standard Intr by calling it
; with Dos.Intr(intno, regs); (provided you have a 'Uses Dos' statment!)
;
ALTINTR proc    near
        assume  cs:CODE, ds:DATA, es:NOTHING

regaddr equ     [bp + 4]
intno   equ     [bp + 8]

        push    bp
        mov     bp,sp
        push    ds                      ;save DS, because we screw it up
        mov     al,intno                ;get interrupt number to use
        mov     cs:intrpt,al            ;and modify our code
        lds     si,regaddr              ;point DS:SI to var regs
        mov     cs:save_ds,ds           ;save pointer for return
        mov     cs:save_si,si
        cld                             ;go forward
        lodsw                           ;load AX and hold it
        push    ax
        lodsw                           ;load BX
        mov     bx,ax
        lodsw                           ;load CX
        mov     cx,ax
        lodsw                           ;load DX
        mov     dx,ax
        lodsw                           ;load BP
        mov     bp,ax
        lodsw                           ;load SI and hold it
        push    ax
        lodsw                           ;load DI
        mov     di,ax
        lodsw                           ;load DS and hold it
        push    ax
        lodsw                           ;load ES
        mov     es,ax
        lodsw                           ;load Flags
        push    ax
        popf
        pop     ds                      ;get held up regs
        pop     si
        pop     ax
        db      0cdh                    ;Int opcode
intrpt  db      ?                       ;loaded with real interrupt when run
        pushf                           ;save flags and modified regs
        push    es
        push    di
        mov     es,cs:save_ds           ;get var regs pointer into ES:DI
        mov     di,cs:save_si
        cld                             ;go forward
        stosw                           ;save AX
        mov     ax,bx
        stosw                           ;save BX
        mov     ax,cx
        stosw                           ;save CX
        mov     ax,dx
        stosw                           ;save DX
        mov     ax,bp
        stosw                           ;save BP
        mov     ax,si
        stosw                           ;save SI
        pop     ax
        stosw                           ;save DI
        mov     ax,ds
        stosw                           ;save DS
        pop     ax
        stosw                           ;save ES
        pop     ax
        stosw                           ;save Flags
        pop     ds                      ;restore regs
        pop     bp
        ret     6

;local storage

save_ds dw      ?
save_si dw      ?

ALTINTR endp

; AltMsDos pulls back the stack a bit, pushes in a 21h, restores the stack,
; then calls AltIntr. If you need a proc far, uncomment the two lines
;
; Pascal format: AltMsDos(var regs: registers); external;
;  You can have AltIntr always be used with this following code:
; procedure MsDos(var regs: registers);
;   begin
;   AltMsDos(regs);
;   end;
;
;   You can always access the standard MsDos by calling it
; with Dos.MsDos(regs); (provided you have a 'Uses Dos' statment!)
ALTMSDOS        proc    near
        assume  cs:CODE, ds:DATA, es:NOTHING

        pop     si              ;back track a bit so we can stuff
        pop     dx              ;interrupt number in
        pop     cx
;       pop     bx              ;uncomment if proc far
        mov     al,21h          ;push interrupt number
        push    ax
;       push    bx              ;uncomment if proc far
        push    cx              ;restore other info
        push    dx
        push    si
        jmp     ALTINTR         ;do interrupt call

ALTMSDOS        endp

code    ends

data    segment byte
data    ends
        end