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 ; blocks: ; 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. %ENDMACRO %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. %ELSE %ERROR Unmatched PROC/ENDP %1 %ENDIF %ENDMACRO %MACRO DosFn 1-2 ; Invoke INT 21h with AH=%1 BX=%2. %IF %1 < 256 MOV AH,%1 %ELSE MOV AX,%1 %ENDIF %IF %0 = 2 ; Second parameter loads to BX only if present. MOV BX,%2 %ENDIF INT 21h %ENDMACRO %MACRO Write 1+ ; Write literal string to StdOut. [SECTION .data] %%String: DB %1 ; Allocate string behing the .text section. %%EndString: __SECT__ MOV DX,%%String MOV CX,%%EndString-%%String CALL Write#Proc %IFNDEF Write# ; Include Write#Proc only at 1st macro invocation. %DEFINE Write# JMP SHORT Write#Endp Write#Proc: ; CX bytes of string DX^ DosFn 40h,1 ; write to handle StdOut=1. RET Write#Endp: %ENDIF %ENDMACRO %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. %%Continue: %ENDMACRO ORG 100h ; COM program begins at offset 256, CS=DS=ES=SS are set to PSP. SECTION .text 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. PUSH AX PUSH DS SUB AX,AX 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? JNE .30 CALL InstallationCheck AbortIf Z, 'was not installed yet.',13,10 CALL Uninstall 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? JNE .70 CALL InstallationCheck .50: AbortIf NZ, 'is installed already.',13,10 .60: Write 'was installed in conventional memory.',13,10 JMP InstallConventional .70: CMP AL,'i' ; Installation to upper memory requested? AbortIf NE,'Options: /InstallToUpper /J:InstallConv /Uninstall',13,10 CALL InstallationCheck 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 JMP InstallUpper ENDP Main 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. CLD .10: LODSB CMP AL,'/' JE .30 CMP AL,'-' ; Parameter may be prefixed with '/' or '-'. JE .30 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. MOV SI,ResidentOrg MOV DI,ShiftedResidentOrg MOV CX,SizeOfResident CLD ; The resident portion will be shifted backward to overlap REP MOVSB ; the tail of PSP which is not needed any longer. POP AX RET ; Returns with requested function in AL='i','j','u' or 13. ENDP Init PROC InstallConventional ; Terminate with ResidentParagraphs left in memory. MOV [ShiftedTsrStatus],Active ; Mark resident as Active. MOV DX,ShiftedNewInt08 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. ENDP InstallConventional ; 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. SUB DI,DI CLD 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. PUSH DS PUSH ES POP DS ; DS temporary set to upper memory segment. MOV DX,ShiftedNewInt08 DosFn 2508h ; Set Interrupt Vector 08 to upper memory. POP DS MOV AX,ES 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,SizeOfIdentificator %ELSE MOV CL,8 ; Maximum size of block owner name. %ENDIF REP MOVSB ; Name of resident is now set in its MCB. DosFn 50h ; Set current PSP to BX=segment of UMB. PUSH DS 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. MOV DX,ResidentParagraphs DosFn 3100h ; Terminate and keep DX paragraphs resident. ENDP InstallUpper PROC Uninstall ; Restore hooked vectors and deallocate resident memory PUSH DS ; pointed to by segment ES (either upper or conventional). SUB AX,AX 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. POP DS STC ; Return with carry flag if interrupt vector JNE .90 ; does not point to segment of deallocated resident. PUSH DS ; Otherwise unhook the interrupt. LDS DX,[ES:ShiftedOldInt08] DosFn 2508h ; Restore Interrupt Vector 08 from DS:DX. POP DS MOV [ES:ShiftedTsrStatus],Passive ; Next inst.check won't find it. DosFn 49h ; Deallocate old TSR memory at segment ES. .90: RET ENDP Uninstall ; 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 [ShiftedTsrStatus],Active MOV DX,ShiftedIdentificator ; Offset of string in installed instance. MOV BX,SizeOfIdentificator CLD 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 ES,AX MOV DI,DX ; Offset in searched segment. MOV SI,DX ; Offset in current segment. MOV CX,BX ; Length of Identificator including TsrStatus. REPE CMPSB JNE .40 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. ENDP InstallationCheck ; Upper memory is not available in MS-DOS without two memory devices which ; must be loaded in CONFIG.SYS: ; DEVICE=HIMEM.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). .90: RET ENDP FindUMB ; 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. END