About me
Home
Resumé
CV
Pictures

Course Work
CSE300-FA98

Teaching
Portfolio
CSE240-SP99
CSE207-FA03

Misc
eHarmony
Documents

Tools

Hello World as a TSR file

Let me start this page by warning you that THIS CODE IS UNSTABLE

Why? Well, as we discussed in class, DOS interrupts (services in 21h) have a problem with reentrancy. This program does not check if we have a reentrancy problem or not. By running this program at the command prompt you should type the name of the program, press ESC a few times and then plan on rebooting your machine. If you never press ESC you can continue to run just fine as the keyboard handler will be transparent to every key except for ESC.

Take a look at the code, and I'll discuss some of what is happening below

TITLE hw_tsr.asm
;=======================================================================
; prints hello world to the screen every time the user presses ESC
;=======================================================================

_TEXT SEGMENT USE16 WORD PUBLIC 'CODE'
  ASSUME cs:_TEXT, ds:_TEXT
   Start:
      StartRes EQU $
      jmp    begin              ; jump to start of install code
                                ;  (i.e. jump over resident code)
      orig80hVector       LABEL DWORD
      orig80hOffset       DW ?
      orig80hSegment      DW ?

      orig09hVector       LABEL DWORD
      orig09hOffset       DW ?
      orig09hSegment      DW ?

      msg                 DB "Hello world :)", 0Ah, 0Dh, "$"


;=======================================================================
; newInt80h - resident code that will print the hello world message
;             to the screen in a DANGEROUS WAY - you really need
;             to verify that you are not going to reenter the int 21h
;             services.  However, to keep this example *very* simple
;             I have ommited these checks.
;=======================================================================
;   PreCond  -- Cannot be in an int 21h service
;   PostCond -- Hello world is printed to the screen, or the machine
;               crashes due to a reentrancy problem.
;=======================================================================
  PROC newInt80h FAR
      push   ax
      push   dx
      push   ds

      push   cs
      pop    ds
      mov    dx, OFFSET msg
      mov    ah, 09h
      int    21h

      pop    ds
      pop    dx
      pop    ax
      iret
  newInt80h ENDP

;=======================================================================
; newInt09h - new keyboard handler that will trap the ESC hotkey
;             and pass everything else on
;=======================================================================
;   PreCond  -- None
;   PostCond -- if ESC is pressed, int80 will be generated
;               otherwise the key is passed on to int09 as normal
;=======================================================================
  PROC newInt09h FAR
      cli
      push   ax                 ; save registers

      in     al, 60h            ; read PORT A
      cmp    al, 81h            ; see if the key released was ESC
      je     runOurHandler

      pop    ax                 ; restore stack and chain to 
      jmp    cs:orig09hVector   ;  the old interrupt handler

   runOurHandler:
      int    80h                ; print hello world message

      in     al, 61h            ; toggle bit 7 to ACK key event
      or     al, 80h
      out    61h, al
      and    al, 7Fh
      out    61h, al

      mov    al, 20h            ; ACK hardware (int controller)
      out    20h, al

      pop    ax                 ; restore registers and return
      iret
  newInt09h ENDP

      EndRes EQU $
  begin:
      mov    ax, _TEXT          ; set our ASSUMEd segements
      mov    ds, ax

      ;----------------------------------------------------------------
      ; Get Old Vectors
      ;----------------------------------------------------------------
      mov     al, 80h           ; vector to retrieve
      mov     ah, 35h           ; function: get interrupt vector
      int     21h
      mov     orig80hOffset, bx ; save the old seg:off of 80h vector
      mov     orig80hSegment, es

      mov     al, 09h           ; vector to retrieve
      mov     ah, 35h           ; function: get interrupt vector
      int     21h
      mov     orig09hOffset, bx ; save the old seg:off of 09h vector
      mov     orig09hSegment, es



      ;----------------------------------------------------------------
      ; Set New Vectors
      ;----------------------------------------------------------------
      mov     dx, OFFSET newInt80h
      mov     ax, SEG newInt80h
      mov     ds, ax            ; ds:dx points to our interrupt routine
      mov     al, 80h           ; interrupt function to get
      mov     ah, 25h           ; function: set interrupt vector
      int     21h

      mov     dx, OFFSET newInt09h
      mov     ax, SEG newInt09h
      mov     ds, ax            ; ds:dx points to our interrupt routine
      mov     al, 09h           ; interrupt function to get
      mov     ah, 25h           ; function: set interrupt vector
      int     21h


      ;----------------------------------------------------------------
      ; Exit Program but leave the resident code!!!
      ;----------------------------------------------------------------
      mov   dx, (100h+EndRes-StartRes+15)/16
      mov   ax, 3100h           ; function: TSR exit to DOS
      int   21h

_TEXT ENDS


_STACK SEGMENT USE16 STACK 'STACK'
      DW  100 dup(?)
_STACK ENDS

END Start

Download the above file here

First, it should be noted that there is no _DATA segment here. This is because we want to keep the data in memory and we need to know exactly where it is so that we can save it when the program terminates. Data that we need to keep in memory is called resident data and should be at the beginning of the text segment. Next in the segment should come the code that must remain resident. In this example, all of that code is interrupts, but other procedures could exist here as well. Following the resident portions of the program are the installion portions of the program. This should include the installer code and any messages or data that are necessary only when loading the program.

The basic layout of the file is as follows

_TEXT ....
 ASSUME ...
 Start:
     jmp    begin
   ; resident data
   ; resident code

 Begin:
   ; installation code
   ; installation data

_TEXT ENDS

So what happens? Well, your program runs starting at Begin:. This should load your TSR functions in memory, tell DOS that you have N bytes to save in memory, and exit back to the command prompt. Perhaps, a check for previous installations will be included here, or the option to remove your TSR (if this can be done safely). After your program returns to DOS, there must be some way of activating your TSR. Common methods are using the timer interrupt (we'll be doing this for the multitasker) and using hot-keys (as we will do for this project).

When you press a key, you trigger interrupt 09h. In the code above, if the key is matched to the ESC key, then our main function is called. To make life more complicated, I have implemented this function as another interrupt to show that interrupts may be nested, typically you want your overall time spent in an interrupt to be as brief as possible. The important point is that when you press a key (or release a key) you generate interrupt 09h. If the key pressed/released does not match ESC the old interrupt handler is chained to and we can pretend that our code never ran. Of course, if the ESC key is pressed, then we do not want to pass that keystroke to the system and we need to handle all of the hardware ACKs.

Our int 09h code generates another interrupt (int 80h) which prints our message to the screen using interrupt 21h services. This is typically a bad idea, unless you ensure that you will NOT have a reentrancy problem. Again, I warn you, run this code, but plan on rebooting your machine.

A few things to note, in addition to having no _DATA segment, we only have a _STACK segment during our installation. As a result, it is important to make sure that we do not overrun the user's stack when our TSR is running. (Now you should start to see the reason why we had to declare a stack in our early programs even though we did not use the stack). If your program requires more stack space, then you will need to declare your own local stack and modify the SS register to point to your stack. This can be a huge headache to debug if you are not careful in your design phase. For this project, it should not be necessary. Create buffers if you need them (you can have large data areas within the _TEXT segment) but avoid pushing or popping too much stuff on the stack.

We will be talking about this in class - but hopefully you will be able to bring questions to ask. Please read the chapter in your book regarding TSR programming. It does an excellent job of covering the basics of TSRs.

Last Modified: January 26, 1999 - Barry E. Mapen