File: DEBUGFIX.TXT On: CLUBware Software Diskette #1 Author: Stan Radzio Copyright (C) 1984. RAYHAWK AUTOMATION, N W, INC. This patch to DOS 2.0 DEBUG prevents system lockup during trace operations. While single stepping through programs with the DEBUG T (trace) command, a timer interrupt may occur at the moment DEBUG transfers control to the next program instruction to execute. Instead of single stepping the next instruction from the user's program, DEBUG begins execution of the first instruction within the timer interrupt routine: an STI at location F000:FEA5 . When this happens, DEBUG displays the current register contents, the STI instruction from the timer interrupt routine, the DEBUG command prompt (-), then waits for a command from the keyboard. Unfortunately, the timer interrupt is an external hardware interrupt prioritized by the 8259 interrupt controller chip present on the PC motherboard. Once a timer interrupt occurs, no further external interrupts are allowed by the 8259 until an end-of-interrupt signal is sent to the 8259. Characters are transmitted from the keyboard to the PC by means of an external interrupt. DEBUG is left in a state where it is waiting for a command from the keyboard while the keyboard cannot communicate with the PC because external interrupts have been temporarily disabled by the 8259. The only recovery possible at this point is to power down the PC, then power up for a cold reboot. We have seen one previous fix to this problem. The circumvention is to disable timer interrupts while tracing program execution. From within DEBUG, use the I (in) command to retrieve the current contents of port 21h. This is the mask used by the 8259 to determine which external interrupts will be passed through to the 8088 CPU. -I 21 BC - The normal mask is BC allowing keyboard interrupts, disk controller interrupts, and timer interrupts. Change the mask to BD to prevent timer interrupts. -O 21 BD - There is a major problem with this process other than inaccuracies in the time of day. The timer interrupt service routine is used by the BIOS to turn off the diskette drive motor after diskette operations. With the timer interrupts masked off, the next diskette I/O will start the motor and the motor will remain on indefinitely. A true fix for this problem must be a modification to DEBUG itself. Once the problem occurs, the keyboard is silenced and there is no opportunity to invoke special service routines to bail us out. File: DEBUGFIX.TXT Page 2 One modification to DEBUG would be to have DEBUG send an end-of-interrupt signal to the 8259 just before requesting the next command from the keyboard. However, the BIOS routines have initialized the 8259 to recognize a non-specific end-of-interrupt. The end-of-interrupt signal is a 20h sent to port 20h and means an end to the current external interrupt whatever it may be. The possibility of DEBUG clearing an external interrupt not related to the timer is very small with the current hardware and software available for the PC. As for the future...? Without access to the source code for DEBUG, we have developed a modification to DEBUG which eliminates the problem and is completely safe. Our modification is to compare the address of the interrupted routine to the list of hardware interrupt service routines in low memory. If the address matches, we know that we collided with the interrupt and must act quickly to prevent the system from becoming deaf and mute. When the timer interrupts the code being traced, DEBUG is given control with the stack in this state: SP FEA5 - offset of timer interrupt routine SP+2 F000 - segment of timer interrupt routine SP+4 flags - saved copy of timer's flags (trap flag bit is set) SP+6 XXXX - offset of user code being traced SP+8 ZZZZ - segment of user code being traced SP+10 flags - saved copy of user's flags (trap flag bit is set) Our modification looks at the stack to see the F000:FEA5 and recognizes we are in trouble. To extricate ourselves, we clear the trap flag bit from SP+4, the timer's flags, and perform an IRET to return control to the timer interrupt routine. The timer code updates his count of the seconds in the day, turns the diskette motor off if needed, signals an end-of-interrupt to the 8259 (very important to us), then performs an IRET which transfers control to our code at SP+6. The trap flag at SP+10 is still set so a single step interrupt again occurs and DEBUG trace picks up normally as if nothing strange had happened. A similar process occurs when any external hardware interrupt other than the timer occurs during the trace command. The code we wish to add to DEBUG should be invoked whenever a single step interrupt occurs. Locating the correct insertion point within DEBUG.COM is as simple as looking backwards from the interrupt vector stored at 0000:0004, the single step interrupt vector. This interrupt vector is 0600:08ED for my version of DOS. The segment value, 600, varies with the number of I/O buffers, whether there are other interrupt service routines resident below DEBUG, whether we are executing from within a BAT file or invoked directly from DOS, etc, etc. The important piece of information obtained from this vector is 8ED, the offset relative to the start of DEBUG where we want to begin our code changes. File: DEBUGFIX.TXT Page 3 Within DEBUG at this offset is code to swap the stack and save all the registers. 600:8ED MOV WORD PTR CS:[2AEA],SP 600:8F2 MOV WORD PTR CS:[XXXX],SS We will replace the MOV instruction at 8ED with a JMP to our special code. When our code completes, we will execute the MOV instruction we have displaced and JMP back to 600:8F2. We need to find a safe place in memory to insert 35 instructions within DEBUG.COM. This is not easy without the source code. We have chosen to insert the extra code into the location currently occupied by the user's program segment prefix. In turn, the program segment prefix is pushed up in memory by 64 bytes. The prefix must be on a paragraph boundary so the number of bytes stolen must be a multiple of 16. The next logical step is to begin looking around inside DEBUG for an invocation of the DOS function 26h which builds the user's program segment prefix. The DEBUG code at 600:138 asks DOS to build a program segment prefix at 600:2f20 but when the smoke clears and the user's program actually starts execution, the prefix has been moved to 600:2f60 and the area at 2f20 has been trashed. Since we don't have DEBUG source, our only possible work around is to pad this area, soaking up 144 bytes to insert a patch less than 64 bytes long. We modify DEBUG code at 600:138 from MOV AX,2F17 to MOV AX,2FA0 For good measure we use the DEBUG S (search) command to look around inside DEBUG.COM for any other occurrences of the string 2F17. This search reveals a second place within DEBUG which must be changed, this time at 600:183 from MOV BX,2F17 to MOV BX,2FA0 The remaining modifications are straight-forward. Modify the instruction pointed to by the single step interrupt vector (600:8ED) from MOV WORD PTR CS:[2AEA],SP to JMP 2F60 File: DEBUGFIX.TXT Page 4 At 600:2F60 start adding our special instructions PUSH BP MOV BP,SP PUSH AX PUSH BX PUSH CX PUSH DI PUSH ES SUB AX,AX MOV ES,AX MOV DI,20 MOV AX,WORD PTR [BP+2] MOV BX,WORD PTR [BP+4] MOV CX,8 SCASW JNE 2F8C ES:CMP BX,WORD PTR [DI] JNE 2F8C AND WORD PTR [BP+6],FEFF POP ES POP DI POP CX POP BX POP AX POP BP IRET If the PUSH BP is at 2F60 then the following ADD is at 2F8C. ADD DI,2 LOOP 2F78 POP ES POP DI POP CX POP BX POP AX POP BP CS: MOV [2AEA],SP JMP 8F2 We set BP to point to the stack. Since BP itself is now on the stack, the offsets listed earlier relative to SP are two bytes off. We check the segment and offset stored on the stack to see if it is an external interrupt service routine. If it isn't, we restore the registers, execute the instruction which used to be at 600:8ED, and JMP back to 600:8F2. If it is an external interrupt, we clear the trap flag with the AND instruction, restore the registers, then IRET back to the interrupt service routine. File: DEBUGFIX.TXT Page 5 As distributed, the DEBUG.COM file does not contain the temporary variables used by DEBUG during execution. These variables reside between 600:2E80 (end of the DEBUG code) and 600:2F60 (eventual home of the user's program segment prefix). Since we are storing our code at 2F60, we must enlarge the DEBUG.COM file to include our added code and the intervening temp space. We modify DEBUG.COM using the DEBUG commands -n debug.com -l make the changes and then enlarge DEBUG.COM by writing back to disk more than we read -r cx CX 2E80 :2FA0 -w Writing 2FA0 bytes The simplest means of propagating this fix to other interested users is to create a text file containing all the needed DEBUG commands, then use the TYPE command to put the text file into the pipeline and feed the commands to DEBUG itself. If we put the following commands in a file named DEBUG20.FIX n debug.com l a 138 mov ax,2fa0 a 183 mov bx,2fa0 a 8ed jmp 2f60 a 2f60 push bp mov bp,sp push ax push bx push cx push di push es sub ax,ax mov es,ax mov di,20 mov ax,word ptr [bp+2] mov bx,word ptr [bp+4] mov cx,8 scasw --------------- Code split for page separation. No blank lines here File: DEBUGFIX.TXT Page 6 --------------- Code split for page separation. No blank lines here jne 2f8c es:cmp bx,word ptr [di] jne 2f8c and word ptr [bp+6],feff pop es pop di pop cx pop bx pop ax pop bp iret add di,2 loop 2f78 pop es pop di pop cx pop bx pop ax pop bp cs:mov [2aea],sp jmp 08f2 r cx 2fa0 w q Then with a copy of DEBUG.COM on the diskette in the default drive merely give the following pipelined command to DOS. TYPE DEBUG20.FIX | DEBUG The blank lines within the DEBUG20.FIX file are needed for correct execution of the commands. Once all the commands have executed, your DEBUG patch is complete. The cost of the patch is a DEBUG.COM file which requires 288 more bytes to store on diskette and which consumes an extra 144 bytes of memory when executed.