VOGONS


First post, by llm

User metadata
Rank Member
Rank
Member

now it runs and does not crash!!!

FIXES:

1. changed wlink.exe format dos ... to wlink.exe format dos com ...
2. removed mov ax,@data and mov ds,ax because ds=cs in tiny model
3. fixed the uinstall code to use the real tsr data
4. fixes missing use of es in uninstall tsr_info acccess

can anyone see were i made a error or anyone got an idea whats wrong with my code - i try to overwrite the int 21h/ah=9 functions to replace some chars on print

its not finished (no code in the ISR except calling the old handler) but already helps my dosbox DOS behaves strange after install - can't even run the Dosbox debugger after install

build with OpenWatcom V2 wasm+wlink (or JWasm, Uasm, Masm)

set WATCOM=f:\projects\fun\dos_games_rev\tools\open-watcom-2_0-c-win-x64
set WATCOM_BIN=%WATCOM%\binnt64
set INCLUDE=%WATCOM%\h
set PATH=%WATCOM_BIN%;%PATH%

wasm.exe tsr.asm
wlink.exe format dos com file tsr.obj name tsr.com

the TSR code

far_ptr_t struc
offs dw 0
segm dw 0
far_ptr_t ends

tsr_info_t struc
signature dw 0CAFEh
old_interrupt_21h far_ptr_t<0,0>
load_segment dw 0
tsr_info_t ends

.model tiny

.code

org 100h ; this is a COM program

start:
jmp setup ; jump to TSR installation

int21h_handler proc far
cmp ah,9 ; is it print
jne run_original

; copy string with $
; search for 'l' and replace with 'X'
; ds=cs,dx=offset buffer
; ...

run_original:
jmp far ptr cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h

; local buffer
buffer db 16 dup(0)
int21h_handler endp

; tsr data
tsr_info TSR_info_t<>

resident_end: ; also contains PSP at start due to "org 100h"

TSR_INFO_OFFSET = offset tsr_info - offset int21h_handler;

current_load_seg dw 0

setup:
mov bx,ds
mov current_load_seg,bx

; ds == cs

mov ax,3521h ; get int 21h
int 21h

mov di,bx
add di,TSR_INFO_OFFSET

mov ax,es:[di+TSR_info_t.signature]
cmp ax,0CAFEh
je uinstall
Show last 41 lines
  
; install

; save current int 21h handler
mov [tsr_info.old_interrupt_21h.offs],bx
mov [tsr_info.old_interrupt_21h.segm],es

; save load-segment for uninstall
mov ax,current_load_seg
mov [tsr_info.load_segment],ax

mov ax,2521h ; set interrupt vector
mov dx,offset int21h_handler
int 21h

mov ax,3100h
mov dx,offset resident_end
mov cl,4
shr dx,cl
inc dx
int 21h

uinstall:
push ds
mov ax,es:[di+TSR_info_t.old_interrupt_21h.segm]
mov ds,ax
mov dx,es:[di+TSR_info_t.old_interrupt_21h.offs]
mov ax,2521h
int 21h
pop ds

mov ax,es:[di+TSR_info_t.load_segment]
mov es,ax
mov ah,49h
int 21h

mov ax,4c00h
int 21h

END start

last time if was something simple like an overwritten ah,ah register - im doing way too much C++ and my brain isn't very good in pattern machting of stupid mistakes in 16bit assembler 🙁

Last edited by llm on 2025-03-30, 14:36. Edited 6 times in total.

Reply 1 of 11, by DaveDDS

User metadata
Rank Oldbie
Rank
Oldbie

I don't know if this will be useful, but my own Micro-C compiler has a TSR
function built into it's 8086/PC library.

In this last year, as part a "Retirement project", I've released the source
code to Micro-C which includes full source code to the library (which contains
TSR.ASM)

You can access my 40+ years worth of source code project (incl. the above)
via my web site.

Here is the (trimmed for brevity) description of the TSR function within the
Micro-C library documentation:

PROTOTYPE:
tsr(&func, int hotkey, int alloc)

ARGUMENTS:
func - Address of function to execute
hotkey - POP-UP activation hotkeys
alloc - Memory allocation

RETURN VALUE:
Function normally never returns, but if it does, an
operating system error code is passed back.

DESCRIPTION:
The TSR function terminates the MICRO-C program, but leaves it
resident in memory. When the specified "HOT KEYS" are detected on the
IBM PC keyboard, the context of whatever program is running is saved,
and the specified MICRO-C function is called. When that function
returns, the interrupted program is resumed.

When activated this way, THE "func" FUNCTION MUST NOT TERMINATE
WITH "exit" or one of its related functions (abort etc.). THE ONLY
WAY IT MAY TERMINATE IS TO "return" NORMALLY.
The meaning of "hotkey" is defined in the "tsr.h" header file.

The "alloc" parameter specifies how much extra memory is to be
retained in the TSR image for use by the MICRO-C stack and heap
memory allocation functions. The "func" function(s) must insure that
the total amount of memory used by the stack and calls to "malloc"
does not exceed this value.

NOTE: "malloc" will fail if the heap grows to within 1K of the
stack pointer.

All memory used by code, global variables, string space, and
"malloc" calls prior to the use of "tsr" is automatically retained,
and should not be included in the "alloc" value.

TSR programs which perform screen I/O should take care to save and
restore the screen contents when popping up and down.


EXAMPLES:
tsr(&popup_func, ALT+L_SHIFT, 1024);

Dave ::: https://dunfield.themindfactory.com ::: "Daves Old Computers"->Personal

Reply 2 of 11, by llm

User metadata
Rank Member
Rank
Member

i need assembler - the result should become a example for a reverse engineering test, the behavior of the TSR should be just visible but minimal

Reply 3 of 11, by DaveDDS

User metadata
Rank Oldbie
Rank
Oldbie

The TSR.ASM mentioned in the afore mention library source code is assembler - and I mentioned it mainly in case
an example of a working general purpose TSR function would be helpful, not so much expecting you to use it "as is".

Dave ::: https://dunfield.themindfactory.com ::: "Daves Old Computers"->Personal

Reply 4 of 11, by Deunan

User metadata
Rank Oldbie
Rank
Oldbie
llm wrote on 2025-03-29, 13:02:
[…]
Show full quote
run_original:
jmp cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h

I have not used WASM a lot, back in the day I would use MASM, then TASM, and now NASM - so I'm not 100% sure about this, but shouldn't that jump be explicitly far? As in, "jmp far cs:[...]". The way you have it now it might be coded as near jump, except with memory argument.

Reply 5 of 11, by llm

User metadata
Rank Member
Rank
Member
Deunan wrote on 2025-03-29, 20:23:
llm wrote on 2025-03-29, 13:02:
[…]
Show full quote
run_original:
jmp cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h

I have not used WASM a lot, back in the day I would use MASM, then TASM, and now NASM - so I'm not 100% sure about this, but shouldn't that jump be explicitly far? As in, "jmp far cs:[...]". The way you have it now it might be coded as near jump, except with memory argument.

thanks i fixed that - also my linker statement was not fully correct

Reply 6 of 11, by llm

User metadata
Rank Member
Rank
Member

i fixes serveral bugs and now install/uintstall seems to work - updated first post accordingly

Last edited by llm on 2025-03-30, 09:05. Edited 1 time in total.

Reply 7 of 11, by llm

User metadata
Rank Member
Rank
Member

so the TSR install/uninstall and just forwarding to the old int 21h handler is working flawless (according to my dosbox debugger checks) - right before calling the old int 21h my ds:dx buffer is correct but the call prints nothing and just hangs until i press a key

im not sure if calling the old int 21h handler is correct that way - and if i needs sti/cli somewere - and if the push/pop savings are good that way
the string char replacing itself works

my current code

far_ptr_t struc
offs dw 0
segm dw 0
far_ptr_t ends

tsr_info_t struc
signature dw 0CAFEh
old_interrupt_21h far_ptr_t<0,0>
load_segment dw 0
tsr_info_t ends

.model tiny

.code

org 100h ; this is a COM program

start:
jmp setup ; jump to TSR installation

int21h_handler proc far
cmp ah,9 ; is it print
jne run_original

; ---------------------------------
; copy string with $
; search for 'l' and replace with 'X'

push es
push ds
push si
push di
push ax
push cx
push dx

; print parameter = ds:dx = string+$
mov si,dx ; source is ds:si

; es:di is dest
mov ax,cs
mov es,ax
mov di,offset buffer

mov cx, 16-1 ; max 15
copy_loop:
lodsb
cmp al, '$'
je done
cmp al, 'l'
jne not_l
mov al, 'X'
not_l:
stosb
loop copy_loop
done:
mov al,'$'
stosb

mov ax,cs
Show last 90 lines
  mov ds,ax
mov dx,offset buffer

pushf
call far ptr cs:[tsr_info.old_interrupt_21h]

pop cx
pop dx
pop ax
pop di
pop si
pop ds
pop es

iret

; ----------------------------------

run_original:
jmp far ptr cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h

; local buffer
buffer db 16 dup('#')
int21h_handler endp

; tsr data
tsr_info TSR_info_t<>

resident_end: ; also contains PSP at start due to "org 100h"

TSR_INFO_OFFSET = offset tsr_info - offset int21h_handler;

current_load_seg dw 0

setup:
mov bx,ds
mov current_load_seg,bx

; ds == cs

mov ax,3521h ; get int 21h
int 21h

mov di,bx
add di,TSR_INFO_OFFSET

mov ax,es:[di+TSR_info_t.signature]
cmp ax,0CAFEh
je uinstall

; install

; save current int 21h handler
mov [tsr_info.old_interrupt_21h.offs],bx
mov [tsr_info.old_interrupt_21h.segm],es

; save load-segment for uninstall
mov ax,current_load_seg
mov [tsr_info.load_segment],ax

mov ax,2521h ; set interrupt vector
mov dx,offset int21h_handler
int 21h

mov ax,3100h
mov dx,offset resident_end
mov cl,4
shr dx,cl
inc dx
int 21h

uinstall:
push ds
mov ax,es:[di+TSR_info_t.old_interrupt_21h.segm]
mov ds,ax
mov dx,es:[di+TSR_info_t.old_interrupt_21h.offs]
mov ax,2521h
int 21h
pop ds

mov ax,es:[di+TSR_info_t.load_segment]
mov es,ax
mov ah,49h
int 21h

mov ax,4c00h
int 21h

END start

Reply 8 of 11, by Deunan

User metadata
Rank Oldbie
Rank
Oldbie
llm wrote on 2025-03-30, 08:21:
[…]
Show full quote
  push cx
push dx
...
pop cx
pop dx

First, correct the push/pop sequence. Second you should consider jumping on AH=9 to your code, rather than jumping twice on AH!=9 to old address. It's not a huge issue, the 21h handler is not that fast, but I like doing things properly for performance reasons - like when you have to write your own high frequency interrupt (HSYNC or doing DAC with PC speaker). Every cycle counts then.

Lastly consider moving the data structures between jmp setup and the handler code. Why? This way you can have the signature very close to the new handler and only need to adjust the pointer a few bytes at most to check if the program is already resident. Simply much easier to calculate and code it.

EDIT: BTW you need CLD somewhere before you start using string instructions. It might just work like this since most programs will have these instructions set to forward/increment mode but it's not a good idea to assume that.
And there's another bug, you call the old interrupt to print out your string but you've destroyed AX by moving the segment registers around. Either set AH=9 or change the push/pop sequence to restore AX first, before you make that jump.

Reply 9 of 11, by llm

User metadata
Rank Member
Rank
Member

First, correct the push/pop sequence.

stupid me - thx

Second you should consider jumping on AH=9 to your code, rather than jumping twice on AH!=9 to old address.

i don't jump twice/or getting called twice - the call after string-replace only calls the original handler
can you explain differently?

Lastly consider moving the data structures between jmp setup and the handler code. Why?
This way you can have the signature very close to the new handler and only need to adjust the pointer a few bytes at most to check if the program is already resident. Simply much easier to calculate and code it.

because at setup time im inside the com executable segment and on uninstall time i need to use the interrupt ptr to find the information for uninstall, isn't the calculation already very easy?
can you explain what you would change in detail?

you need CLD somewhere before you start using string instructions.

facepalm - yepp thanks again

you call the old interrupt to print out your string but you've destroyed AX by moving the segment registers around. Either set AH=9 or change the push/pop sequence to restore AX first, before you make that jump.

that was my "i need a keypress" behavior - works now

so it seems to work now fully - my int 21h/9h string get replaced at 'l' with 'X' no crashes so far - but im still interested in getting more feedback

current code

far_ptr_t struc
offs dw 0
segm dw 0
far_ptr_t ends

tsr_info_t struc
signature dw 0CAFEh
old_interrupt_21h far_ptr_t<0,0>
load_segment dw 0
tsr_info_t ends

.model tiny

.code

org 100h ; this is a COM program

start:
jmp setup ; jump to TSR installation

int21h_handler proc far
cmp ah,9 ; is it print
jne run_original

; ---------------------------------
; copy string with $
; search for 'l' and replace with 'X'

push ds
push es
push di
push si
push ax
push cx
push dx

; print parameter = ds:dx = string+$
mov si,dx ; source is ds:si

; es:di is dest
mov ax,cs
mov es,ax
mov di,offset buffer

cld
mov cx, 16-1 ; max 15
copy_loop:
lodsb
cmp al, '$'
je done
cmp al, 'l'
jne not_l
mov al, 'X'
not_l:
stosb
loop copy_loop
done:
mov al,'$'
stosb

Show last 92 lines
  mov ax,cs
mov ds,ax
mov dx,offset buffer
mov ah,9

pushf
call far ptr cs:[tsr_info.old_interrupt_21h]

pop dx
pop cx
pop ax
pop si
pop di
pop es
pop ds

iret

; ----------------------------------

run_original:
jmp far ptr cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h

; local buffer
buffer db 16 dup('#')
int21h_handler endp

; tsr data
tsr_info TSR_info_t<>

resident_end: ; also contains PSP at start due to "org 100h"

TSR_INFO_OFFSET = offset tsr_info - offset int21h_handler;

current_load_seg dw 0

setup:
mov bx,ds
mov current_load_seg,bx

; ds == cs

mov ax,3521h ; get int 21h
int 21h

mov di,bx
add di,TSR_INFO_OFFSET

mov ax,es:[di+TSR_info_t.signature]
cmp ax,0CAFEh
je uinstall

; install

; save current int 21h handler
mov [tsr_info.old_interrupt_21h.offs],bx
mov [tsr_info.old_interrupt_21h.segm],es

; save load-segment for uninstall
mov ax,current_load_seg
mov [tsr_info.load_segment],ax

mov ax,2521h ; set interrupt vector
mov dx,offset int21h_handler
int 21h

mov ax,3100h
mov dx,offset resident_end
mov cl,4
shr dx,cl
inc dx
int 21h

uinstall:
push ds
mov ax,es:[di+TSR_info_t.old_interrupt_21h.segm]
mov ds,ax
mov dx,es:[di+TSR_info_t.old_interrupt_21h.offs]
mov ax,2521h
int 21h
pop ds

mov ax,es:[di+TSR_info_t.load_segment]
mov es,ax
mov ah,49h
int 21h

mov ax,4c00h
int 21h

END start

Reply 10 of 11, by Deunan

User metadata
Rank Oldbie
Rank
Oldbie
llm wrote on 2025-03-30, 14:31:

i don't jump twice/or getting called twice - the call after string-replace only calls the original handler
can you explain differently?

What I mean is AH=9 is just one of many functions of int 21h. Consider this approach:

  cmp ah,9 ; is it print
je run_print
jmp far ptr cs:[tsr_info.old_interrupt_21h] ; jump to original int 21h
run_print:
...

This way the default path, every function except 09h, is a conditional jump not taken. No prefetch flush, faster execution. Again this is not going to do much here because it's an OS call and is slow on its own. I just figured I'd point this out in case you want to write something more time critical in future.

llm wrote on 2025-03-30, 14:31:

because at setup time im inside the com executable segment and on uninstall time i need to use the interrupt ptr to find the information for uninstall, isn't the calculation already very easy?
can you explain what you would change in detail?

You calculate this using assembler pointer arithmetic at compile time: TSR_INFO_OFFSET = offset tsr_info - offset int21h_handler;
If you ever create another version of this program, with some code changes, this value will also change. So now your newly compiled TSR can't uninstall the previous version(s) because it will be looking for the signature in the wrong place. Obviously it's your personal program so you probably don't care - but for public release it's not good enough. People might be keeping different versions because of bugs, or memory usage, or whatever, and be annoyed they have to keep track of what version was installed to uninstall it.

Consider this solution:

signature dw 0CAFEh
int21h_handler proc far
...

Now when you fetch the interrupt pointer you can easily, and with always the same constant, check for the presence of your code:

  mov ax,3521h              ; get int 21h
int 21h

cmp word ptr es:[bx-2],0CAFEh
je uinstall

Now these are just suggestions, you don't have to do it this way if you'd rather stick to a known working solution. BTW shouldn't this topic be in Software rather than Hardware? That being said I don't visit other sections of the forums as often as I visit here...

Reply 11 of 11, by llm

User metadata
Rank Member
Rank
Member

thanks for the detailed descriptions - sensefull but i keep it as is - its just an example - nothing that should reach version 2.0 😀