I am not sure now why I chose to allocate this much upper memory, perhaps my DOS 5 refused to terminate and left resident the incomplete PSP in those times (30 years ago).
I don't think so. You are relocating only the PSP contents up to 53h with your relocated resident code after that point already.
Nevertheless, tsrup.com /I works in DosBox and only shortened PSP + resident is finally allocated in the upper memory (9 paragraphs = 144 bytes).
Yes, after your reply I inspected the code more and you do actually terminate with function 31h in either case, so you will eventually shrink the block to 144 Bytes when running
InstallUpper as well. However, that means if only a very small UMB is available, you may fail to allocate it when it is between 144 and 288 Bytes in size, even though that would suffice to install the resident portion.
I was confused by your initial over-allocation because I assumed that you'd use function 4Ch to terminate when installing into a UMB. This is
what Ralf Brown's TSRs do (when installing into a UMB) and
what my TSRs do (always). In this technique, the MCB owner of the allocated (U)MCB is changed to avoid the block being freed by function 4Ch.
Terminating with function 4Ch also happens to close all file handles, which is useful if a user redirected like in
> NUL. If you use function 31h and do not close all file handles, then you will leak a Process Handle and also possibly a System File Table entry. The Process Handle Table is not that important, but the SFT impacts all processes on the system.
Another thing is you do not clear the environment block reference in the PSP to zero. I do that in TSRs that I terminate with function 31h.
And there are
some different opinions on how much of the PSP is re-usable. DOS keeps at least 60h Bytes of the PSP block when function 31h is passed a zero length. Ralf Brown's TSRs keep only the first 40h Bytes. I'd go with 60h to be safe.
If you are developping TSR for the real DOS, you can try calling AllocUMB with
CX=(OFFSET# TsrTop - Shift) / 16 ; Number of paragraphs for the resident code.
I might try it myself in future release, but I don't promise for sure. Memory allocation is not easy to debug in DosBox and TSR programs are not in my concern nowadays, sorry.
Anyway, thank you for checking.
I know that this is the calculation that you pass to function 31h. However, it is arguably not entirely right.
The
TsrTop label is aligned with an
ALIGN 16 directive in front of it. But this alignment may be no longer valid when you subtract the
Shift value, as that value is possibly
not aligned to a paragraph. If the shift equate modulo 16 happens to be 15 (as happens if
JMP Main: is a short jump), then the alignment may be wrong. I created
a small NASM example program to demonstrate how this can go wrong:
PSP.Reserved53: equ 53h
org 256
start:
jmp strict short $
TsrBottom:
times LEN int3
align 16
TsrTop:
Shift EQU TsrBottom - start + 256 - PSP.Reserved53
paras equ (TsrTop - start + 256 - Shift)/16
%assign shiftnumber Shift
%assign wantalloc LEN + PSP.Reserved53
%assign havealloc paras*16
%warning shift is shiftnumber, want wantalloc bytes, have havealloc bytes
The
- start + 256 is equivalent to an
OFFSET# keyword for €ASM I believe. It is a standard idiom that I often use to calculate a scalar number from a label in NASM, which allows to use the scalar in division etc.
If you run the program with the
LEN smacro set to 14, you get an error:
nasm test.asm -DLEN=14
test.asm:19: warning: shift is 175, want 97 bytes, have 96 bytes [-w+user]
175 is equal to 0AFh, showing that the shift equate is not paragraph aligned. The TSR top label is paragraph aligned, but with subtracting the shift equate, this alignment is no longer valid. When the align directive inserts exactly zero bytes, we want to allocate 53h + 14 = 97 Bytes, but we actually allocate 96 Bytes.
What you want to do is not align
TsrTop and instead do the alignment, once, when calculating the paragraphs value. That is you want
(OFFSET# TsrTop - Shift + 15) / 16. (This calculation still works with the align directive left where it is, but that's double-booking the alignment and may cause too much space to be allocated for alignment, >= 16 Bytes.)
I found later that your jump at the program entrypoint is actually a near jump because it jumps across both the resident part and the help message, so it cannot be a short jump. That means your alignment calculation is actually correct, as the shift equate modulo 16 is equal to zero. But I do think that it would be better to do it my way, because that hardens it against failure if one changes the offset into the PSP to something other than 53h, or changes the initial entrypoint jump to be shorter or longer than 3 Bytes, such as by moving the help message.
Finally, in
InstallUpper you have this comment:
; The following instructions actually run in deallocated memory but they are not overwritten yet by DOS.
The relocated PSP actually will also still refer to the Process Handle Table of the unrelocated PSP, so that's another reference to free memory. Obviously, I don't recommend doing that. You are in good company though, I believe that some DOS command.com also runs from freed memory at some point. And the original AMIS uninstall function design hints that it expected the TSR to free itself before returning, which in a naive implementation also runs the last return instruction from free memory. (To avoid this and still implement the entire uninstaller in the resident program, you
need some tricks like returning via a stack trampoline that frees the memory block.)