[Chapter Eighteen][Previous]
[Next] [Art of
Assembly][Randall Hyde]
Art of Assembly: Chapter Eighteen
- 18.5 - Installing a TSR
- 18.6 - Removing a TSR
- 18.7 - Other DOS Related Issues
18.5 Installing a TSR
Although we've already discussed how to make a program go resident,
there are a few aspects to installing a TSR that we need to address. First,
what happens if a user installs a TSR and then tries to install it a second
time without first removing the one that is already resident? Second, how
can we assign a TSR identification number that won't conflict with a TSR
that is already installed? This section will address these issues.
The first problem to address is an attempt to reinstall a TSR program. Although
one could imagine a type of TSR that allows multiple copies of itself in
memory at one time, such TSRs are few and far in-between. In most cases,
having multiple copies of a TSR in memory will, at best, waste memory and,
at worst, crash the system. Therefore, unless you are specifically written
a TSR that allows multiple copies of itself in memory at one time, you should
check to see if the TSR is installed before actually installing it. This
code is identical to the code an application would use to see if the TSR
is installed, the only difference is that the TSR should print a nasty message
and refuse to go TSR if it finds a copy of itself already installed in memory.
The following code does this:
mov cx, 0FFh
SearchLoop: mov ah, cl
push cx
mov al, 0
int 2Fh
pop cx
cmp al, 0
je TryNext
strcmpl
byte "Randy's INT "
byte "10h Extension",0
je AlreadyThere
TryNext: loop SearchLoop
jmp NotInstalled
AlreadyThere: print
byte "A copy of this TSR already exists in memory",cr,lf
byte "Aborting installation process.",cr,lf,0
ExitPgm
.
.
.
In the previous section, you saw how to write some code that would allow
an application to determine the TSR ID of a specific resident program. Now
we need to look at how to dynamically choose an identification number for
the TSR, one that does not conflict with any other TSRs. This is yet another
modification to the scanning loop. In fact, we can modify the code above
to do this for us. All we need to do is save away some ID value that does
not does not have an installed TSR. We need only add a few lines to the
above code to accomplish this:
mov FuncID, 0 ;Initialize FuncID to zero.
mov cx, 0FFh
SearchLoop: mov ah, cl
push cx
mov al, 0
int 2Fh
pop cx
cmp al, 0
je TryNext
strcmpl
byte "Randy's INT "
byte "10h Extension",0
je AlreadyThere
loop SearchLoop
jmp NotInstalled
; Note: presumably DS points at the resident data segment that contains
; the FuncID variable. Otherwise you must modify the following to
; point some segment register at the segment containing FuncID and
; use the appropriate segment override on FuncID.
TryNext: mov FuncID, cl ;Save possible function ID if this
loop SearchLoop ; identifier is not in use.
jmp NotInstalled
AlreadyThere: print
byte "A copy of this TSR already exists in memory",cr,lf
byte "Aborting installation process.",cr,lf,0
ExitPgm
NotInstalled: cmp FuncID, 0 ;If there are no available IDs, this
jne GoodID ; will still contain zero.
print
byte "There are too many TSRs already installed.",cr,lf
byte "Sorry, aborting installation process.",cr,lf,0
ExitPgm
GoodID:
If this code gets to label "GoodID
" then a previous
copy of the TSR is not present in memory and the FuncID
variable
contains an unused function identifier.
Of course, when you install your TSR in this manner, you must not forget
to patch your interrupt 2Fh handler into the int 2Fh chain. Also, you have
to write an interrupt 2Fh handler to process int 2Fh calls. The following
is a very simple multiplex interrupt handler for the code we've been developing:
FuncID byte 0 ;Should be in resident segment.
OldInt2F dword ? ; Ditto.
MyInt2F proc far
cmp ah, cs:FuncID ;Is this call for us?
je ItsUs
jmp cs:OldInt2F ;Chain to previous guy, if not.
; Now decode the function value in AL:
ItsUs: cmp al, 0 ;Verify presence call?
jne TryOtherFunc
mov al, 0FFh ;Return "present" value in AL.
lesi IDString ;Return pointer to string in es:di.
iret ;Return to caller.
IDString byte ""Randy's INT "
byte "10h Extension",0
; Down here, handle other multiplex requests.
; This code doesn't offer any, but here's where they would go.
; Just test the value in AL to determine which function to execute.
TryOtherFunc:
.
.
.
iret
MyInt2F endp
18.6 Removing a TSR
Removing a TSR is quite a bit more difficult that installing one. There
are three things the removal code must do in order to properly remove a
TSR from memory: first, it needs to stop any pending activities (e.g., the
TSR may have some flags set to start some activity at a future time); second
it needs to restore all interrupt vectors to their former values; third,
it needs to return all reserved memory back to DOS so other applications
can make use of it. The primary difficulty with these three activities is
that it is not always possible to properly restore the interrupt vectors.
If your TSR removal code simply restores the old interrupt vector values,
you may create a really big problem. What happens if the user runs some
other TSRs after running yours and they patch into the same interrupt vectors
as your TSR? This would produce interrupt chains that look something like
the following:
If you restore the interrupt vector with your original value, you will create
the following:
This effectively disables the TSRs that chain into your code. Worse yet,
this only disables the interrupts that those TSRs have in common with your
TSR. the other interrupts those TSRs patch into are still active. Who knows
how those interrupts will behave under such circumstances?
One solution is to simply print an error message informing the user that
they cannot remove this TSR until they remove all TSRs installed prior to
this one. This is a common problem with TSRs and most DOS users who install
and remove TSRs should be comfortable with the fact that they must remove
TSRs in the reverse order that they install them.
It would be tempting to suggest a new convention that TSRs should obey;
perhaps if the function number is 0FFh, a TSR should store the value in
es:bx
away in the interrupt vector specified in cl
.
This would allow a TSR that would like to remove itself to pass the address
of its original interrupt handler to the previous TSR in the chain. There
are only three problems with this approach: first, almost no TSRs in existence
currently support this feature, so it would be of little value; second,
some TSRs might use function 0FFh for something else, calling them with
this value, even if you knew their ID number, could create a problem; finally,
just because you've removed the TSR from the interrupt chain doesn't mean
you can (truly) free up the memory the TSR uses. DOS' memory management
scheme (the free pointer business) works like a stack. If there are other
TSRs installed above yours in memory, most applications wouldn't be able
to use the memory freed up by removing your TSR anyway.
Therefore, we'll also adopt the strategy of simply informing the user that
they cannot remove a TSR if there are others installed in shared interrupt
chains. Of course, that does bring up a good question, how can we determine
if there are other TSRs chained in to our interrupts? Well, this isn't so
hard. We know that the 80x86's interrupt vectors should still be pointing
at our routines if we're the last TSR run. So all we've got to do is compare
the patched interrupt vectors against the addresses of our interrupt service
routines. If they all match, then we can safely remove our TSR from memory.
If only one of them does not match, then we cannot remove the TSR from memory.
The following code sequence tests to see if it is okay to detach a TSR containing
ISRs for int 2fH and int 9:
; OkayToRmv- This routine returns the carry flag set if it is okay to
; remove the current TSR from memory. It checks the interrupt
; vectors for int 2F and int 9 to make sure they
; are still pointing at our local routines.
; This code assumes DS is pointing at the resident code's
; data segment.
OkayToRmv proc near
push es
mov ax, 0 ;Point ES at interrupt vector
mov es, ax ; table.
mov ax, word ptr OldInt2F
cmp ax, es:[2fh*4]
jne CantRemove
mov ax, word ptr OldInt2F+2
cmp ax, es:[2Fh*4 + 2]
jne CantRemove
mov ax, word ptr OldInt9
cmp ax, es:[9*4]
jne CantRemove
mov ax, word ptr OldInt9+2
cmp ax, es:[9*4 + 2]
jne CantRemove
; We can safely remove this TSR from memory.
stc
pop es
ret
' Someone else is in the way, we cannot remove this TSR.
CantRemove: clc
pop es
ret
OkayToRmv endp
Before the TSR attempts to remove itself, it should call a routine like
this one to see if removal is possible.
Of course, the fact that no other TSR has chained into the same interrupts
does not guarantee that there are not TSRs above yours in memory. However,
removing the TSR in that case will not crash the system. True, you may not
be able to reclaim the memory the TSR is using (at least until you remove
the other TSRs), but at least the removal will not create complications.
To remove the TSR from memory requires two DOS calls, one to free the memory
in use by the TSR and one to free the memory in use by the environment area
assigned to the TSR. To do this, you need to make the DOS deallocation call
. This call requires that you pass the segment address of the block to release
in the es
register. For the TSR program itself, you need to
pass the address of the TSR's PSP. This is one of the reasons a TSR needs
to save its PSP when it first installs itself. The other free call you must
make frees the space associated with the TSR's environment block. The address
of this block is at offset 2Ch in the PSP. So we should probably free it
first. The following calls handle the job of free the memory associated
with a TSR:
; Presumably, the PSP variable was initialized with the address of this
; program's PSP before the terminate and stay resident call.
mov es, PSP
mov es, es:[2Ch] ;Get address of environment block.
mov ah, 49h ;DOS deallocate block call.
int 21h
at allows multiple copies of itself in
memory at one time, such TSRs are few and far in-between. In most cases,
having multiple copies of a TSR in memory will, at best, waste memory and,
at worst, crash the system. Therefore, unless you are specifically written
a TSR that allows multiple copies of itself in memory at one time, you should
check to see if the TSR is installed before actually installing it. This
code is identical to the code an application would use to see if the TSR
is installed, the only difference is that the TSR should print a nasty message
and refuse to go TSR if it finds a copy of itself already installed in memory.
The following code does this:
mov cx, 0FFh
SearchLoop: mov ah, cl
push cx
mov al, 0
int 2Fh
pop cx
cmp al, 0
je TryNext
strcmpl
byte "Randy's INT "
byte "10h Extension",0
je AlreadyThere
TryNext: loop SearchLoop
jmp NotInstalled
AlreadyThere: print
byte "A copy of this TSR already exists in memory",cr,lf
byte "Aborting installation process.",cr,lf,0
ExitPgm
.
.
.
In the previous section, you saw how to write some code that would allow
an application to determine the TSR ID of a specific resident program. Now
we need to look at how to dynamically choose an identification number for
the TSR, one that does not conflict with any other TSRs. This is yet another
modification to the scanning loop. In fact, we can modify the code above
to do this for us. All we need to do is save away some ID value that does
not does not have an installed TSR. We need only add a few lines to the
above code to accomplish this:
mov FuncID, 0 ;Initialize FuncID to zero.
mov cx, 0FFh
SearchLoop: mov ah, cl
push cx
mov al, 0
int 2Fh
pop cx
cmp al, 0
je TryNext
strcmpl
byte "Randy's INT "
byte "10h Extension",0
je AlreadyThere
loop SearchLoop
jmp NotInstalled
; Note: presumably DS points at the resident data segment that contains
; the FuncID variable. Otherwise you must modify the following to
; point some segment register at the segment containing FuncID and
; use the appropriate segment override on FuncID.
TryNext: mov FuncID, cl ;Save possible function ID if this
loop SearchLoop ; identifier is not in use.
jmp NotInstalled
AlreadyThere: print
byte "A copy of this TSR already exists in memory",cr,lf
byte "Aborting installation process.",cr,lf,0
ExitPgm
NotInstalled: cmp FuncID, 0 ;If there are no available IDs, this
jne GoodID ; will still contain zero.
print
byte "There are too many TSRs already installed.",cr,lf
byte "Sorry, aborting installation process.",cr,lf,0
ExitPgm
GoodID:
If this code gets to label "GoodID
" then a previous
copy of the TSR is not present in memory and the FuncID
variable
contains an unused function identifier.
Of course, when you install your TSR in this manner, you must not forget
to patch your interrupt 2Fh handler into the int 2Fh chain. Also, you have
to write an interrupt 2Fh handler to process int 2Fh calls. The following
is a very simple multiplex interrupt handler for the code we've been developing:
FuncID byte 0 ;Should be in resident segment.
OldInt2F dword ? ; Ditto.
MyInt2F proc far
cmp ah, cs:FuncID ;Is this call for us?
je ItsUs
jmp cs:OldInt2F ;Chain to previous guy, if not.
; Now decode the function value in AL:
ItsUs: cmp al, 0 ;Verify presence call?
jne TryOtherFunc
mov al, 0FFh ;Return "present" value in AL.
lesi IDString ;Return pointer to string in es:di.
iret ;Return to caller.
IDString byte ""Randy's INT "
byte "10h Extension",0
; Down here, handle other multiplex requests.
; This code doesn't offer any, but here's where they would go.
; Just test the value in AL to determine which function to execute.
TryOtherFunc:
.
.
.
iret
MyInt2F endp
18.6 Removing a TSR
Removing a TSR is quite a bit more difficult that installing one. There
are three things the removal code must do in order to properly remove a
TSR from memory: first, it needs to stop any pending activities (e.g., the
TSR may have some flags set to start some activity at a future time); second
it needs to restore all interrupt vectors to their former values; third,
it needs to return all reserved memory back to DOS so other applications
can make use of it. The primary difficulty with these three activities is
that it is not always possible to properly restore the interrupt vectors.
If your TSR removal code simply restores the old interrupt vector values,
you may create a really big problem. What happens if the user runs some
other TSRs after running yours and they patch into the same interrupt vectors
as your TSR? This would produce interrupt chains that look something like
the following:
If you restore the interrupt vector with your original value, you will create
the following:
This effectively disables the TSRs that chain into your code. Worse yet,
this only disables the interrupts that those TSRs have in common with your
TSR. the other interrupts those TSRs patch into are still active. Who knows
how those interrupts will behave under such circumstances?
One solution is to simply print an error message informing the user that
they cannot remove this TSR until they remove all TSRs installed prior to
this one. This is a common problem with TSRs and most DOS users who install
and remove TSRs should be comfortable with the fact that they must remove
TSRs in the reverse order that they install them.
It would be tempting to suggest a new convention that TSRs should obey;
perhaps if the function number is 0FFh, a TSR should store the value in
es:bx
away in the interrupt vector specified in cl
.
This would allow a TSR that would like to remove itself to pass the address
of its original interrupt handler to the previous TSR in the chain. There
are only three problems with this approach: first, almost no TSRs in existence
currently support this feature, so it would be of little value; second,
some TSRs might use function 0FFh for something else, calling them with
this value, even if you knew their ID number, could create a problem; finally,
just because you've removed the TSR from the interrupt chain doesn't mean
you can (truly) free up the memory the TSR uses. DOS' memory management
scheme (the free pointer business) works like a stack. If there are other
TSRs installed above yours in memory, most applications wouldn't be able
to use the memory freed up by removing your TSR anyway.
Therefore, we'll also adopt the strategy of simply informing the user that
they cannot remove a TSR if there are others installed in shared interrupt
chains. Of course, that does bring up a good question, how can we determine
if there are other TSRs chained in to our interrupts? Well, this isn't so
hard. We know that the 80x86's interrupt vectors should still be pointing
at our routines if we're the last TSR run. So all we've got to do is compare
the patched interrupt vectors against the addresses of our interrupt service
routines. If they all match, then we can safely remove our TSR from memory.
If only one of them does not match, then we cannot remove the TSR from memory.
The following code sequence tests to see if it is okay to detach a TSR containing
ISRs for int 2fH and int 9:
; OkayToRmv- This routine returns the carry flag set if it is okay to
; remove the current TSR from memory. It checks the interrupt
; vectors for int 2F and int 9 to make sure they
; are still pointing at our local routines.
; This code assumes DS is pointing at the resident code's
; data segment.
OkayToRmv proc near
push es
mov ax, 0 ;Point ES at interrupt vector
mov es, ax ; table.
mov ax, word ptr OldInt2F
cmp ax, es:[2fh*4]