Assembly DOS program

Assembly DOS program

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? Thanks in advance, any help is very much appreciated!

Answer

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.
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 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:

    ...
    pushf
    callf dword ptr cs:old_handler_offset
    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)

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

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]
    
    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 cs
    push offset Back
    push word ptr cs:old_handler_segment
    push word ptr cs:old_handler_offset
    retf
Back:
    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               

Enjoyed this question?

Check out more content on our blog or follow us on social media.

Browse more questions