Copyright 1984 by ABComputing July 15, 1984 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º PC-DOS Interrupts º º º º by º º º º Donald Buresh º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ My next three articles discuss the PC-DOS interrupts. These interrupts can be classified into 4 categories: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ 1. Interrupt 21H or the PC-DOS function ³ ³ calls; ³ ³ ³ ³ 2. Interrupt 20H and interrupts 22H through ³ ³ 27H which are used by applications ³ ³ programs; ³ ³ ³ ³ 3. Interrupts 28H and 2FH which are used ³ ³ internally by PC-DOS; and ³ ³ ³ ³ 4. Interrupts 29H through 2FH and interrupts ³ ³ 30H through 3FH which are reserved for ³ ³ future use. ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This article covers interrupts 20H-27H, 28H, and 2FH, leaving the PC-DOS function calls for the next two issues. I will present the PC-DOS function-calls 00H through 2EH that appeared in PC-DOS 1.x in my next article, and PC-DOS 2.x function-calls 2FH through 57H in the following one. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 20H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This interrupt is used to exit a program and return control to PC-DOS. It restores the termination, Control-Break, and critical error-handling addresses stored in a program's Program Segment Prefix to their original values. All files should be closed before issuing this interrupt; otherwise the file lengths will not be recorded correctly. The CS register must also contain the starting address of the Program Segment Prefix. INT 20H is invoked by typing INT 20H ; Return to PC-DOS in an assembly-language program. Regardless of the values in the registers, control will return to PC-DOS. I recommend that you use this interrupt with .COM files only. Although the PC-DOS manual states that INT 20H and PC-DOS function-call 00H are the same, terminating a program with the latter function call insures that the program can be initiated or terminated using function-calls 4BH (load or execute a program), 4CH (terminate a process), and 4DH (retrieve the return code of a subprocess.) ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 22H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ When a program terminates, control is transferred to the address contained in this interrupt vector. The address is a place in PC-DOS. This means that INT 22H cannot be invoked by a program like INT 20H. Its sole purpose is to hold an address to be used by INT 20H. When executing one program from another by using function-call 4BH, the calling program must redirect INT 22H to a return address in the calling program. This insures that the called program returns to the calling program rather than to PC-DOS or to some unknown area in memory. Let's learn how to use this interrupt. On page E-8 of the PC-DOS 2.0 manual is a map of a Program Segment Prefix. Here is how it might look in a program. CSEG Segment Para Public 'CSEG' Assume CS:CSEG,DS:CSEG,ES:CSEG,SS:Nothing Org 00H PSP01_Interrupt_20H Label Word Org 02H PSP02_Top_of_Memory Label Word Org 04H PSP03_Reserved_Byte Label Byte Org 05H PSP04_Long_DOS_Call Label Byte Org 0AH PSP05_Term_Addr_Offset Label Word Org 0CH PSP06_Term_Addr_Segment Label Word . . Org 100H A1000_Main_Module Proc Far . . ; Here is the program . . CSEG Ends End A1000_Main_Module PC-DOS copies the address contained in INT 22H into PSP05_Term_Addr_Offset and PSP06_Term_Addr_Segment when loading the program. If a program calls another program using function-call 4BH, the calling program should execute the following sequence of instructions to save and restore INT 22H. Push ES ; Save ES register Mov AH,35H ; Get function call Mov AL,22H ; Set INT 22H Int 21H ; Invoke interrupt Mov WRK12_Int22H_Offset,BX ; Save offset address Mov WRK13_Int22H_Segment,ES; Save segment address Pop ES ; Restore ES register ; ; 1. Save the segment registers, stack pointer, and ; base pointer. ; 2. Call the program using INT 21H with AH = 4BH ; 3. Restore the segment registers, stack pointer, and ; base pointer ; Mov AH,25H ; Set function call Mov AL,22H ; Set INT 22H Mov DX,WRK12_Int22H_Offset ; Get offset address Mov BX,WRK13_Int22H_Segment; Get segment address Push DS ; Save DS for a moment Mov DS,BX ; Put DS into BX Int 21H ; Invoke interrupt Pop DS ; Restore DS register ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 23H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ When a user presses Control-Break or Control-C, PC-DOS transfers control to this address. INT 23H points either to PC-DOS or to a module in an applications program with the following features: 1. If the exit from the module is by an IRET instruction, the module must save and restore all registers. 2. If the module trashes registers and uses a far RET instruction, it must set the Carry Flag to return to PC-DOS; otherwise, the program continues running. If a user module processes Control-Break or Control-C, control returns to the instruction that it was executing when the interrupt occurred. When calling a program with function-call 4BH, the called program may return to the calling program or have its own Control-Break handler. Suppose that a program handles Control-Break or Control-C rather than letting PC-DOS do it. The following code points the INT 23H interrupt vector to the control-break handler in the program. This code should be appear immediately after setting up the program's stack. CSEG Segment Para Public 'CSEG' Assume CS:CSEG,DS:CSEG,ES:CSEG,SS:Nothing Org 100H A1000_Main_Module Proc Far Cli ; Set up user's stack Mov SP,MEM01_Top_of_Stack ; . Sti ; ..End ; Mov AH,25H ; Set up the control-break Mov AL,23H ; handler Mov DX,Offset U1400_Control_Break; . Int 21H ; ..End . . If the control-break module exits by an IRET instruction, it might look like: U1400_Control_Break Proc Near ; Push AX ; Save the registers Push DX ; . ; Mov AH,09H ; Display the control Mov DX,Offset MES35_Control_Break; . Int 21H ; ..End ; Pop DX ; Restore the registers Pop AX ; ..End ; Iret ; MES35_Control_Break Label Byte DB 'Processing a control break.',0AH,0CH,'$' ; U1400_Control_Break Endp On the other hand, suppose the routine employs a far RET to exit and that the value in WRK13_DOS_Return determines whether or not to return to PC-DOS. Then the routine could be written as: U1400_Control_Break Proc Far ; Push AX ; Save the registers Push DX ; ..End ; Cmp WRK13_DOS_Return,00H ; Compare to return Jne U1401 ; to PC-DOS ; Mov AH,09H ; Display continue Mov DX,Offset MES43_Continue; execution message Int 21H ; ..End Clc ; Clear carry flag Jmp U1499 ; Go to end of module U1401: Mov AH,09H ; Display return to Mov DX,Offset MES44_Return ; PC-DOS message Int 21H ; ..End Stc ; Set the carry flag U1499: Pop DX ; Restore the registers Pop AX ; ..End ; Ret ; MES43_Continue Label Byte DB 'Continuing execution.',0AH,0CH,'$' MES44_Return Label Byte DB 'Returning to PC-DOS.',0AH,0CH,'$' ; U1400_Control_Break Endp ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 24H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Like most of the PC-DOS interrupts, INT 24H can be redirected to a user-written module. This module is executed whenever there is a critical error in one of the INT 21H function calls. PC-DOS 2.0 and 2.1 try to perform a function call 5 times before invoking the interrupt, while PC-DOS 1.x only attempts a function call 3 times. When entering the module, interrupts are disabled and the 7th bit of AH is set if there was a hard-disk error. In PC-DOS 2.0 and 2.1, BP:SI contains the address of the Device Header Control Block defined on page D-8 of the PC-DOS 2.0 manual. In the low byte of DI is an error code. The routine should save registers AX, BX, CX, DX, SI, DI, BP, DS, and ES on the stack before processing the error. Although it is not explicitly stated, it is extremely important to preserve the ES register. In PC-DOS 1.x, a critical error-handler could process only disk errors. PC-DOS 2.0 or 2.1 allows this routine to process character-device errors as well. If it is a disk error, the routine should set AL to indicate the desired action: 0 (ignore) 1 (retry), or 2 (abort). A program should use only PC-DOS functions 00H through 0CH in the critical error-handler because using the other functions could cause the module to be called recursively. This situation might be disastrous because of the limited size PC-DOS stack. A program can employ most of the ROM calls with little or no effect. The critical error-handler should avoid INT 13H because it talks to the PC-DOS file functions. When leaving the critical error-handler, the module should restore all registers, remove the last 3 words from the stack, enable interrupts, and return by an IRET instruction. If this is not done, the operating system crashes. Let's write a critical error-handler that displays a message when there is a disk error. In the initial stages of the program we must save and redirect INT 24H. Push ES ; Save ES register Mov AH,35H ; Get function call Mov AL,24H ; Set INT 24H Int 21H ; Invoke interrupt Mov WRK22_Int24H_Offset,BX ; Save offset address Mov WRK23_Int24H_Segment,ES; Save segment address Pop ES ; Restore ES register Mov AH,25H ; Set up the critical Mov AL,24H ; . error handler Mov DX,Offset U1900_Int24H ; . Int 21H ; ..End The critical error-handler might appear as: U1900_Int24H Proc Near Push AX ; Save the registers Push BX ; . Push CX ; . Push DX ; . Push SI ; . Push DI ; . Push BP ; . Push DS ; . Push ES ; ..End Push CS ; Set up DS and ES Pop DS ; . Push CS ; . Pop ES ; ..End Test AH,80H ; Test for bit 7 = 1 Jnz U1901 ; The bit is not set Mov AX,DI ; Set AX to error code Cbw ; Perform sign extend Mov DL,0AH ; Set DL to divisor Div DL ; Divide AL by 10 Add AL,32H ; Set the chars in AX Add AH,32H ; ..End Mov BX,AX ; Store result in BX Mov AH,09H ; DOS function call Mov DX,Offset MES15_Disk_Message Mov [DX+18],AH ; Move error to message Mov [DX+19],AL ; ..End Int 21H ; Invoke DOS call Jmp U1990 ; Go to end of module U1901: Mov AH,09H ; DOS function call Mov DX,Offset MES16_Other_Error Int 21H ; Invoke DOS function U1990: Pop ES ; Restore the registers Pop DS ; . Pop BP ; . Pop DI ; . Pop SI ; . Pop DX ; . Pop CX ; . Pop BX ; . Pop AX ; ..End Iret MES15_Disk_Error Label Byte DB 'Disk Error Number .',0AH,0CH,'$' MES16_Other_Error Label Byte DB 'Other Error.',0AH,0CH,'$' U1900_Int24H Endp ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 25H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This vector reads absolute sectors from a diskette by invoking INT 13H from the BIOS. This interrupt puts the status of the disk operation into the Carry Flag. To obtain this information a program must issue a POPF instruction after issuing INT 25H. On entry, the drive number is in AL, the number of sectors to read is in CX, the beginning logical sector number is in DX, and the disk transfer address is in DS:BX. Logical sector numbers are sequentially numbered starting with head 0, track 0, sector 1 (logical sector 0), continuing along the same head until reaching the last sector on the last head and track. A logical sector number can be found by: Logical Sector Number = A * B * (Head Number) + B * (Track Number) + (Sector Number - 1) where A is the number of tracks per head and B is the number of sectors per track. For example, suppose we want to calculate the logical sector number for a PC-DOS 2.0 diskette. The number of tracks per head is 40 and the sectors per track is 9. The logical sector number for head 1 track 12 sector 5 is: (40 * 9 * 1) + (9 * 12) + (5 - 1) = 360 + 108 + 4 = 472 All registers except the segment registers are destroyed by this interrupt. If the disk read was successful, the Carry Flag is equal to 0; otherwise, it is set to 1 and AX contains the error code returned by INT 13H. As an example, we read the directory of a PC-DOS 1.1 single-sided diskette in drive A. The directory begins on head 0, track 0, sector 4 and is 4 sectors long. The logical sector number is: (40 * 8 * 0) + (8 * 0) + (4 - 1) = 3 The assembly language code is: Mov AH,25H ; DOS absolute read Mov AL,00H ; Drive number Mov CX,0004 ; Num. of sectors to read Mov BX,Offset MEM04_Transfer_Address Int 25H ; Read the directory Popf ; Get the carry flag Jnc D3201_Good_Return ; Go to good return ; ; Process the error condition ; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 26H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This vector writes absolute sectors on a diskette by transferring control directly to the BIOS. Since this is the counterpart of INT 25H, the same description applies to this vector. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupt 27H ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The purpose of this interrupt is to allow a program to remain resident in low memory after PC-DOS regains control. In order for INT 27H to work correctly, the program must set DX to the highest address plus 1 in the code segment. After invoking the interrupt, PC-DOS considers the program to be part of the operating system and loads all programs above it. A program that employs INT 27H must be a .COM file because it must remain resident in low memory. Suppose that we trap INT 10H and insert a linefeed whenever we encounter a linefeed in the write-teletype option. The code looks like: CSEG Segment Para Public 'CSEG' Assume CS:CSEG,DS:CSEG,ES:CSEG,SS:Nothing Org 100H A1000_Main_Module Proc Far Mov CX,ES ; Save ES register Mov AH,35H ; Get DOS function Mov AL,10H ; Set INT 10H Int 21H ; Invoke DOS call Mov WRK01_Int10H_Offset,BX ; Get offset address Mov WRK02_Int10H_Segment,ES; Get segment address Mov ES,CX ; Restore ES register Mov AH,25H ; DOS function call Mov AL,10H ; Set INT 10H Mov DX,Offset B1000_Int10H ; Set new INT 10H addr Int 21H ; Invoke DOS call Mov DX,Offset End_of_Program ; Set DX to addr of Inc DX ; . the end of program Int 27H ; Invoke the interrupt A1000_Main_Module Endp B1000_Int10H Proc Far Mov AH,25H ; DOS function call Mov AL,10H ; Set INT 10H Mov CX,DS ; Save DS for moment Mov DX,WRK01_Int10H_Offset ; Set offset address Mov BX,WRK02_Int10H_Segment ; Set segment address Mov DS,BX ; ..End Int 21H ; Invoke DOS call Mov DS,CX ; Restore DS register Push AX ; Save AX from trashing Int 10H ; Invoke ROM call Pop AX ; Restore AX Cmp AH,0EH ; Compare for write tty Jne B1001 ; If not, continue Cmp AL,0AH ; Compare for linefeed Jne B1001 ; If not, continue Int 10H ; Invoke ROM call again B1001: Mov AH,25H ; Restore DOS function Mov AL,10H ; Set INT 10H Mov DX,Offset B1000_Int10H ; Set new INT 10H addr Int 21H ; Invoke DOS call B1000_Int10H Endp WRK01_Int10H_Offset DW (?) WRK02_Int10H_Segment DW (?) End_of_Program Equ $ CSEG Ends End A1000_Main_Module I have used this interrupt in writing and debugging display, keyboard, and serial device drivers. While testing a display driver for a serial communications program, I discovered that INT 21H and INT 10H use common variables in the ROM data segment. This made it nearly impossible to test the display driver by keeping it resident in low memory. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Interrupts 28H and 2FH ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ According to the PC-DOS manual, these interrupts are used internally by the operating system. After a great deal of snooping with DEBUG, I determined how they are used by PRINT.EXE. INT 28H appears to be a software interrupt that counts time. Using this interrupt permits a program to execute a process independent of the hardware. Interrupt 2FH seems to communicate with the parallel and serial ports. It is unfortunate that IBM and Microsoft chose to keep these interrupts proprietary, since their use makes a program extremely portable. Here are the results of my snooping into PRINT.EXE. The program initially obtains the name of the file to be PRINTed and the device name from the user. The program then uses PC-DOS to redirect INT 28H to its own routine. In the next series of instructions, PRINT opens the file and redirects INT 05H, 13H, 14H and 17H to its own routines. The program terminates but stays resident using PC-DOS function call 31H. PRINT.EXE then redirects the critical error-handler INT 24H to its own routine, sets the disk transfer address using function call 1AH, and reads the file into the buffer. After completing this task, PRINT.EXE redirects INT 28H to the appropriate write routine and restores INT 24H. When invoking INT 28H, PRINT.EXE redirects INT 24H and outputs a character in the buffer using INT 2FH. The critical error-handler checks for disk errors as well as Control-Break or Control-C. Since I have just isolated the routine, I'm not sure how it all works. When the buffer is empty, the program reads the file once again. In the end, PRINT.EXE restores INT 24H and transfers control to PC-DOS. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conclusion ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This article presented my analysis of the PC-DOS interrupts, excluding INT 21H, and gave examples of their use. My next article discusses INT 21H function calls. I will highlight their advantages and disadvantages. I think it will be fun. ÚÄÄÄÄÄÄ¿ ³ P.S. ³ ÀÄÄÄÄÄÄÙ Because of my heavy work schedule and my recent move, I have not completed the source-code guide for FREECOPY. I expect to finish the manuscript shortly. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ File Name: ÛÛ dos1.txt ÛÛ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ