Search code examples
assemblyx86interrupttasmdosbox

how do I catch the timer interrupt in 8086 assembly language


I am using tasm and dosbox to solve a problem. I have to catch the timer interrrupt and output all squares of natural numbers until they have 16-bit length. In the example my professor provides the 08h interrupt is replaced with a new one, which is not a good thing to do, since altering 08h can lead to malfunction. As far as I know it is much better to toggle 1ch interrupt, which is triggered by 08h every 18,2 ms and does not do anything necessary to the computer. Moreover reading my professor's code has left me with a great number of whats and whys. I wrote the following code, but it does not seem do work as intended, it does output squares, however, it stops execution at a random moment of time and I do not understand what I am missing. Any help would be appreciated, thanks for reading

.MODEL SMALL
.STACK 1024H 
.DATA     
  NUM    DB 1
  SQUARE DW ?         
.CODE  
  ASSUME CS:@CODE, DS:@DATA, ES:NOTHING 
  START:    
  OLD_INT_SEG DW ?
  OLD_INT_OFF DW ? 
  MOV AX, @DATA
  MOV DS, AX
  ;saving away the old interrupt;         
  MOV AX, 0
  MOV ES, AX
  MOV AX, ES:[1CH * 4]     
  MOV WORD PTR OLD_INT_OFF, AX
  MOV AX, ES:[1CH * 4 + 2]
  MOV WORD PTR OLD_INT_SEG, AX          
  ;patching into the new one;
  CLI 
  MOV WORD PTR ES:[1CH * 4], OFFSET TIMER_ISR 
  MOV WORD PTR ES:[1CH * 4 + 2], CS      
  STI   
  ;calculating squares;    
  TO_THE_SECOND_POWER:  
    XOR AX, AX
    MOV AL, NUM   
    MUL NUM
    ;outputing them;  
    CALL PRINT_NUM    
    INC NUM
  LOOP TO_THE_SECOND_POWER
  ;the new isr's body;              
  TIMER_ISR PROC NEAR     
    PUSH DS
    PUSH AX
    MOV AX, @DATA
    MOV DS, AX 
    JС OVERFLOW_REACHED   
    POP AX           
    POP DS 
   ;calling the original 1ch isr; 
    JMP CS:OLD_INT_OFF   
  TIMER_ISR ENDP        
  PRINT_NUM PROC 
    PUSH BX
    PUSH DX
    PUSH SI 
    PUSH CX 
    MOV CX, 0
    MOV BX, 10  
    LOOP_HERE: 
      MOV DX, 0 
      DIV BX 
      PUSH AX 
      ADD DL, "0"  
      POP AX  
      PUSH DX  
      INC CX  
      CMP AX, 0 
    JNZ LOOP_HERE
    MOV AH, 2  
    LOOP_HERE_2: 
      POP DX      
      MOV AH, 02H  
      INT 21H
    LOOP LOOP_HERE_2   
    MOV DL, 10
    MOV AH, 02H
    INT 21H
    MOV DL, 13
    MOV AH, 02H
    INT 21H
    POP CX 
    POP SI 
    POP DX 
    POP BX 
    RET
  PRINT_NUM ENDP  
  ;if overflow has occurred;
  OVERFLOW_REACHED:  
  ;restoring the old isr;                  
  MOV AX, 0
  MOV ES, AX
  CLI 
  MOV AX, WORD PTR OLD_INT_OFF  
  MOV ES:[1CH * 4], AX
  MOV AX, WORD PTR OLD_INT_SEG
  MOV ES:[1CH * 4 + 2], AX
  STI
  ;and terminating the program;    
  MOV AH, 4CH                     
  MOV AL, 0H                   
  INT 21H                     
END START   
CODE ENDS

Solution

    1. Your code segment begins at the START: label with two DW ? directives. They will be executed as if they were code. The fact that your code does not crash right there is a miracle.

    2. To be perfectly (paranoidly so) correct, saving away the old interrupt should be done inside the CLI/STI block, because in theory someone might change it after you have saved it and before you patched it.

    3. Your JС OVERFLOW_REACHED jumps if the C flag was set, but does not follow any instructions that would modify the C flag. (MOV instructions do not alter the C flag.) Therefore, it will probably jump if the C flag was set by whatever code was pre-empted by the interrupt. That's a recipe for disaster.

    4. Loop LOOP TO_THE_SECOND_POWER uses the CX register as a loop counter. You do not initialize CX anywhere.

    5. After LOOP TO_THE_SECOND_POWER you are not doing anything meaningful to ensure that your program will gracefully handle the case where it computes all numbers that were to be computed.

    6. Your interrupt handler may invoke OVERFLOW_REACHED:, which will invoke INT 21H . That's a recipe for disaster. Do not invoke any operating system functions from within an interrupt handler.

    In short, your interrupt handler should set some variable to inform your main loop that it should stop looping.