vit$oft freeware home page
;* Skeleton of TSR self-loading to upper memory. *;
;* Public Domain by vit$oft *;
; This commented resident program written in Netwide Assembler ver. 0.98
; can be used as skeleton for beginners in DOS TSR programming.
; Save this text as TSRUP.ASM and convert it to executable form with
; NASMW TSRUP.ASM -f bin -o TSRUP.COM
; The target file can be run in DOS or in Windows NT/9x console.
; Terminate and Stay Resident (TSR) are 16bit programs which provide services
; for other DOS programs. This is similar to Device Drivers which are
; installed to memory during DOS boot. Unlike devices, properly written TSR
; programs can be installed ad hoc and easily removed from memory when they
; are no longer needed.
; To understand how TSRs are installed into memory, we must be familiar with
; the concept of memory allocation in DOS. MS-DOS quantitizes memory into
; 16 bytes long chunks called 'paragraphs'. Each block of memory is prefixed
; with one additional paragraph, Memory Control Block (MCB), which holds
; information about its block and link to the next MCB. When a process
; requests a memory block allocation, DOS must rearrange the chain of memory
; blocks, reserve continuous block of requested size and mark it as occupied
; by the process in question. When user launches a COM program from command
; line, the command interpreter (typically COMMAND.COM) allocates two memory
; 1.one small for the copy of master environment strings
; 2.the second for the Program Segment Prefix (PSP) and adjacent program body.
; All available conventional memory is given to the executed program.
; DOS will deallocate (=mark as free) both memory blocks when the program
; terminates normally. If TSR program wants DOS not to deallocate all its
; memory upon termination, it has to terminate with call to special DOS
; function 31h and tell DOS how many paragraphs of terminating program's
; memory should be shrinked and left allocated. Such block decreases the
; amount of available memory for other programs. We can inspect the largest
; executable program size with the MEM command.
; Conventional memory is critical resource in DOS. All devices and TSR
; programs should always be optimized to minimize the amount of resident
; memory. This example TSR shifts the resident code at installation and
; overwrites FCB and DTA portion of PSP which would otherwise remain unused.
; With this technique it occupies only 128 bytes.
; Let's start with some macro definitions.
%MACRO PROC 1 ; PROC and ENDP just encapsulate procedures.
%1: ; Declare the name of the procedure as a label.
%PUSH %1 ; Push the name space context.
%MACRO ENDP 1 ; Both PROC and ENDP can be omitted
%IFCTX %1 ; but they help to guard the procedure nesting,
%POP ; similary to %MACRO and %ENDMACRO.
%ERROR Unmatched PROC/ENDP %1
%MACRO DosFn 1-2 ; Invoke INT 21h with AH=%1 BX=%2.
%IF %1 < 256
%IF %0 = 2 ; Second parameter loads to BX only if present.
%MACRO Write 1+ ; Write literal string to StdOut.
%%String: DB %1 ; Allocate string behing the .text section.
%IFNDEF Write# ; Include Write#Proc only at 1st macro invocation.
JMP SHORT Write#Endp
Write#Proc: ; CX bytes of string DX^
DosFn 40h,1 ; write to handle StdOut=1.
%MACRO AbortIf 2+ ; %1=condition %2=error message
J%-1 %%Continue ; Continue if condition %1 not met
Write %2 ; otherwise write error message %2+
DosFn 4C08h ; and terminate main program with errorlevel 8.
ORG 100h ; COM program begins at offset 256, CS=DS=ES=SS are set to PSP.
JMP SHORT Main
ShiftedResidentOrg EQU 5Ch ; At this offset begins discardable part of PSP.
ResidentOrg: EQU $ ; Will be shifted backward on installation time.
Shift EQU (ResidentOrg-ShiftedResidentOrg)
PROC NewInt08 ; New handler for interrupt which does the actual TSR's job.
; The far jump at its end chains the handler to previously installed Int08
; handlers (if any) and, finally, to the original BIOS routine which is
; responsible for 8259 controller satisfaction and IRET. Interrupt 08 is
; invoked by hardware timer every 55 ms. When this happens, only CS:IP are
; defined. Each access to variables should be related to CS: rather than DS:.
; As this takes additional prefix byte, it may be more convenient to load DS
; from CS if number of such references exceeds four.
; The actual purpose of this example TSR is to keep NumLock ON. This is
; achieved by setting NumLock status bit periodically. You can easy find out
; if it is working when you try to type something on numeric keypad and
; change NumLock status. The NumLock indicator will keep pace with its status
; bit in DOS or in Windows 9x (but not in NT).
; Not much useful but this is only a skeleton for your more sofisticated TSR.
MOV DS,AX ; Keyboard status will be referenced at segment 0.
OR BYTE [417h],20h ; Set NumLock status bit.
POP DS ; All registers must be preserved before control returns
POP AX ; to the original interrupt handling routine OldInt08.
JMP 0:0 ; Immediate segment:offset in the JMP instruction body
OldInt08 EQU $-4 ; will be refered later as OldInt08 and replaced with
ENDP NewInt08 ; the current vector taken from interrupt table.
Identificator DB 'TSRUP' ; Unique name among other TSR programs.
TsrStatus DB 0 ; This modifies Identificator to distinguish
%DEFINE Passive BYTE 0 ; between different states of resident part.
%DEFINE Active BYTE 1
SizeOfIdentificator EQU $-Identificator
SizeOfResident EQU $-Shift
ResidentParagraphs EQU (SizeOfResident+15) >> 4
ShiftedNewInt08 EQU NewInt08-Shift
ShiftedOldInt08 EQU OldInt08-Shift
ShiftedIdentificator EQU Identificator-Shift
ShiftedTsrStatus EQU TsrStatus-Shift
PROC Main ; The main program controls (un)installation of resident part
; into memory and writes diagnostic messages to the Standard Output.
Write 'TSRUP skeleton',13,10 ; Introduce self, put your (c) here.
CALL Init ; Parse command line and then shift the resident part.
CMP AL,'u' ; Uninstall requested?
AbortIf Z, 'was not installed yet.',13,10
AbortIf C, 'cannot uninstall - another TSR was hooked later.',13,10
Write 'was uninstalled.',13,10
DosFn 4C00h ; Terminate the main program.
.30: CMP AL,'j' ; Installation to conventional memory requested?
.50: AbortIf NZ, 'is installed already.',13,10
.60: Write 'was installed in conventional memory.',13,10
.70: CMP AL,'i' ; Installation to upper memory requested?
AbortIf NE,'Options: /InstallToUpper /J:InstallConv /Uninstall',13,10
JNZ .50 ; Jump to abortion if already installed.
CALL FindUMB ; Try to allocate upper memory block.
JC .60 ; Jump if upper memory is not available.
Write 'was installed in upper memory.',13,10
PROC Init ; Parses command line arguments and shifts resident to overlap PSP.
MOV SI,128+1 ; Cmd line arguments start at this offset in PSP.
CMP AL,'-' ; Parameter may be prefixed with '/' or '-'.
CMP AL,13 ; Cmd line is terminated with this character.
JNE .10 ; Continue search for argument prefix.
JE .40 ; End of cmd line reached, AL=13.
.30: LODSB ; Fetch the letter following argument prefix.
OR AL,'A'^'a' ; Convert to lower case
.40: PUSH AX ; and temporary save.
; Cmd line is parsed, PSP can be overwritten.
; Current program should deallocate its environment by itself now
; for the case of resident installation.
MOV ES,[2Ch] ; Get segment address of environment.
DosFn 49h ; and deallocate the environment.
DosFn 3508h ; Get Interrupt Vector 08.
MOV WORD [OldInt08+0],BX ; IntVec must be saved in the resident body.
MOV WORD [OldInt08+2],ES
PUSH DS ; Now make the shift in order to spare occupied memory.
POP ES ; Restore ES to current segment.
CLD ; The resident portion will be shifted backward to overlap
REP MOVSB ; the tail of PSP which is not needed any longer.
RET ; Returns with requested function in AL='i','j','u' or 13.
PROC InstallConventional ; Terminate with ResidentParagraphs left in memory.
MOV [ShiftedTsrStatus],Active ; Mark resident as Active.
DosFn 2508h ; Set Interrupt Vector 08 to the new handler.
MOV DX,ResidentParagraphs ; How much memory must be left allocated.
DosFn 3100h ; Terminate and keep TSR in conventional memory.
; With version 5, MS-DOS brought out the concept of using memory above
; conventional limit 9000:FFFF, so called UPPER memory. In Microsoft DOS this
; is achieved with two device drivers: HIMEM and EMM386. Resident programs
; can be loaded to upper memory using internal command LOADHIGH but there's
; a rub. Upper memory is often fragmented and LOADHIGH must find continuous
; block of upper memory large enough for the whole program, not only for the
; portion which remains resident.
; This example uses better solution: the program is launched in conventional
; memory, it allocates only a little slot in upper memory and moves the resi-
; dental part by itself. Thanks to this technique it can benefit from upper
; memory in cases where LOADHIGH or MEMORYMAKER are useless.
PROC InstallUpper ; Install to upper memory block at segment ES which has
SUB SI,SI ; already been allocated with FindUMB.
MOV CX,SizeOfResident ; Head of PSP and adjacent resident
REP MOVSB ; block will be copied to upper memory
MOV [ES:ShiftedTsrStatus],Active ; and then marked Active.
POP DS ; DS temporary set to upper memory segment.
DosFn 2508h ; Set Interrupt Vector 08 to upper memory.
MOV BX,AX ; Save upper memory segment to BX.
DEC AX ; Its Memory Control Block is 1 paragraph bellow.
MOV ES,AX ; MCB segment of our upper memory block.
MOV DI,8 ; At this offset in MCB should be the name of block owner.
MOV SI,Identificator ; The name displayed with MEM /C.
%IF SizeOfIdentificator < 8
MOV CL,8 ; Maximum size of block owner name.
REP MOVSB ; Name of resident is now set in its MCB.
DosFn 50h ; Set current PSP to BX=segment of UMB.
POP ES ; ES points to running PSP in conventional memory.
DosFn 49h ; Now deallocate the whole program pointed with ES.
; The following instructions actually run in deallocated memory
; but luckily it is not overwritten yet by DOS.
DosFn 3100h ; Terminate and keep DX paragraphs resident.
PROC Uninstall ; Restore hooked vectors and deallocate resident memory
PUSH DS ; pointed to by segment ES (either upper or conventional).
MOV DS,AX ; Interrupt table is at segment 0.
MOV AX,ES ; Segment of previously installed instance
CMP AX,[WORD (4*08h+2)] ; should be pointed to by interrupt table.
STC ; Return with carry flag if interrupt vector
JNE .90 ; does not point to segment of deallocated resident.
PUSH DS ; Otherwise unhook the interrupt.
DosFn 2508h ; Restore Interrupt Vector 08 from DS:DX.
MOV [ES:ShiftedTsrStatus],Passive ; Next inst.check won't find it.
DosFn 49h ; Deallocate old TSR memory at segment ES.
; Good TSR program will not allow the user to install it twice or more. This
; requires some kind of installation check. I prefer detection by name, which
; takes less resident memory (only the size of identificating name). The
; identificator must be modified when set to active resident state,
; otherwise the installation check would be fooled with old invalid copies
; of identificator which may accidentally occur in deallocated memory
; or in cache buffers.
PROC InstallationCheck ; Searches for active previously installed instance.
; Current Identificator will be temporary disguised as Active and
; the presence of previously installed resident with the same Identificator
; will be inspected at all possible segments.
MOV DX,ShiftedIdentificator ; Offset of string in installed instance.
SUB AX,AX ; This register will hold inspected segment address.
.40: DEC AX ; Check all segments from FFFF downto 0000.
JZ .90 ; Jump if bottom reached and still not found.
MOV DI,DX ; Offset in searched segment.
MOV SI,DX ; Offset in current segment.
MOV CX,BX ; Length of Identificator including TsrStatus.
MOV CX,DS ; Match was found but
CMP CX,AX ; if at current segment,
JE .40 ; ignore this and continue searching bellow current instance.
.90: MOV [ShiftedTsrStatus],Passive; At end of search return to passive state.
RET ; ZF=1 if not installed yet else ZF=0 and ES=segment found.
; Upper memory is not available in MS-DOS without two memory devices which
; must be loaded in CONFIG.SYS:
; DEVICE=EMM386.EXE RAM and/or NOEMS
; Even with this devices the upper memory is not automaticly allocated by DOS
; function 48h. DOS must be told to add upper memory to the chain of free
; memory blocks. We must also temporary change its default allocation
; strategy which prefers conventional memory.
PROC FindUMB ; This procedure will try to allocate ResidentParagraphs
; of upper memory and put its segment to ES. CF=error.
DosFn 5800h ; Get alloc strategy (0=first fit, 1=best fit, 2=last fit).
JC .90 ; Fails on DOS version < 3.
MOV SI,AX ; Save the current strategy to SI to be restored later.
DosFn 5802h ; Get UMB Link Status (0=convent.only 1=including upper).
JC .90 ; Fails on DOS version < 5.
MOV DX,AX ; UMB Link Status will be saved to DL.
DosFn 5803h,1 ; Set UMB Link Status to 1=include upper memory.
DosFn 5801h,41h ; Set Allocation Strategy to 41h=upper best fit.
DosFn 48h,ResidentParagraphs ; Try to allocate upper memory block.
PUSHF ; Save result of allocation (CF=error).
MOV ES,AX ; Segment address of UMB if allocation succeeded.
SUB BX,BX ; In any case we should restore previous state.
MOV BL,DL ; UMB Link Status was kept in DL.
DosFn 5803h; Restore UMB Link Status.
DosFn 5801h,SI ; Restore Allocation Strategy from SI.
POPF ; Return with ES=allocated segment (valid only if CF=0).
; This skeleton TSRUP in source and executable form is available
; at vit$oft freeware pages.
; You will need freeware Netwide Assembler to compile it.
; For further information see also x86 Assembler WebRing,
; Assembly Programming Journal or Interrupt List.
; See also the new version for EuroAssembler.