;====================================================================== ; 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