0

I need to write a program in Assembly for DOSBOX using MASM.

Task: Write a resident (TSR) program for DOS that changes the keyboard repeat rate every second in a cyclic manner, from the slowest to the fastest setting. The program must unload the initialization section upon completion.

Program requirements:

DOS + MASM environment.

Terminate the program using: INT 27h.

Interrupt handler installation method: Manually modify the Interrupt Vector Table using the MOV instruction.

Calling the previous interrupt handler: Use a far CALL with the flags register pushed onto the stack beforehand.

Position of the call to the previous handler: At the end of the new interrupt handler.

My code:

.MODEL small
.STACK 100h

.DATA
old_handler_offset DW 0
old_handler_segment DW 0

TickCounter     db 0          
CurrentRate     db 0          
RepeatRates     db 32 dup (?)

.CODE

InitRepeatRates proc
    mov cx, 32
    mov si, offset RepeatRates
    mov al, 1Fh            
FillLoop:
    mov [si], al
    dec al
    inc si
    loop FillLoop
    ret
InitRepeatRates endp

int_handler proc far
    push ax
    push bx
    push dx
    push si0
    push es
    push di

    
    inc cs:TickCounter
    cmp cs:TickCounter, 18
    jl skip_update

    
    mov cs:TickCounter, 0

    
    xor si, si
    mov al, cs:CurrentRate
    mov si, x
    mov al, cs:RepeatRates[si]

    
    inc cs:CurrentRate
    cmp cs:CurrentRate, 32
    jb no_reset
    mov cs:CurrentRate, 0
no_reset:

    
wait_input_ready:
    in al, 64h
    test al, 02h
    jnz wait_input_ready

    mov al, 0F3h       
    out 60h, al

wait_data_ready:
    in al, 64h
    test al, 02h
    jnz wait_data_ready

    
    mov al, cs:RepeatRates[si]
    out 60h, al

skip_update:
    
    pushf
    push word ptr cs:old_handler_segment
    push word ptr cs:old_handler_offset
    retf

    pop di
    pop es
    pop si
    pop dx
    pop bx
    pop ax
    iret
int_handler endp

start:
    mov ax, @DATA
    mov ds, ax
    
    call InitRepeatRates

    xor ax, ax
    mov es, ax

    mov ax, es:[1Ch*4]
    mov old_handler_offset, ax
    mov ax, es:[1Ch*4+2]
    mov old_handler_segment, ax

    cli
    mov word ptr es:[1Ch*4], offset int_handler
    mov ax, seg int_handler
    mov word ptr es:[1Ch*4+2], ax
    sti


    mov dx, offset start  
    int 27h               

END start

The program compiles successfully, but after I run and exit it, DOS stops responding to keyboard input. Please help, i don`t know what to do with it. Or offer general advice for coding TSR's in DOS?

1
  • What debugging have you done? Commented May 17 at 23:06

1 Answer 1

3

The conditions for using int 27h are not met

Your program is an .EXE executable for which CS does not point at the PSP, and that is a requirement for using int 27h. Beginning with DOS version 2, the approved way to terminate and stay resident uses function int 21h, AH=31h.
Even if you solved this detail, you would still be left with invalid accesses to your program's variables through the cs: segment override prefix as in your executable CS does not point at the .DATA section.

The best solution would be to use the .COM executable file format. All the segment registers start out equal to each other and are conveniently pointing at the PSP.

Calling the previous interrupt handler: Use a far CALL with the flags register pushed onto the stack beforehand.

pushf
push word ptr cs:old_handler_segment
push word ptr cs:old_handler_offset
retf

You don't actually call the previous handler! The retf instruction consumes the 2 pushes, so the old handler only 'sees' the pushed flags.
The 'old_handler' will return through the iret instruction but there will be no far return address waiting on the stack.

    ...
    pushf
    push cs
    push offset Back
    push word ptr cs:old_handler_segment
    push word ptr cs:old_handler_offset
    retf
Back:
    pop  di
    ...

or use a far CALL (like it was requested):

    ...
    pushf
    call  far ptr cs:old_handler_offset
    pop   di
    ...

As an alternative write:

    ...
    pushf
    mov   bx, offset old_handler_offset
    call  dword ptr [cs:bx]
    pop   di
    ...

The .COM file (I did not yet look at the keyboard stuff (*)):

ORG 256

jmp start

old_handler_offset DW 0
old_handler_segment DW 0

TickCounter     db 0          
CurrentRate     db 0          
RepeatRates     db 32 dup (0)


int_handler:
    push ax
    push bx
    push dx
    push si
    push es
    push di

    inc cs:TickCounter
    cmp cs:TickCounter, 18
    jl skip_update

    mov cs:TickCounter, 0
    
    xor si, si
    mov al, cs:CurrentRate
    mov si, x                       <<<<<<<<  ??????
    mov al, cs:RepeatRates[si]      <<<<<<<<  redundant!
    
    inc cs:CurrentRate
    cmp cs:CurrentRate, 32
    jb no_reset
    mov cs:CurrentRate, 0
no_reset:
    
wait_input_ready:
    in al, 64h
    test al, 02h
    jnz wait_input_ready

    mov al, 0F3h       
    out 60h, al

wait_data_ready:
    in al, 64h
    test al, 02h
    jnz wait_data_ready
    
    mov al, cs:RepeatRates[si]
    out 60h, al

skip_update:
    pushf
    call  FAR PTR cs:old_handler_offset

    pop di
    pop es
    pop si
    pop dx
    pop bx
    pop ax
    iret
; -----------------------------

start:
    mov ax, @DATA
    mov ds, ax
    
    call InitRepeatRates

    xor ax, ax
    mov es, ax

    mov ax, es:[1Ch*4]
    mov old_handler_offset, ax
    mov ax, es:[1Ch*4+2]
    mov old_handler_segment, ax

    cli
    mov word ptr es:[1Ch*4], offset int_handler
    mov ax, seg int_handler
    mov word ptr es:[1Ch*4+2], ax
    sti

    mov dx, offset start  
    int 27h               

InitRepeatRates:
    mov cx, 32
    mov si, offset RepeatRates
    mov al, 1Fh            
FillLoop:
    mov [si], al
    dec al
    inc si
    loop FillLoop
    ret

Tip1: InitRepeatRates belongs to the initialization part of the program. No need to have it 'stay resident'.

Tip2: Use BX instead of SI to correct next code:

xor si, si
mov al, cs:CurrentRate
mov si, x                       <<<<<<<<  ??????

It becomes:

xor bx, bx
mov bl, cs:CurrentRate

...

mov al, cs:RepeatRates[bx]
out 60h, al

(*) Separate the making of a TSR from the changing of the keyboard repeat setting. The task of pinpointing your error(s) will become much easier.

Sign up to request clarification or add additional context in comments.

3 Comments

You can address a cs using the same segment address as the PSP even in an MZ .exe executable, but it may be difficult to get the assembler to do that (if you don't emit the entire MZ header yourself).
callf dword ptr cs:old_handler_offset this line causes a compilation error A2016. Maybe you can send the syntax of the call specifically for masm. I didn`t find any information about it.
@Youvii I have changed the far call instruction. You now have 2 alternative solutions. call far ptr ... or call dword ptr [...].

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.