[email protected] (Mats Petersson) wrote:

>I am currently working on a little TSR utility that intercepts the keyboard
>interrupt INT 09, and I need the possibility to save and load a file from
>within it using DOS functions 3ch, 3dh, 3eh, 3fh and 40h.
>However, it seems calling INT 21h from the TSR makes the computer hang
>sooner or later. I have tried every step I can think of to try to avoid it
>in some way, but to no avail.
>Isn't it possible to call INT 21h from a TSR? If not, please explain why!
>Calling most of the BIOS interrupts works OK as it seems, so I am very
>curious about the difference in calling INT 21h...

>If it IS possible, how do you make it work without the computer hanging?

It is, there is a documented MS-DOS function that returns the address
of a flag in MS-DOS that is incremented on entry to a DOS function and
decremented on exit, so you can check if it is safe to call DOS
functions by verifying that the flag is zero.  The function is

       Int 21h
               On Entry
                       ah = 34h

               On Exit
                       es:bx = Address of InDOS flag

This alone will not help because you will now only be able to call DOS
functions some of the time when you need to.  You should also hook Int
28h, the MS-DOS idle interrupt.  If DOS is idle in its polling loop it
is in an Int 21h but can be re-entered, however the InDOS flag is
non-zero.  If the InDOS flag is non-zero but an Int 28h is executed by
DOS it is safe to call DOS functions.  When you detect the condition
that activates your TSR you should first check the InDOS flag, if it
is zero just go ahead and do what you need to.  If the InDOS flag is
non-zero set a flag in your own code.  In your Int 28h handler check
the flag in your code, if it is set do what you need to.  Don't forget
to clear the flag.

One final point, the Int 28h is only guaranteed to be called in the
COMMAND.COM console input (ie. at a DOS prompt).  You would make your
TSR complete if you also trapped the user timer interrupt, int 1Ch.
The code for this should be very similar to your Int 28h handler.  For
example:


InDOS           dd      ?
OldInt28        dd      ?
OldInt1C        dd      ?
PendingFlag     db      0

Initialise:
       ; Your own initialisation

       ; Get the INDOS flag
       mov     ah, 34h
       int     21h
       mov     word ptr [InDOS], bx
       mov     word ptr [InDOS + 2], es

       ; Hook the Int 28h and Int 1Ch handlers
       mov     ax, 3528h
       int     21h
       mov     word ptr [OldInt28], bx
       mov     word ptr [OldInt28 + 2], es

       mov     ax, 351Ch
       int     21h
       mov     word ptr [OldInt1C], bx
       mov     word ptr [OldInt1C + 2], es

       mov     dx, cs
       mov     ds, dx
       mov     dx, offset NewInt28
       mov     ax, 2528h
       int     21h

       mov     dx, cs
       mov     ds, dx
       mov     dx, offset NewInt1C
       mov     ax, 251Ch
       int     21h

       ; Ensure the pending flag is zero
       mov     al, 0
       mov     byte ptr [PendingFlag], al

       ; Anything else, then TSR

Int09Entry:
       ; Check the key being pressed
       ; Is it my activate key
       ; No, call previous handler
       ; Yes, check INDOS flag
       les     di, InDOS
       cmp     word ptr es:[di], 0
       ; Set, we have a pending request
       jne     Pending

       ; Just do what we need to
       call    DoStuff
       jmp     Done09

       inc     byte ptr [PendingFlag]

Done09:
       iret

NewInt1C:
       ; Save registers, initialise anything

       ; Is there anything pending
       cmp     byte ptr [PendingFlag], 0       ; NB, assumes DS = CS
       ; No, we're done
       jz      Nothing1C

       ; We must check if DOS is not active
       les     di, [InDOS]     ; NB, assumes DS = CS
       cmp     word ptr es:[di], 0

       ; DOS is active, wait until later
       jnz     Nothing1C

       ; There is pending work and DOS is not active, do what we need to
       jmp     DoStuff

       ; Clear the pending flag
       dec     byte ptr [PendingFlag]  ; NB, assumes DS = CS

Nothing1C:
       ; Restore registers
       jmp     dword ptr [OldInt1C]

NewInt28:
       ; Save registers, initialise anything

       ; Is there anything pending
       cmp     byte ptr [PendingFlag], 0       ; NB, assumes DS = CS
       ; No, we're done
       jz      Nothing28

       ; Do what we need to
       jmp     DoStuff

       ; Clear the pending flag
       dec     byte ptr [PendingFlag]  ; NB, assumes DS = CS

Nothing28:
       ; Restore registers
       jmp     dword ptr [OldInt28]





� Area: ENG/SWE: E-Mail/FidoNetMail ������������������������������������������
 Msg#: 3093            Pvt Rec'd Sent               Date: 04 Nov 95  04:56
 From: Don Krull                                    Read: Yes    Replied: No
   To: Mats Peterson                                Mark:
 Subj: Calling INT21 from TSR
������������������������������������������������������������������������������
@FMPT 1
From: Don Krull <[email protected]>
To: Mats Petersson <[email protected]>

On 3 Nov 1995, Mats Petersson wrote:

> I am currently working on a little TSR utility that intercepts the keyboard
> interrupt INT 09, and I need the possibility to save and load a file from
> within it using DOS functions 3ch, 3dh, 3eh, 3fh and 40h.
> However, it seems calling INT 21h from the TSR makes the computer hang
> sooner or later. I have tried every step I can think of to try to avoid it
> in some way, but to no avail.
> Isn't it possible to call INT 21h from a TSR? If not, please explain why!
> Calling most of the BIOS interrupts works OK as it seems, so I am very
> curious about the difference in calling INT 21h...
> If it IS possible, how do you make it work without the computer hanging?
> I am just a happy amateur, so please don't laugh if my query seems kind of
> stupid :-)
> Please send any reply to my address below.
> All help appreciated,
> Mats Peterson         [email protected]

This question was just recently asked and answered. Fortunately for you,
I typed up a rather lengthy explanation of it. Here's your answer:
----------
To: [email protected]
Date: 19 Oct 1995, 21:30 CST
Newsgroup: comp.lang.asm.x86
Subject: Interrupts in Interrupt handlers

>While writing my own interrupt handler (actually it is an *extension*
>of the standard interrupt 13H), I encountered the problem that calling
>an INT (like INT 21H) within this TSR code causes a system crash.

Yep. It sure will. :-)

>I hope an experienced ASM programmer can give me some tips on
>how to use the INT xxh directive within an interrupt handler.

>Basically, my handler should "do some work" and then call the
>original interrupt handler. Is it possible to use INTs to
>"do some work" and then call the original handler?
>Any help would be greatly appreciated.
>Chris

Generally, ABSOLUTELY NOT! Here's why:

DOS is non-reentrant. What this means is that it keeps only ONE COPY
of the current values of the critical DOS variables (and maybe the
registers) in its private workspace. If another DOS INT call is made
before the first one has completed, those values will be wiped out
with the values for the latest call -- that call will complete just
fine; but upon return, the old values will be gone. In the worst case,
the old call could have been a disk write, and some of the variables
are still valid and DOS goes ahead and attempts the write -- either
writing garbage to the correct file, or worse, writing good/garbage
OVER SOMETHING ELSE! OUCH! Most of the time, DOS just locks up hard
(you must turn the PC off and wait 15 seconds to get it to come back).

Now, that having been said, there are *SOME* things which actually
CAN be done, and things which must NEVER be done.

1. You can always call ROM BIOS INTs (0 to 1Fh) at any time.
  So, to display a message, use the ROM BIOS TTY call, with
  your own little loop to print one char of the string at a time.

2. DOS sets up 3 stacks for its various work. One stack handles the
  DOS INT 21h functions 0 to 0Ch (the DOS 1.x char I/O functions),
  one handles the INT 21h functions above 0Ch (the fragile ones),
  and the last one handles the other DOS INTs (22h to 2Fh, actually
  up to 7Fh) -- this is primarily for the purpose of separating the
  Critical Error Handler (INT 24h) from the others.

3a.YOU MUST NEVER CALL *ANY* DOS INTs WHILE A DISK ERROR (INT 24h)
  IS BEING PROCESSED, PERIOD!
3b.When less serious DOS activities are in progress, basically you
  can call any DOS function which does NOT use the SAME stack as
  the one which the function that you are interrupting is using.
  Most of the time, this means you can only use the char I/O
  functions.

4. So how do we find out which DOS functions / stack(s) may be in use?
The DOS INT 21h function 34h returns a vector in ES:BX which points
to a word known as the InDOS flag and the ErrorMode flag. Of course,
YOU MUST CALL THIS FUNCTION **BEFORE** YOU NEED IT, and save the
DWORD in your own TSR or pop-up; because when you need it, you WON'T
BE ABLE TO CALL IT! :-) Note: this call is still Undocumented, and
Microsoft refuses to tell anyone about it, but info has escaped over
the years, and now everyone knows about it; both Microsoft and others
have used this vector so extensively that they are VERY unlikely to
change it. Still, they have reserved the option to do so...

5. General rules: When an application program is running in the fore-
ground (this may NOT mean Windows!?), then we are NOT "In DOS", so the
InDOS byte will zero, and we can call ANY functions safely (Microsoft
turns green here :-). However, when NO application is running (DOS
prompt on the screen), then DOS is running (COMMAND.COM = DOS !),
so we are "In DOS" and the usual rules above apply.

6. EXCEPTION: Even if InDOS = False (0), WE CANNOT INTERRUPT A DISK
ERROR IN PROGRESS (unsure about successful disk access, but DON'T).
This is what the ErrorMode flag is for. It is located at one byte
BELOW the InDOS flag byte, so YOU MUST DECREMENT BX before you save
it, so you'll be pointing at the correct word! When ErrorMode > 0,
a disk error or access is in progress, and you must NOT CALL ANY
DOS FUNCTIONS!

7. Now for the sweet part: When DOS is waiting for user input or
other detainment, DOS calls INT 28h -- the DOS IDLE function. If
your program hooks this one (don't forget to pass control down
the chain by JMP FAR -- don't be an asshole and IRET), then when-
ever DOS calls it, YOU can do some work. Since DOS *is* IDLE, you
can probably call just about anything. Now, Microsoft says that
this INT 28h is obsolete, like so many others; yet, DOS PRINT and
many other commands and applications use it, so ignore them and
use it. The so-called replacement is INT 2Fh, function 2860h? or
something ... The documentation is [I think intentionally] unclear
about how these 2 interact. My suggestion is to use only INT 28h,
because it's older and in more widespread use. (Both are documented.)

Supposedly, there's also another, similar hook, possibly built-into
COMMAND.COM, which can be used when *applications* are idle, but
that is rumor and undocumented. So there you have it. A carefully
written application or TSR can work beautifully in combination with
DOS, without resorting to GUIs or protected mode to work wonders.

It should be pointed out that how all of this behaves is very
likely highly dependent on the version of DOS that you're using --
3.x, 4.0, or 5-6.x, and on the DOS OEM. The goal is to make a
program which is well-behaved with a variety of different DOSes.

My sources for all of the above are _MS-DOS Programmer's Reference_
from Microsoft Press (the stuff on this topic is rather terse, but
thorough and fairly well written), and various files and magazine
articles that I have read over the years. The one which I remember
specifically is ASM Lang Mag -- as far as I know, only 4 electronic
"issues" were ever published (in 1989).

Don't worry TOO much about damaging things while you test your
programs. Usually, DOS just locks up when system data areas are
corrupted, and you have to reboot. Just follow the above guidelines
and you should be safe. Hope all of this helps!

Don

-!- uugate 0.40 (SunOS 4.1.3)
! Origin: Internet gateway [cindy] (2:200/427.1)