;======================================================================
; LEVEL 1.00 Copyright (c) 1992, Robert L. Hummel
; PC Magazine Assembly Language Lab Notes
;
; LEVEL is a TSR management tool that provides a reliable way to
; remove TSRs from memory.
;----------------------------------------------------------------------
CSEG SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
ORG 100H ;COM file format
ENTPT: JMP MAIN
;======================================================================
; Program data area.
;----------------------------------------------------------------------
CR EQU 13 ;ASCII carriage return
LF EQU 10 ;ASCII line feed
BLANK EQU 32 ;ASCII blank (space)
JMPFAR EQU 0EAH ;Opcode for JMP FAR
;----------------------------------------------------------------------
; This copyright notice remains resident along with the data structure.
;----------------------------------------------------------------------
DB "LEVEL 1.00 ",254," Copyright (c) 1992,"
DB " Robert L. Hummel",26
;----------------------------------------------------------------------
; This data structure controls this layer:
;
; _NEXTLEVEL SEG:OFF of next data area
; _PREVLEVEL SEG:OFF of previous data area
; _NPSPS (Word) Number of PSP entries in the _PSP_TABLE
; _PSP_TABLE Array of up to MAX_PSPS words holding resident PSPs
; _INTVECTS Array of DWORD vectors for interrupts 00h-7Fh
; _OVERFLOW (Word) Non-zero if table has overflowed
;
; The "O_" entries specify the offset of the data elements to the
; beginning of the structure. These are later used to address elements
; when examining other structures.
;----------------------------------------------------------------------
DATA_STRUCTURE LABEL BYTE
O_NEXTLEVEL EQU $-DATA_STRUCTURE
_NEXTLEVEL DD -1
O_PREVLEVEL EQU $-DATA_STRUCTURE
_PREVLEVEL DD -1
O_NPSPS EQU $-DATA_STRUCTURE
_NPSPS DW 0
MAX_PSPS EQU 16
_PSP_TABLE DW MAX_PSPS DUP (0)
O_INTVECTS EQU $-DATA_STRUCTURE
_INTVECTS DD 128 DUP (0)
_OVERFLOW DW 0
;======================================================================
; INT_21 (ISR)
;
; When resident, this routine intercepts calls to Int 21h. If the INT
; request is for the Keep (Int 21h, AH=31h) function and if this is the
; top level, the PSP of the terminating process is recorded.
;----------------------------------------------------------------------
INT_21 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
CMP AH,31H ;Keep function?
JNE I21_EXIT
CMP WORD PTR CS:[_NEXTLEVEL],-1 ;Our level?
JNE I21_EXIT
;----------------------------------------------------------------------
; Save the PSP of the terminating process in our table.
;----------------------------------------------------------------------
STI ;Allow interrupts
PUSH AX ;Save used registers
PUSH BX
MOV AH,51H ;Get active PSP in BX
INT 21H ; thru DOS
MOV AX,BX ;Save PSP
MOV BX,CS:[_NPSPS] ;Get index
CMP BX,MAX_PSPS ;Too many saved?
JB I21_1A
;----------------------------------------------------------------------
; The table is full, there are too many TSRs at this level. (This is
; very unlikely.) Place a non-zero value in the trouble flag so that
; we'll signal an error when removing. (We know BX is non-zero.)
;----------------------------------------------------------------------
MOV CS:[_OVERFLOW],BX ;Set flag non-zero
JMP SHORT I21_1B
;----------------------------------------------------------------------
; List is not full. Add this PSP.
;----------------------------------------------------------------------
I21_1A:
ADD BX,BX ;Index to words
MOV CS:[_PSP_TABLE][BX],AX ;Save PSP in table
INC CS:[_NPSPS] ;Advance count
I21_1B:
POP BX ;Restore used registers
POP AX
CLI ;Disable interrupts
;----------------------------------------------------------------------
; Exit by jumping to the original Int 21h vector as stored in this
; level's _INTVECTS table.
;----------------------------------------------------------------------
I21_EXIT:
JMP DWORD PTR CS:[_INTVECTS][21H*4] ;Far jump
INT_21 ENDP
;======================================================================
; INT_27 (ISR)
;
; When resident, this routine intercepts calls to Int 27h (terminate
; and stay resident). If this is the top level, the PSP of the
; terminating process is recorded.
;----------------------------------------------------------------------
INT_27 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
CMP WORD PTR CS:[_NEXTLEVEL],-1 ;Our level?
JNE I27_EXIT
;----------------------------------------------------------------------
; Save the PSP of the terminating process in our table.
;----------------------------------------------------------------------
STI ;Allow interrupts
PUSH AX ;Save used registers
PUSH BX
MOV AH,51H ;Get active PSP in BX
INT 21H ; thru DOS
MOV AX,BX ;Save PSP
MOV BX,CS:[_NPSPS] ;Get index
CMP BX,MAX_PSPS ;Too many saved?
JB I27_1A
;----------------------------------------------------------------------
; The table is full, there are too many TSRs at this level. (This is
; very unlikely.) Place a non-zero value in the trouble flag so that
; we'll signal an error when removing. (We know BX is non-zero.)
;----------------------------------------------------------------------
MOV CS:[_OVERFLOW],BX ;Set flag non-zero
JMP SHORT I27_1B
;----------------------------------------------------------------------
; Add this PSP to our list.
;----------------------------------------------------------------------
I27_1A:
ADD BX,BX ;Create word index
MOV CS:[_PSP_TABLE][BX],AX ;Save PSP in table
INC CS:[_NPSPS] ;Advance count
I27_1B:
POP BX ;Restore used registers
POP AX
CLI ;Disable interrupts
;----------------------------------------------------------------------
; Exit by jumping to the original Int 21h vector as stored in this
; level's _INTVECTS table.
;----------------------------------------------------------------------
I27_EXIT:
JMP DWORD PTR CS:[_INTVECTS][27H*4] ;Far jump
INT_27 ENDP
;----------------------------------------------------------------------
; This is the cutoff point for all but the first installation.
;----------------------------------------------------------------------
CUTOFF EQU $
;======================================================================
; INT_66 (ISR)
;
; This routine chains into a "user-defined" interrupt to signal its
; presence to other copies. Only the lowest installed level hooks this
; interrupt.
;----------------------------------------------------------------------
; If, on entry, the following conditions are met:
; AX = "RH"
; BX = 0
;
; Then, on exit, the registers will be set as follows:
; AX="HR"
; ES:BX -> data structure of first copy (defined above)
;----------------------------------------------------------------------
INT_66 PROC FAR
ASSUME CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
CMP AX,"RH" ;Our special ID?
JE I66_1
I66_EXIT:
I66_OPCODE DB JMPFAR ;FAR jump to
OLD66 DD -1 ; saved vector
;----------------------------------------------------------------------
; Code to check BX is here to allow more functions to be supported.
;----------------------------------------------------------------------
I66_1:
OR BX,BX ;Function 0 = get ptr
JNZ I66_EXIT
MOV BX,OFFSET DATA_STRUCTURE ;Return pointer
PUSH CS ;Point ES
POP ES ; to this segment
ASSUME ES:NOTHING
XCHG AH,AL ;Reverse signature
IRET ;Return to caller
INT_66 ENDP
;----------------------------------------------------------------------
; This is the cutoff point for the first installation only.
;----------------------------------------------------------------------
FIRST_CUTOFF EQU $
;======================================================================
; MAIN procedure.
;----------------------------------------------------------------------
COPYRIGHT$ DB LF,"LEVEL 1.00 ",254," Copyright (c) 1992,"
DB " Robert L. Hummel",CR,LF
DB "PC Magazine Assembly Language Lab Notes",CR,LF
DB "Usage: LEVEL [IN | OUT]",CR,LF,LF
DB "Current Status:",CR,LF,"$"
LEVEL$ DB "-> LEVEL # "
LEVEL_NUM DB "00: "
NUM_TSRS DB "00 TSRs",CR,LF,"$"
NOLEVELS$ DB "-> No LEVELs In Memory",CR,LF,"$"
IN_OKAY$ DB LF,"New Level Successfully Installed",CR,LF,"$"
OUT_OKAY$ DB LF,"Level Successfully Removed",CR,LF,"$"
OVERFLOW$ DB "Too many TSRs were installed at this level",CR
DB LF,"Some memory will not be released",CR,LF,"$"
COPTION DB 0 ;Save cmd line option
UMB_LINK DB -1 ;Holds UMB link status
;----------------------------------------------------------------------
MAIN PROC NEAR
ASSUME CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG
CLD ;String moves forward
;----------------------------------------------------------------------
; Display the program title.
;----------------------------------------------------------------------
MOV AH,9 ;Display string fn
MOV DX,OFFSET COPYRIGHT$ ; located here
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Search the command tail for options.
;----------------------------------------------------------------------
MOV SI,81H ;Point to cmd tail
SUB CH,CH ;Set CX to
MOV CL,[SI-1] ; # chars in tail
JCXZ M_1D
M_1A:
LODSB ;Get char
CMP AL,BLANK ;Skip blanks
JNE M_1B
LOOP M_1A
JMP SHORT M_1D
M_1B:
;----------------------------------------------------------------------
; We found a non-blank character in the command tail. If not IN or OUT,
; just ignore it and print the usage message.
;----------------------------------------------------------------------
OR AL,20H ;Make lower case
CMP AL,"i" ;Was it "IN"?
JE M_1C
CMP AL,"o" ;Was it "OUT"?
JNE M_1D
M_1C:
MOV [COPTION],AL ;Save the option
M_1D:
;----------------------------------------------------------------------
; Get the current vector for Int 66h in ES:BX.
;----------------------------------------------------------------------
MOV AX,3566H ;Get vector for Int 66h
INT 21H ; thru DOS
ASSUME ES:NOTHING
;----------------------------------------------------------------------
; If ES=BX=0, there are no previous levels in memory.
;----------------------------------------------------------------------
MOV WORD PTR [OLD66][0],BX ;Save vector in case
MOV WORD PTR [OLD66][2],ES ; we need it later
MOV AX,ES ;Get the segment
OR AX,BX ; and the offset
JNZ M_3
;----------------------------------------------------------------------
; There's no previous int 66h handler to transfer control to, so just
; patch our Int 66 handler so that it does an IRET.
;----------------------------------------------------------------------
MOV BYTE PTR [I66_OPCODE],0CFH ;Opcode for IRET
;----------------------------------------------------------------------
; There are no levels. Display a message saying so.
;----------------------------------------------------------------------
ASSUME ES:NOTHING
M_2:
MOV AH,9 ;Display string
MOV DX,OFFSET NOLEVELS$ ;Say no levels
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Because there are no previous levels, the only permissible option is
; "IN". If "OUT" or nothing was specified, just terminate.
;----------------------------------------------------------------------
CMP [COPTION],"i" ;Was it "IN"?
JE M_5A
;----------------------------------------------------------------------
; Terminate this iteration of the program.
;----------------------------------------------------------------------
ASSUME ES:NOTHING
M_EXIT:
MOV AH,4CH ;Terminate program
INT 21H ; thru DOS
;----------------------------------------------------------------------
; The 66h vector is non-zero, so we'll assume it's a valid vector.
; Request a pointer to the Level 1 data structure.
;----------------------------------------------------------------------
M_3:
MOV AX,"RH" ;Pass this code
SUB BX,BX ;0=return address
INT 66H ; to previous levels
ASSUME ES:NOTHING
CMP AX,"HR" ;Should return this
JNE M_2
;----------------------------------------------------------------------
; Trace the structures and display the info.
;----------------------------------------------------------------------
MOV BP,"00" ;ASCII digit mask
SUB CX,CX ;CX=current level
M_4A:
INC CX ;Move to next level
MOV AX,CX ; and put in AX
AAM ;Separate digits
OR AX,BP ;Make ASCII
XCHG AH,AL ;Put in string order
MOV WORD PTR [LEVEL_NUM],AX ;Save as level #
MOV AX,ES:[BX][O_NPSPS] ;Get # TSRs this level
DEC AX ;Remove ourselves
AAM ;Separate digits
OR AX,BP ;Make ASCII
XCHG AH,AL ;Put in string order
MOV WORD PTR [NUM_TSRS],AX ;Save in message
MOV AH,9 ;Display string
MOV DX,OFFSET LEVEL$ ;Describe level
INT 21H ; thru DOS
CMP WORD PTR ES:[BX][O_NEXTLEVEL],-1 ;Last one?
JE M_4B
LES BX,DWORD PTR ES:[BX][O_NEXTLEVEL] ;Get next
ASSUME ES:NOTHING
JMP M_4A
M_4B:
;----------------------------------------------------------------------
; ES:BX is left pointing to the last structure in memory. Save this
; backpointer in the current structure.
;----------------------------------------------------------------------
MOV WORD PTR [_PREVLEVEL][0],BX ;Offset and
MOV WORD PTR [_PREVLEVEL][2],ES ; segment
;----------------------------------------------------------------------
; See if this is an IN or OUT remove request.
;----------------------------------------------------------------------
M_4C:
CMP [COPTION],"o" ;Level OUT?
JE M_7A
CMP [COPTION],"i" ;Level IN?
JNE M_EXIT
;----------------------------------------------------------------------
; Insert a new level. If there were previous levels, change the last
; pointer to point to us. Otherwise, hook INT 66h.
;----------------------------------------------------------------------
ASSUME ES:NOTHING
M_5A:
CMP WORD PTR [LEVEL_NUM],"00" ;No previous?
JE M_5B
MOV WORD PTR ES:[BX][O_NEXTLEVEL][0],OFFSET DATA_STRUCTURE
MOV WORD PTR ES:[BX][O_NEXTLEVEL][2],CS
M_5B:
;----------------------------------------------------------------------
; Capture the interrupt vectors for INT 0 - INT 7F.
;----------------------------------------------------------------------
SUB SI,SI ;Point DS:SI to 0:0
MOV DS,SI
ASSUME DS:NOTHING
MOV DI,OFFSET _INTVECTS ;Point ES:DI to
PUSH CS ; vector save area
POP ES
ASSUME ES:CSEG
MOV CX,128*2 ;Copy this many words
REP MOVSW
PUSH CS ;Restore segment
POP DS
ASSUME DS:CSEG
;----------------------------------------------------------------------
; Leave the INT_66 proc in memory only for the first install.
;----------------------------------------------------------------------
MOV CX,(OFFSET CUTOFF - CSEG + 15) / 16 ;Not 1st
CMP WORD PTR [LEVEL_NUM],"00" ;No previous?
JNE M_6
MOV CX,(OFFSET FIRST_CUTOFF - CSEG + 15) / 16
;----------------------------------------------------------------------
; Hook in our interrupt service routines.
;----------------------------------------------------------------------
MOV AX,2566H ;Set Int 66h vector
MOV DX,OFFSET INT_66 ; to point here
INT 21H ; thru DOS
M_6:
MOV AX,2521H ;Set Int 21h vector
MOV DX,OFFSET INT_21 ; to point here
INT 21H ; thru DOS
MOV AX,2527H ;Set Int 27h vector
MOV DX,OFFSET INT_27 ; to point here
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Now terminate and stay resident.
;----------------------------------------------------------------------
MOV AX,DS:[2CH] ;Get environment seg
MOV ES,AX
ASSUME ES:NOTHING
MOV AH,49H ;Release seg in ES
INT 21H ; thru DOS
MOV AH,9 ;Display string
MOV DX,OFFSET IN_OKAY$ ;Say it worked
INT 21H ; thru DOS
MOV AH,31H ;End as TSR
MOV DX,CX ;Save DX paragraphs
INT 21H ; thru DOS
;----------------------------------------------------------------------
; We come here to remove the top level. If, however, the PSP table
; overflowed, we can't free all the memory. Disable the TSRs by
; restoring the IVT and free as much memory as possible.
;----------------------------------------------------------------------
ASSUME DS:CSEG, ES:NOTHING
M_7A:
CMP ES:[_OVERFLOW],0 ;Top layer overflow?
JE M_7B
MOV AH,9 ;Display string
MOV DX,OFFSET OVERFLOW$ ;Relate problem
INT 21H ; thru DOS
;----------------------------------------------------------------------
; Check our LEVEL message to determine if the level we're removing is
; the only one in memory.
;----------------------------------------------------------------------
M_7B:
CMP WORD PTR [LEVEL_NUM],"10" ;Reversed 01
JE M_7C
;----------------------------------------------------------------------
; There is at least one level below the one we're removing. Point DS:SI
; to the next lower copy's data structure and erase the forward link.
;----------------------------------------------------------------------
LDS SI,DWORD PTR ES:[BX][O_PREVLEVEL] ;Get link
ASSUME DS:NOTHING
MOV AX,-1 ;Negate
MOV WORD PTR DS:[SI][O_NEXTLEVEL][0],AX ; forward
MOV WORD PTR DS:[SI][O_NEXTLEVEL][2],AX ; link
M_7C:
;----------------------------------------------------------------------
; Restore the interrupt vector table. This will disable any of the TSRs
; we're about to remove.
;----------------------------------------------------------------------
LEA SI,[BX][O_INTVECTS] ;Point to vector table
PUSH ES ; in segment of
POP DS ; top level
ASSUME DS:NOTHING ;DS -> top level
SUB DI,DI ;Point ES:DI to 0:0
MOV ES,DI ; destination
ASSUME ES:NOTHING ;ES -> 0000
MOV CX,128*2 ;# of words to move
CLI ;No interrupts
REP MOVSW ;Restore vectors
STI ;Interrupts on
;----------------------------------------------------------------------
; Get the DOS version. If ver 5 or later, save the current UMB state,
; then link them.
;----------------------------------------------------------------------
MOV AH,30H ;Get DOS version in AX
INT 21H ; thru DOS
CMP AL,5 ;DOS 5 or later
JB M_8
MOV AX,5802H ;Get current UMB link
INT 21H ; thru DOS
JC M_8
MOV CS:[UMB_LINK],AL ;Save it
MOV AX,5803H ;Set UMBs to be
MOV BX,1 ; linked in chain
INT 21H ; thru DOS
M_8:
;----------------------------------------------------------------------
; Deallocate memory belonging to the PSPs recorded in the top level.
;----------------------------------------------------------------------
MOV AH,52H ;Get IVARS pointer
INT 21H ; thru DOS
ASSUME ES:NOTHING
MOV ES,ES:[BX-2] ;Get first MCB
ASSUME ES:NOTHING
;----------------------------------------------------------------------
; Point DS:SI to the table of PSPs in the top level. Compare each MCB
; segment to the entries in the table. If any match, release them.
; (Humming `Born Free' as you do so is optional.)
;----------------------------------------------------------------------
M_9A:
MOV SI,OFFSET _PSP_TABLE ;DS:SI -> PSP table
MOV CX,DS:[_NPSPS] ;Number PSPS in table
MOV BX,ES:[1] ;Get owner of block
M_9B:
LODSW ;Get first PSP
CMP AX,BX ;Does it match?
JNE M_9C
;----------------------------------------------------------------------
; The owner of this block is in our list. To release the memory, we
; have to point ES to the segment -- not to the MCB.
;----------------------------------------------------------------------
PUSH ES ;Save MCB segment
MOV BX,ES ;Change MCB seg
INC BX ; to block seg
MOV ES,BX ; and reload
ASSUME ES:NOTHING
MOV AH,49H ;Release seg in ES
INT 21H ; thru DOS
POP ES ;Restore MCB
ASSUME ES:NOTHING
JMP SHORT M_9D
;----------------------------------------------------------------------
; Try the next entry in the PSP table.
;----------------------------------------------------------------------
M_9C:
LOOP M_9B
;----------------------------------------------------------------------
; We either found a match and removed it or went through the entire
; list without a match. Move to the next MCB.
;----------------------------------------------------------------------
M_9D:
MOV BX,ES ;MCB address
INC BX ;+1=segment address
ADD BX,ES:[3] ;+block length
CMP BYTE PTR ES:[0],"Z" ;This block the last?
MOV ES,BX ;(Load it meanwhile)
ASSUME ES:NOTHING
JNE M_9A
;----------------------------------------------------------------------
; Restore the UMB link to its previous state.
;----------------------------------------------------------------------
MOV BL,CS:[UMB_LINK] ;Original link state
CMP BL,-1 ;Was it recorded?
JE M_10
SUB BH,BH ;Link in BX
MOV AX,5803H ;Set UBM link
INT 21H ; thru DOS
M_10:
;----------------------------------------------------------------------
; All memory has been released. Exit the program.
;----------------------------------------------------------------------
PUSH CS ;Address data
POP DS ; in this segment
ASSUME DS:CSEG
MOV AH,9 ;Display string
MOV DX,OFFSET OUT_OKAY$ ;Say removal is done
INT 21H ; thru DOS
JMP M_EXIT
MAIN ENDP
CSEG ENDS
END ENTPT