Homegrown Debugging--386 Style

Use the 80386's hardware to debug your programs by including Al's assembly language code to establish breakpoints.


March 01, 1990
URL:http://www.drdobbs.com/embedded-systems/homegrown-debugging-386-style/184408317

Figure 3

Figure 5

Figure 4

MAR90: HOMEGROWN DEBUGGING--386 STYLE

HOMEGROWN DEBUGGING--386 STYLE

Use hardware breakpoints to sniff through your C and assembly code

Al Williams

Al Williams is a staff systems engineer for Quad-S Consultants Inc. His current work includes a hypertext system, several expert systems, and a 386 DOS extender package. He can be reached at 2525 South Shore Boulevard, Suite 309, League City, TX 77573.


Although the installed base of 80386-based machines is ever increasing, most use this souped-up machine as a faster 8086. One of the problems in running the 80386 under DOS is that you lose many of the advantages of the 386. In addition, many of the 80386's powerful features are only usable in protected mode. Of course, developers loath to use special 80386 features because this can shut them out of the large 8086/80286 market.

Still, some features are usable while the 80386 is operating as an 8086 (the so-called "real mode"). For instance, the 80386 has powerful on-board hardware that allows sophisticated debugging techniques that require hardware debugging boards on other processors. This on-board hardware is available in real mode (as well as the other modes). With a little ingenuity, you can put this hardware to work while debugging programs.

This article puts a little of that kind of ingenuity in your hands by showing how you can use the 80386 hardware to debug your programs. I'll provide a program that can be included in your assembly code to establish breakpoints for the purpose of debugging either C or assembly language programs. In addition, I'll provide an example program and a quick utility that I'll explain shortly. All examples presented in this article compile under either MASM 5.0 or Microsoft C 5.1.

BREAK386

BREAK386 (Listing One, BREAK386 .ASM, page 96) is not a traditional debugger in the sense of, say, DEBUG or CodeView. By adding BREAK386 to your assembly language code, you can study it with code, data, and single-step breakpoints. You can also examine DOS or BIOS interrupts that your program calls. In addition, BREAK386 can add the same 386 hardware debugging to your Microsoft C programs.

BREAK386 provides functions to set up 386 debugging (setup386( )), set breakpoints (break386( )), and reset 80386 debugging (clear386()). In addition, BREAK386 provides an optional interrupt handler ( int1_386( )) that supports register, stack, and code dumps along with single stepping. You can use any of these functions from either C or assembly language.

There are cases where you may wish to modify int1_386( ) or write your own interrupt handler. For example, you may want to send the register dumps to a printer and automatically restart your program. With C, you will often want the interrupt handler to print out variables instead of registers. I'll provide some example interrupt handlers in C in a later section.

Using BREAK386

You must assemble BREAK386 before you can use it. Be sure to change the .MODEL statement to reflect the model you are using. If you are using explicit segment definitions in assembly, you must decide how to integrate BREAK386's code and data segments with your own. Assemble BREAK386 with the /Ml option to prevent MASM from converting all labels to uppercase. The resulting .OBJ file can be linked with your programs just as with any other object module.

If you are using programs (such as memory managers or multitaskers) that also use 386-specific functions, you may have to remove these programs before BREAK386 will function. The other program will usually report a "privilege exception" or something similar. Simply remove the other 386 programs and try again.

Adding 386 breakpoints to your program requires three steps:

Note that when calling these routines from assembly, the routine names contain leading underscores. For convenience,
Listing Two (BREAK386.INC, page 102) contains the assembly language definitions to use BREAK386. Listing Three (BREAK386.H, page 102) contains the same definitions for C. BREAK386.INC also includes two macros, traceon and traceoff, which are used to turn single stepping on and off from within the program.

Figure 1 shows the output from a breakpoint dump when using int1_ 386( ). The hexadecimal number on the first line is the contents of the low half of the DR6 register at the time of the breakpoint. The display shows all 16-bit and segment registers (except FS and GS). Following that is a dump of 32 words of memory starting at the bottom of the stack (1CB1:09FA in the example). The first three words of the stack are from the debug interrupt. The first word is the IP register, followed by the CS register and the flags. A simple change in the interrupt handler can remove this extra data from the display (see "Detailed Program Operation" in the next section).

Figure 1: Sample output from a breakpoint dump

Program breakpoint:OFF1
AX=0000 FL=7216 BX=0080 CX=0007 DX=06AA
SI=0000 DI=0A00 SP=09FA BP=0882
CS=1B66 IP=0051 DS=1BAD ES=1B56 SS=1CB1
Stack dump:(1CB1 : 09FA)
0051 1B66 7216 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000

CODE=1B66 : 0049 = 6A 04 E8 3F 00 83 C4 08 * B9 14 00 8A D1 80 C2 41

<V>iew output, <T>race toggle, <C>ontinue or <A>bort? _

Below the stack dump is a dump of program code. This dump usually consists of 16 bytes; 8 bytes before the current instruction and 8 bytes at the instruction pointer. This is convenient for data breakpoints because they occur after the offending instruction. The dump shows the starting memory address (1 B66:0049) followed by the bytes at that address. An asterisk marks the current CS:IP location, followed by the remaining 8 bytes. If IP is less than 8, the code dump will start at CS:0 resulting in fewer than 8 bytes before the asterisk.

The last line of the dump prompts you for further action. You can:

    1. View your program's output screen. When you select this option, BREAK386 replaces the current screen with your program's original output. To restore the debugging screen, press any key.

    2. Toggle the trace flag. This will switch the state of the trace or single-step flag, and continue the program in the same manner as the "C" command (see number 3). To determine whether or not tracing is on, examine the value of DR6. If bit 14 is set (4000 hex), tracing is on.

    3. Continue execution of the program. Selecting this option will resume the program where it left off. The program will execute until the next breakpoint (if the trace flag is clear) or to the next instruction (if the trace flag is set).

    4. Abort the program. This will cause the program to exit. Be careful, however, when using this selection. If you have interrupt vectors intercepted, expanded memory allocated, or anything else that needs fixing before you quit, the "A" command will not take care of these things unless you rewrite the interrupt handler or clear386( ). (Also, if your program spawns child processes, and the breakpoint occurred in the child, the abort command will terminate the child and the parent program will continue without breakpoints.)

Listings Four and Five, page 102, show examples of using BREAK386 in assembly and C. The identifiers beginning with BP_ are defined in BREAK386.H and BREAK 386.INC.

A few notes on these functions are in order. Your program must call setup386( ) before any other BREAK386 calls. You should pass it a segment and an offset pointing to the interrupt handler. After calling setup386( ), you may use break386( ) to set and clear breakpoints. Figure 2 shows the parameters break386( ) requires.

Figure 2: The parameters required by break386( )

  retcode=break386(n,type,address);
where:
  n is the breakpoint number (from 1 to 4).
  type is the type of breakpoint.  This should be one of the manifest
  constants defined in BREAK386.H (or BREAK386.INC).  If you are clearing
  the breakpoint, the type is not meaningful.
  address is the address to set the breakpoint.  This must be a far address
  (that is, one with both segment and offset).  If you are using small
  model C, you should cast the pointer to be a far type (see the example).
  To clear a breakpoint, set address to 0000:0000 (or a far NULL in C).
  retcode is returned by the function.  A zero indicates success.  A
  non-zero value means that you tried to set a breakpoint less than 1 or
  greater than 4.  Note that the type parameter is not checked for
  validity.

  The types available are:
         BP_CODE            - Code breakpoint
         BP_DATAW1          - One byte data write breakpoint
         BP_DATARW1         - One byte data read/write breakpoint
         BP_DATAW2          - Two byte data write breakpoint
         BP_DATARW2         - Two byte data read/write breakpoint
         BP_DATAW4          - Four byte data write breakpoint
         BP_DATARW4         - Four byte data read/write breakpoint

You must keep in mind a few facts about the 80386 when setting breakpoints or tracing. First, 2- and 4-byte data breakpoints must be aligned according to their size. For example, it is incorrect to set a 2-byte breakpoint at location 1000:0015 because that location is on an odd byte. Similarly, a 4-byte breakpoint can monitor address 1000:0010 or 1000:0014 but not address 1000:0013. If you must watch an unaligned data item, you will have to set multiple breakpoints. For example, to monitor 2 bytes at 1000:0015, set a 1-byte breakpoint at 1000:0015 and another at 1000:0016.

More Details.

Also, keep in mind that a data breakpoint will occur even if you only access a portion of its range. For instance, if you are monitoring a word at 2200:00F0 and a program writes a byte to 2200: 00F1, a breakpoint will occur.

Setting a data breakpoint with break 386( ) will also set the global exact bit. When all the data breakpoints are either reassigned or deactivated, break386( ) will clear the exact bit.

Because int1_386( ) always sets the resume flag, you will find that a code breakpoint that immediately follows a data breakpoint won't work. I'll show how this can be rectified shortly.

Because INT and INTO instructions temporarily clear the trace flag, BREAK-386 will not single step through interrupt handlers. If you wish to single step through an interrupt routine, you will have to set a breakpoint on its first instruction. A replacement for int1_ 386( ) might emulate INT and INTO instructions to solve this problem.

Because BREAK386 uses BIOS keyboard and video routines, take care when placing breakpoints in these routines. In addition, single-stepping BIOS keyboard and video routines should be avoided. If you must debug in these areas, reassemble BREAK386 so that it doesn't use BIOS (see the DIRECT equate in BREAK386.ASM). Note, however, that many of its features will no longer function. Finally, you should avoid setting breakpoints in BREAK386's code or data.

BREAK386.INC contains two macros, traceon and traceoff, that can be used to control tracing. You may insert them anywhere in your code to enable or disable tracing. Remember, however, that you will see the traceoff macro as well as your own code when single stepping.

The function clear386( ) must be called prior to exiting the program. This turns off the breakpoint handlers. If you fail to call clear386( ) for any reason (a control-break, or a critical error), the next program that uses a location you have breakpointed will cause the break to occur. This can have unfortunate consequences because your interrupt 1 handler is probably no longer in memory. If you find that you have exited a program without turning off debugging and you have not encountered a breakpoint, run DBGOFF (Listing Six, page 104) to turn off hardware debugging.

With some care, BREAK386 can be used with other debuggers. In CodeView, for example, BREAK386 seems to work fine, as long as you are not single stepping (via CodeView). When you single step data breakpoints will be ignored and BREAK386 code breakpoints will "freeze" CodeView at that step. If you are using BREAK386 with CodeView, it is probably a good idea to leave the code breakpoints and single stepping to CodeView.

Detailed Program Operation

BREAK386 (Listing One) begins with the .386P directive, which ensures that MASM 5.0 will generate references to the debug registers. Be careful to place the .MODEL directive before the .386P, otherwise 32-bit segments will be generated (which doesn't work well with unmodified DOS!).

The parameters you may want to change are near the top of the source file. The equate to DIRECT controls the video mode. If DIRECT is 0, BREAK386 uses BIOS for input and output. If, however, you want to poke around in the keyboard or video routines, you must set DIRECT to 1. This causes BREAK386 to use direct video output for the debug dump. It will share the screen with your program (no video swapping) and breakpoints will simply terminate the program in a similar manner to the "A" command mentioned earlier.

You can change the STKWRD equate to control how many words are dumped from the stack when using int1_386( ). Setting STKWRD to zero will completely disable stack dumping. Similarly, if you set INTSTACK to zero, the display will not show the IP/CS/FLAGS at the top of the stack. If you are writing your own interrupt handler and don't need int1_386( ), you can assemble with ENABLE_INT1 set to zero to reduce BREAK386's size.

The operations of start386( ), clear-386( ), and break386( )are fairly straight-forward. The implementation of int1_386( ) deserves some comment. It is important to realize that int1_386( ) only debugs non-386-specific programs because it only saves the 16-bit registers and the 8086 segment registers (int1_386( ) does not destroy FS and GS). Because int1_386( ) only runs on a 386, it does use the 32-bit registers. You can easily modify int1_386( ) to save all the 386 registers, but it requires more space on the interrupted program's stack.

The most difficult aspect of the interrupt handler is managing the resume flag. The code below label c1 converts the three words at the top of the stack into six words so that setting the resume flag is possible. There are three things to remember about the way the resume flag is managed:

    1. As mentioned earlier, int1_386( ) always sets the resume flag. As a consequence, a code breakpoint that occurs immediately after a data breakpoint will not cause an interrupt. This is due to the resume flag being set even though the instruction that generated the data breakpoint has already executed. When the program restarts, the next instruction will execute with the resume flag set. This could be rectified by not setting the resume flag in the interrupt handler when processing data breakpoints.

    2. An interrupt handler written entirely in C has no way to manipulate the resume flag properly. Listing Seven, page 104, however, shows two assembly language functions that allow you to write your handler in C. (See the next section for more details on writing C interrupt handlers.)

    3. In real mode, hardware interrupt handlers (for example, those in the BIOS) will probably not preserve the resume flag. This means that if your code runs with interrupts enabled, there is some chance that one breakpoint will cause two interrupts. This chance increases greatly if interrupts remain disabled during the interrupt 1 processing. Why is this true? If the 80386 receives a hardware interrupt just before executing an instruction with the resume flag set, it will process that interrupt. When the interrupt returns, the resume flag is clear and the breakpoint occurs again. When interrupts are disabled during breakpoint processing, it is far more likely that an interrupt is pending when the program restarts. If interrupts were enabled while processing the debug interrupt, however, there is little chance of this happening. If it does, simply press "C" (when using int1_386( )).

Advanced Interrupt Handlers in C

It is possible to write an interrupt handler completely in C to monitor data breakpoints. The hahdler must be declared as a far interrupt function. For example, the following function could be linked with the example in Listing Five:

void interrupt far new1(Res,Rds,Rdi,Rsi,Rbp,Rsp,Rbx,Rdx, Rcx,Rax)

{ printf("\nBreakpoint reached.\n"); }

By calling setup386new1( ) instead of setup386(int1_386), new1( )will be invoked for every breakpoint. Your function can read and write the interrupted program's registers using the supplied parameters (Rax, Rbx, and so on). Keep in mind that you cannot use this technique for code breakpoints. C's inability to manipulate the resume flag will cause an endless loop on a code breakpoint.

Listing Seven, provides the functions to write interrupt handlers in C. The procedure is much the same as described earlier, except that you must call csetup386( ) instead of setup386( ). The argument to csetup386( ) is always a pointer to an ordinary far function (even in small model).

The actual interrupt handler is _cint1_ 386( ). This function will call your C code when an interrupt occurs. _cint1 _386( ) passes your routine two arguments. The first argument, a far void pointer, is set to the beginning of the interrupted stack frame (see Figure 3 for the format of the stack frame). The second argument is an unsigned long int that contains the contents of DR6.

All registers, and local variables on the stack can be read using the pointer to the stack frame (if you know where to look). In addition, all values (except SS) can be modified. It is usually wise not to modify SP, CS, or IP.

_cint1_386( ) switches to a local stack. The size of the stack can be controlled using STACKSIZE (near the top of Listing Seven). Be sure to adjust the stack if you need more space.

Listing Eight (page 105) shows an example of an interrupt handler in C. The example interrupt handler displays a breakpoint message and allows you to continue with or without breakpoints, abort the program, or change the value of a local variable in the loop( ) function.

Future Directions

Many enhancements and modifications are possible with BREAK386. By altering the words on int1_386( )'s stack, for example, you can modify registers. You can redirect output to the printer (although you can screen print the display now) by replacing the OUCH routine. Perhaps the most ambitious enhancement would be to use BREAK386 as the core of your own debugger. You could write a stand-alone debugger or a TSR debugger that would pop up over another debugger (DEBUG or CodeView).

Keep in mind that 386 hardware breakpoints aren't just for debugging. The data breakpoint capability has many uses. For example, you might want to monitor the BIOS keyboard typeahead buffer's head and tail pointers to see when a keystroke is entered or removed. In this manner you could capture the keyboard interrupt in such a way that other programs couldn't reprogram your interrupt vector.

You can also use data breakpoints to detect interrupt vector changes or interrupt processing. Some assembly language programs could use data breakpoints for automatic stack overflow detection. Programs that decrement the stack pointer without using a push instruction (Microsoft C programs, for example) are not candidates for this type of stack protection.

Debugging with 386 assistance is quite practical and useful. The programs presented here should get you started and help you develop your own programs with this powerful hardware feature.

Bibliography

Turley, James L., Advanced 80386 Programming Techniques, Osborne McGraw-Hill, Berkeley, Calif., 1988.

Intel Corporation, 80386 Programmer's Reference Manual, Intel Corp., Santa Clara, Calif., 1986.

80386 Debugging Features

Most PC developers are familiar with some aspect of chip debug assistance. Even the 8088 has a breakpoint interrupt and a "single-step flag," which allows debuggers to trace code one instruction at a time. The 386 shares these same features with the earlier processors, but adds eight debug registers (two of which Intel reserves). These debug registers control the hardware breakpoint features.

Hardware breakpoints are much more powerful than ordinary breakpoints (such as those in DEBUG) for two reasons. First, hardware breakpoints don't actually modify your program. This means that you can set breakpoints anywhere, even in ROM. Also, a program can't overwrite a breakpoint when it modifies itself or loads an overlay. Second, it is possible to set breakpoints on data. A data breakpoint triggers when your program accesses a certain memory location.

Microsoft's CodeView implements a similar data breakpoint capability, called "tracepoints." To maintain compatibility with non-386 PCs, however, CodeView doesn't use 386 features. As a result, CodeView checks tracepoints after the execution of each instruction. This, of course, is terribly slow. By moving the tracepoints to 386 hardware, execution isn't slowed down at all. Actually, you will usually want to slow down execution just a bit (see the discussion of the exact bit). Even then, the slowdown in execution is imperceptible.

Because there are four debug address registers in the 80386, it is possible to have four active breakpoints at once. Each address register (DRO-DR3) represents a linear address at which a different breakpoint will occur. In protected mode, the concept of a linear address is not straightforward. In real mode, however, a linear address can easily be calculated from a segment/offset pair. Simply multiply the segment value by 10 hex (shift left 4 bits) and add the offset. For example, to set a data breakpoint at B800:0020 (somewhere in the CGA video buffer), you would need a linear address of:

  B800 x 10 + 20 = B8020

Once you have loaded the address registers, you must enable the breakpoints you wish to use and tell the processor what type of breakpoints they are. This is done via the debug control register (DR7). DR7 contains bits to enable each breakpoint and to set their type individually (see Figure 4). You will notice that DR7 has global and local enable bits as well as global and local exact bits (explained shortly). The difference between the various global bits and local bits is only important when the 80386 is multitasking in protected mode. For the purpose of this article, they are the same.


The Exact Bits

The exact bits are flags to tell the 80386 to slow down. At first glance, this doesn't seem to be helpful, but a detailed look at the 80386 architecture reveals the purpose of this bit.

The 80386 gains some of its speed by overlapping instruction fetches and data fetches. This is an excellent idea when executing code, but causes problems in debugging data. Without the exact bit set, a data breakpoint will not occur at the instruction that caused the data access! Being somewhat of an inconvenience, Intel included the GE/LE bits. With either (or both) of them set, data breakpoints will occur immediately after the instruction that caused them, although the processor will lose a slight amount of speed.

Other Bits

All debug breakpoints generate an interrupt 1. To distinguish the various breakpoints, you must read the debug status register (DR6). DR6 has bits corresponding to the various breakpoint conditions (see Figure 5). Note the BT flag at bit 15. As with the local bits in DR7, only multitasking systems use the BT flag. Therefore, the flag is not considered in this article. The 386 never clears the bits in DR6, so after you determine what caused the interrupt, you should clear DR6.

The Only Other Bit We Haven't Discussed is ...

With the general detect (GD) bit set in DR7, the 80386 prohibits access to the debug registers. Any attempt to access the debug registers will cause an interrupt 1 with the BD flag set in DR6. Intel's in-circuit emulator uses this feature, although you can use it if you have any reason to disable or control access to the debug registers. When a GD interrupt occurs, the interrupt handler is invoked and the GD bit is cleared. Otherwise, the routine would fault (with an endless loop) when the interrupt routine attempted to read DR6.

You can decide from the interrupt routine whether to terminate the user program, or to allow access to the registers. BREAK386 does not use the GD bit.

The Resume Flag

The last consideration with breakpoint interrupts is how to resume the interrupted program. If we simply return (as in a normal interrupt), there is nothing to stop a code breakpoint from occurring again immediately. The resume flag (found in the flag's register) prevents this from occurring. This flag inhibits further debug exceptions while set, and resets automatically as soon as one instruction successfully executes. Control of the resume flag is automatic in protected mode. Handling it from real mode, however, is somewhat of a trick, as seen in BREAK386.

-- A.W.

HOMEGROWN DEBUGGING -- 386 STYLE! by Al Williams

[LISTING ONE]



;***************************************************************************
;* File: BREAK386.ASM                                                         *
;* BREAK386 "main programs". Contains setup386, clear386, break386 and        *
;* int1_386.                                                                  *
;*     Williams - June, 1989                                                  *
;* Compile with: MASM /Ml BREAK386;                                           *
;***************************************************************************
.MODEL small
.386P

       public _break386,_clear386,_setup386,_int1_386

; Set up stack offsets for word size arguments based on the code size
; Be careful, regardless of what Microsoft's documentation says,
; you must use @CodeSize (not @codesize, etc.) when compiling with /Ml

IF @CodeSize               ; True for models with far code
arg1       EQU  <[BP+6]>
arg2       EQU  <[BP+8]>
arg3       EQU  <[BP+10]>
arg4       EQU  <[BP+12]>
ELSE
arg1       EQU  <[BP+4]>
arg2       EQU  <[BP+6]>
arg3       EQU  <[BP+8]>
arg4       EQU  <[BP+10]>
ENDIF



.DATA
; Things you may want to change:
DIRECT      EQU 0        ; IF 0 use BIOS; IF 1 use direct video access
STKWRD      EQU 32       ; # of words to dump off the stack
INTSTACK    EQU 1        ; When 0 don't display interrupt stack words
USE_INT1    EQU 1        ; Set to 0 to disable int1_386()

oldoffset   dw 0         ; old interrupt 1 vector offset
oldsegment  dw 0         ; old interrupt 1 vector segment

IF USE_INT1
video       dw 0b000H    ; segment of video adapter (changed by vinit)
csip        db 'CODE=',0
done        db 'Program terminated normally.',0
notdone     db 'Program breakpoint:',0
stkmess     db 'Stack dump:',0

vpage       db 0
vcols       db 80

IFE DIRECT
prompt      db '<V>iew output, <T>race toggle, <C>ontinue or <A>bort? ',0
savcursor   dw 0         ; inactive video cursor
ALIGN 4
vbuff       dd 1000 dup (07200720H)
ELSE
cursor      dw 0
color       db 7
ENDIF
ENDIF

.CODE

; This is the start up code. The old interrupt one vector is saved in
; oldsegment, oldoffset. int1_386 does not chain to the old vector, it
; simply replaces it.

_setup386 proc
        push bp
        mov bp,sp
        push es
        mov ax,3501H               ; get old int1 vector
        int 21h
        mov ax,es
        mov oldsegment,ax
        mov oldoffset,bx
        pop es
        mov ax,arg2                ; get new interrupt handler address
        push ds
        mov dx,arg1
; If int1_386 is being assembled, setup386 will check to see if you are
; installing int1386. If so, it will call vinit to set up the video parameters
; that int1_386 requires.
IF USE_INT1
        cmp ax,seg _int1_386
        jnz notus
        cmp dx,offset _int1_386
        jnz notus
        push dx
        push ax
        call vinit                 ; Int'l video if it is our handler
        pop ds
        pop dx
ENDIF
notus:  mov ax,2501H               ; Store interrupt address in vector table
        int 21H
        pop ds
        xor eax,eax                ; Clear DR7/DR6 (just in case)
        mov dr7,eax
        mov dr6,eax
        pop bp
        ret
_setup386 endp


; This routine sets/clears breakpoints
; Inputs:
;    breakpoint # (1-4)
;    breakpoint type (see BREAK386.INC)
;    segment/offset of break address (or null to clear breakpoint)
; Outputs:
;    AX=0 If successful
;    AX=-1 If not successful

_break386 proc
        push bp
        mov bp,sp
        mov bx,arg1                     ; breakpoint # (1-4)
        cmp bx,1
        jb outrange
        cmp bx,4
        jna nothigh
outrange:
        mov ax,0ffffH                   ; error: breakpoint # out of range
        pop bp
        ret
nothigh:
        movzx eax,word ptr arg4         ; get breakpoint address
        shl eax,4
        movzx edx,word ptr arg3         ; calculate linear address
        add eax,edx                     ; if address = 0 then
        jz resetbp                      ; turn breakpoint off!
        dec bx                          ; set correct address register
        jz bp0
        dec bx
        jz bp1
        dec bx
        jz bp2
        mov dr3,eax
        jmp short brcont
bp0:    mov dr0,eax
        jmp short brcont
bp1:    mov dr1,eax
        jmp short brcont
bp2:    mov dr2,eax
brcont:
        movzx eax,word ptr arg2         ; get type
        mov cx,arg1                     ; calculate proper position
        push cx
        dec cx
        shl cx,2
        add cx,16
        shl eax,cl                      ; rotate type
        mov edx,0fh
        shl edx,cl                      ; calculate type mask
        not edx
        pop cx
        shl cx,1                        ; calculate position of enable bit
        dec cx
        mov ebx,1
        shl ebx,cl
        or eax,ebx                      ; enable bp
        mov ebx,dr7                     ; get old DR7
        and ebx,edx                     ; mask out old type
        or ebx,eax                      ; set new type/enable bits
; Adjust enable bit (set on for data bp's, off if no data bp's)
adjge:
        mov eax,200H
        and ebx,0fffffdffH              ; reset GE bit
        test ebx,033330000H             ; test for data bp's
        jz nodatabp
        or ebx,512
nodatabp:
        mov dr7,ebx
        pop bp
        xor ax,ax
        ret
; Here we reset a breakpoint by turning off it's enable bit & setting type to 0
; Clearing the type is required so that disabling all data breakpoints will
; clear the GE bit also.
resetbp:
        mov cx,bx                       ; calculate type/len bit positions
        mov edx,0fh
        dec cx
        shl cx,2
        add cx,16
        shl edx,cl
        not edx
        mov cx,bx                       ; calculate enable bit position
        shl cx,1
        dec cx
        mov eax,1
        shl eax,cl
        not ax                          ; flip bits
        mov ebx,dr7
        and ebx,eax                     ; clear enable
        and ebx,edx                     ; clear type
        jmp adjge
_break386 endp



; Reset the debug register, disabling all breakpoint. Also restore the old
; interrupt 1 vector
_clear386 proc
        pushf
        pop ax
        and ax,0FEFFH                   ; turn off trace flag
        push ax
        popf
        xor eax,eax                     ; turn off all other breakpoints
        mov dr7,eax
        mov dr0,eax
        mov dr1,eax
        mov dr2,eax
        mov dr3,eax
        mov dr6,eax
        mov ax,2501H                    ; restore old int 1 vector
        push ds
        mov bx,oldsegment
        mov dx,oldoffset
        mov ds,bx
        int 21H
        pop ds
        ret
_clear386 endp

IF USE_INT1
; This is all code relating to the optional INT 1 handler

; This macro is used to get a register value off the stack and display it
; R is the register name and n is the position of the register on the stack
; i.e.: outreg 'AX',10

outreg   macro r,n
         mov ax,&r
         mov dx,[ebp+&n SHL 1]
         call regout
         endm


; This is the interrupt 1 handler
_int1_386  proc far

         sti                            ; Enable interrupts (see text)
         pusha                          ; Save all Registers
         push ds
         push es
         push ss
         push @data
         pop ds                         ; Reload DS
         mov bp,sp                      ; point ebp to top of stack
IFE DIRECT
        call savevideo
ENDIF
         mov ax,video                   ; get video addressabilty
         mov es,ax
         assume cs:@code,ds:@data
         mov bx,offset notdone          ; Display breakpoint message
         call outstr
         mov edx,dr6
         call  hexout
         xor edx,edx
         mov dr6,edx
         call  crlf
;do register dump
         outreg 'AX',10
         outreg 'FL',13
         outreg 'BX',7
         outreg 'CX',9
         outreg 'DX',8
         call  crlf
         outreg 'SI',4
         outreg 'DI',3
         outreg 'SP',6
         outreg 'BP',5
         call  crlf
         outreg 'CS',12
         outreg 'IP',11
         outreg 'DS',2
         outreg 'ES',1
         outreg 'SS',0
         call  crlf
 ; do stack dump
IF STKWRD
         mov bx,offset stkmess
         call outstr                    ; Print stack dump title
         push fs
         mov dx,[ebp]                   ; get program's ss
         mov fs,dx
         mov al,'('
         call  ouch
         mov al,' '
         call  ouch
         call  hexout
         mov al,':'
         call  ouch
         mov al,' '
         call  ouch
         mov bx,[ebp+12]                ; get stack pointer (before pusha)
IFE INTSTACK
         add bx,6                       ; skip interrupt info if desired
ENDIF
         mov dx,bx
         push bx
         call  hexout
         mov al,')'
         call  ouch
         call  crlf
         pop bx
         mov cx,STKWRD
sloop:

         mov dx,fs:[bx]                 ; get word at stack
         push bx
         push cx
         call  hexout                    ; display it
         pop cx
         pop bx
         inc bx
         inc bx
         loop sloop
         pop fs
ENDIF
nostack:
; Here we will dump 16 bytes starting 8 bytes prior to the instruction
; that caused the break
         push fs
         call  crlf
         mov bx, offset csip
         call outstr
         mov cx,8
         mov ax,[ebp+24]                ; get cs
         mov fs,ax
         mov bx,[ebp+22]                ; get ip
         cmp bx,8                       ; make sure we have 8 bytes before
         jnb ipbegin                    ; the begining of the segment
         mov cx,bx                      ; If not, only dump from the start
ipbegin: sub bx,cx                      ; of the segment
         push bx
         push cx
         mov dx,ax                      ; display address
         call  hexout
         mov al,':'
         call  ouch
         mov al,' '
         call  ouch
         mov dx,bx
         call  hexout
         mov al,'='
         call  ouch
         pop cx
         pop bx
         or bx,bx                       ; if starting at 0, don't display any
         jz ipskip                      ; before IP
iploop:
         mov dl,fs:[bx]                 ; get byte
         push bx
         push cx
         call  hex1out                   ; output it
         pop cx
         pop bx
         inc bx
         loop iploop
ipskip:
         push bx
         mov al,'*'                     ; put '*' before IP location
         call  ouch
         mov al,' '
         call  ouch
         pop bx
; This is basically a repeat of the above loop except it dumps the 8 bytes
; starting at IP
         mov cx,8
xiploop:
         mov dl,fs:[bx]
         push bx
         push cx
         call  hex1out
         pop cx
         pop bx
         inc bx
         loop xiploop
         call  crlf
         call  crlf
         pop fs
IFE DIRECT
; Here we will ask if we should continue or abort
         mov bx,offset prompt
         call outstr
keyloop:
         xor ah,ah                      ; Get keyboard input
         int 16H
         and al,0dfh                    ; make upper case
         cmp al,'T'

         jz ttoggle
         cmp al,'A'
         jz q1
         cmp al,'C'
         jz c1
         cmp al,'V'
         jnz keyloop
; Display program's screen until any key is pressed
         call savevideo
         xor ah,ah
         int 16H
         call savevideo
         jmp keyloop

; Execution comes here to toggle trace flag and continue
ttoggle:
         xor word ptr [bp+26],256       ; toggle trace flag on stack

; Execution comes here to continue running the target program
c1:
         call  crlf
IFE DIRECT
         call savevideo
ELSE
         xor ax,ax
         mov cursor,ax
ENDIF
         pop ss
         pop es
         pop ds
         popa
; This seems complicated at first.
; You MUST insure that RF is set before continuing. If RF is not set
; you will just cause a breakpoint immediately!
; In protected mode, this is handled automatically. In real mode it
; isn't since RF is in the high 16 bits of the flags register.
; Essentially we have to convert the stack from:
;
;       16 bit Flags                 32 bit flags (top word = 1 to set RF)
;       16 bit CS       to ----->    32 bit CS    (garbage in top 16 bits)
;       16 bit IP                    32 bit IP    (top word = 0)
;
; All this so we can execute an IRETD which will change RF.

         sub esp,6            ; make a double stack frame
         xchg ax,[esp+6]      ; get ip in ax
         mov [esp],ax         ; store it
         xor ax,ax
         mov [esp+2],ax       ; eip = 0000:ip
         mov ax,[esp+6]
         xchg ax,[esp+8]      ; get cs
         mov [esp+4],ax
         xor ax,ax
         mov [esp+6],ax
         mov ax,[esp+8]       ; zero that stack word & restore ax
         xchg ax,[esp+10]     ; get flags
         mov [esp+8],ax
         mov ax,1             ; set RF
         xchg ax,[esp+10]
         iretd                ; DOUBLE IRET (32 bits!)

ENDIF

; Execution resumes here to abort the target program
q1:
IFE DIRECT
         call savevideo
ENDIF
         call quit
_int1_386  endp

IFE DIRECT
; save video screen & restore ours (only with BIOS please!)
; (assumes 25 lines/page)
savevideo proc near
        pusha
        push es
        mov ah,0fh
        int 10h                         ; reread video page/size in case
        mov vpage,bh                    ; program changed it
        mov vcols,ah

        push savcursor
        mov ah,3                        ; get old cursor
        mov bh,vpage
        int 10H
        mov savcursor,dx
        pop dx
        mov ah,2                        ; set new cursor
        int 10H
        movzx ax,vpage
        mov cl,vcols                    ; compute # bytes/page
        xor ch,ch
        mov dx,cx                       ; vcols * 25 * 2
        shl cx,3
        shl dx,1
        add cx,dx
        mov dx,cx
        shl cx,2
        add cx,dx
        push cx
        mul cx
        mov di,ax                       ; start at beginning of page
        pop cx
        shr cx,2                        ; # of double words to transfer
        mov ax,video
        mov es,ax
        mov si,offset vbuff             ; store inactive screen in vbuff
xloop:  mov eax,es:[di]                 ; swap screens
        xchg eax,[si]
        mov es:[di],eax
        add si,4
        add di,4
        loop xloop
        pop es
        popa
        ret
savevideo endp
ENDIF


; This routine prints a register value complete with label
; The register name is in AX and the value is in dx (see the outreg macro)
regout  proc near
        push dx
        push ax
        mov al,ah
        call  ouch
        pop ax
        call  ouch
        mov al,'='
        call  ouch
        pop dx
        call  hexout
        ret
regout  endp

; Plain vanilla hexadecimal digit output routine
hexdout proc near
        and dl,0fh
        add dl,'0'
        cmp dl,3ah
        jb ddigit
        add dl,'A'-3ah
ddigit:
        mov al,dl
        call ouch
        ret
hexdout endp

; Plain vanilla hexadecimal word output routine
hexout  proc near
        push dx
        shr dx,12
        call hexdout
        pop dx
        push dx
        shr dx,8
        call hexdout
        pop dx
; Call with this entry point to output just a byte
hex1out:
        push dx
        shr dx,4
        call hexdout
        pop dx
        call hexdout
        mov al,' '
        call ouch
        ret
hexout  endp


; These routines are for direct video output. Using them allows you to
; debug video bios calls, but prevents you from single stepping
IF DIRECT
;output a character in al assumes ds=dat es=video destroys bx,ah
ouch     proc near
         mov bx,cursor
         mov ah,color
         mov es:[bx],ax
         inc bx
         inc bx
         mov cursor,bx
         ret
ouch     endp

; <CR> <LF> output.  assumes ds=dat es=video  destroys ax,cx,dx,di  clears df
crlf     proc near
         mov ax,cursor
         mov cx,160
         xor dx,dx
         div cx
         inc ax
         mul cx
         mov cursor,ax
         mov cx,80
         mov ah,color
         mov al,' '
         mov di,cursor
         cld
         rep stosw
         ret
crlf     endp

ELSE
; These are the BIOS output routines
; Output a character
ouch     proc near
         mov ah,0eh
         mov bh,vpage
         int 10h
         ret
ouch     endp

; <CR> <LF> output.
crlf     proc near
         mov al,0dh
         call ouch
         mov al,0ah
         call ouch
         ret
crlf     endp



ENDIF

; Intialize the video routines
vinit    proc near
         mov ah,0fh
         int 10h
         mov vcols,ah
         mov vpage,bh
         cmp al,7                       ; monochrome
         mov ax,0b000H
         jz vexit
         mov ax,0b800H
vexit:   mov video,ax
         ret
vinit    endp


; outputs string pointed to by ds:bx (ds must be dat) es= video when DIRECT=1
outstr   proc near
outagn:
         mov al,[bx]
         or al,al
         jz outout
         push bx
         call ouch
         pop bx
         inc bx
         jmp outagn
outout:  ret
outstr   endp


; This routine is called to return to DOS
quit     proc near
         call _clear386
         mov ax,4c00h                   ; Return to DOS
         int 21h
quit     endp


ENDIF

        end





[LISTING TWO]



;***************************************************************************
;* File: BREAK386.INC                                                         *
;* Header file to include with assembly language programs using BREAK386      *
;*     Williams - June, 1989                                                  *
;***************************************************************************

IF @CodeSize       ; If large style models
        extrn _break386:far,_clear386:far,_setup386:far,_int1_386:far
ELSE
        extrn _break386:near,_clear386:near,_setup386:near,_int1_386:far
ENDIF

; Breakpoint equates
BP_CODE     EQU  0                 ; CODE BREAKPOINT
BP_DATAW1   EQU  1                 ; ONE BYTE DATA WRITE BREAKPOINT
BP_DATARW1  EQU  3                 ; ONE BYTE DATA R/W BREAKPOINT
BP_DATAW2   EQU  5                 ; TWO BYTE DATA WRITE BREAKPOINT
BP_DATARW2  EQU  7                 ; TWO BYTE DATA R/W BREAKPOINT
BP_DATAW4   EQU 13                 ; FOUR BYTE DATA WRITE BREAKPOINT
BP_DATARW4  EQU 15                 ; FOUR BYTE DATA R/W BREAKPOINT

; Macros to turn tracing on and off
; Note: When tracing, you will actually "see" traceoff before it turns
;       tracing off

traceon  macro
         push bp
         pushf
         mov bp,sp
         xchg ax,[bp]
         or ax,100H
         xchg ax,[bp]
         popf
         pop bp
         endm

traceoff macro
         push bp
         pushf
         mov bp,sp
         xchg ax,[bp]
         and ax,0FEFFH
         xchg ax,[bp]
         popf
         pop bp
         endm




[LISTING THREE]


/***************************************************************************
 * File: BREAK386.H                                                           *

 * C Header for C programs using BREAK386 or CBRK386                          *
 *     Williams - June, 1989                                                  *
 **************************************************************************/

#ifndef NO_EXT_KEYS
  #define _CDECL cdecl
#else
  #define _CDECL
#endif

#ifndef BR386_HEADER
  #define BR386_HEADER

  /* declare functions */
  void  _CDECL setup386(void (_CDECL interrupt far *)());
  void  _CDECL csetup386(void (_CDECL far *)());
  void  _CDECL clear386(void);
  int   _CDECL break386(int,int, void far *);
  void  _CDECL far interrupt int1_386();

  /* breakpoint types */
  #define BP_CODE        0                 /* CODE BREAKPOINT*/
  #define BP_DATAW1      1                 /* ONE BYTE DATA WRITE BREAKPOINT*/
  #define BP_DATARW1     3                 /* ONE BYTE DATA R/W BREAKPOINT*/
  #define BP_DATAW2      5                 /* TWO BYTE DATA WRITE BREAKPOINT*/
  #define BP_DATARW2     7                 /* TWO BYTE DATA R/W BREAKPOINT*/
  #define BP_DATAW4     13                 /* FOUR BYTE DATA WRITE BREAKPOINT*/
  #define BP_DATARW4    15                 /* FOUR BYTE DATA R/W BREAKPOINT*/

#endif





[LISTING FOUR]


;***************************************************************************
;* File: DEBUG386.ASM                                                         *
;* Example assembly language program for use with BREAK386                    *
;*     Williams - June, 1989                                                  *
;* Compile with: MASM /Ml DEBUG386.ASM;                                       *
;***************************************************************************

.model large
.386
INCLUDE break386.inc
.stack 0a00H

.data
align 2                                  ; make sure this is word aligned
memcell dw 0                             ; cell to write to


.code

main    proc
;setup data segment
        mov ax,@data
        mov ds,ax
        assume cs:@code,ds:@data

; start debugging
        push seg _int1_386               ; segment of interrupt handler
        push offset _int1_386            ; offset of interrupt handler
        call _setup386
        add sp,4                         ; balance stack (like a call to C)
; set up a starting breakpoint
        push seg bp1                     ; segment of breakpoint
        push offset bp1                  ; offset of breakpoint
        push BP_CODE                     ; breakpoint type
        push 1                           ; breakpoint # (1-4)
        call _break386
        add sp,8                         ; balance the stack

        push seg bp2                     ; set up breakpoint #2
        push offset bp2
        push BP_CODE
        push 2
        call _break386
        add sp,8

        push seg bp3                    ; set up breakpoint #3
        push offset bp3
        push BP_CODE
        push 3
        call _break386
        add sp,8

        push @data                      ; set up breakpoint #4 (data)
        push offset memcell
        push BP_DATAW2
        push 4
        call _break386
        add sp,8



bp1:
        mov cx,20                       ; loop 20 times
loop1:
        mov dl,cl                       ; print some letters
        add dl,'@'
        mov ah,2
bp2:
        int 21h
bp3:
        loop loop1                      ; repeat

        mov bx,offset memcell           ; point bx at memory cell
        mov ax,[bx]                     ; read cell (no breakpoint)
        mov [bx],ah                     ; this should cause breakpoint 4
        call _clear386                  ; shut off debugging
        mov ah,4ch
        int 21h                         ; back to DOS
main    endp
        end main






[LISTING FIVE]


/***************************************************************************
 * File: DBG386.C                                                          *
 * Example C program using BREAK386 with the built in interrupt handler    *
 * Al Williams  -- 15 July 1989                                            *
 * Compile with: CL DBG386.C BREAK386                                      *
 ***************************************************************************/
#include <stdio.h>
#include <dos.h>
#include "break386.h"

int here[10];
void far *bp;
int i;

main()
{
  int j;
  setup386(int1_386);          /* set up debugging */
  bp=(void far *)&here[2];     /* make long pointer to data word */
  break386(1,BP_DATAW2,bp);    /* set breakpoint */
  for (j=0;j<2;j++) {          /* loop twice */
    for (i=0;i<10;i++)         /* for each element in here[] */
     {
     char x;
     putchar(i+'0');           /* print index digit */
     here[i]=i;                /* assign # to array element */
     }
  break386(1,0,NULL);          /* turn off breakpoint on 2nd pass */
  }
  clear386();                  /* turn off debugging */
}







[LISTING SIX]


;***************************************************************************
;* File: DBGOFF.ASM                                                           *
;* Try this program if you leave a program abnormally (say, with a stack      *
;* overflow). It will reset the debug register.                               *
;*     Williams - June, 1989                                                  *
;* Compile with: MASM DBGOFF;                                                 *
;***************************************************************************

.model small
.386P
.stack 32
.code

main proc
     xor eax,eax                        ; clear dr7
     mov dr7,eax
     mov ah,4ch                         ; exit to DOS
     int 21H
main endp
     end main





[LISTING SEVEN]


;***************************************************************************
;* File: CBRK386.ASM                                                          *
;* Functions to allow breakpoint handlers to be written in C.                 *
;*     Williams - June, 1989                                                  *
;* Compile with: MASM /Ml CBRK386.ASM;                                        *
;***************************************************************************
.MODEL small
.386P

       public _csetup386

; Set up stack offsets for word size arguments based on the code size
; Be careful, regardless of what Microsoft's documentation says,
; you must use @CodeSize (not @codesize, etc.)

IF @CodeSize               ; True for models with far code
arg1       EQU  <[BP+6]>
arg2       EQU  <[BP+8]>
arg3       EQU  <[BP+10]>
arg4       EQU  <[BP+12]>
ELSE
arg1       EQU  <[BP+4]>
arg2       EQU  <[BP+6]>
arg3       EQU  <[BP+8]>
arg4       EQU  <[BP+10]>
ENDIF



.DATA
; You may need to change the next line to expand the stack your breakpoint
; handler runs with
STACKSIZE   EQU 2048

oldoffset   dw 0                        ; old interrupt 1 vector offset
oldsegment  dw 0                        ; old interrupt 1 vector segment
oldstack    equ this dword
sp_save     dw 0
ss_save     dw 0
ds_save     dw 0
es_save     dw 0
ccall       equ this dword              ; C routine's adress is saved here
c_off       dw 0
c_seg       dw 0
oldstkhqq   dw 0                        ; Old start of stack


newsp       equ this dword              ; New stack address for C routine
            dw offset stacktop
            dw seg newstack

; Here is the new stack. DO NOT MOVE IT OUT OF DGROUP
; That is, leave it in the DATA or DATA? segment.
newstack    db STACKSIZE DUP (0)
stacktop    EQU $
            extrn STKHQQ:word           ; Microsoft heap/stack bound

.CODE


; This routine is called in place of setup386(). You pass it the address of
; a void far function that you want invoked on a breakpoint.
; It's operation is identical to setup386() except for:
;
;      1) The interrupt 1 vector is set to cint1_386() (see below)
;      2) The address passed is stored in location CCALL
;      3) DS and ES are stored in ds_save and es_save

_csetup386 proc
        push bp
        mov bp,sp
        push es
        mov ax,es
        mov es_save,ax
        mov ax,ds
        mov ds_save,ax
        mov ax,3501H
        int 21h
        mov ax,es
        mov oldsegment,ax
        mov oldoffset,bx
        pop es
        mov ax,arg2
        push ds
        mov dx,arg1
        mov c_seg,ax
        mov c_off,dx
        mov ax,seg _cint1_386
        mov ds,ax
        mov dx,offset _cint1_386
        mov ax,2501H
        int 21H
        pop ds
        xor eax,eax
        mov dr6,eax
        pop bp
        ret
_csetup386 endp

;**************************************************************************
;*                                                                           *
;* Here is the interrupt handler!!!                                          *
;* Two arguments are passed to C, a far pointer to the base of the stack     *
;* frame and the complete contents of dr6 as a long unsigned int.            *
;*                                                                           *
;* The stack frame is as follows:                                            *
;*                                                                           *
;*    .                                                                      *
;*    .                                                                      *
;*   (Interrupted code's stack)                                              *
;*   FLAGS                                                                   *
;*   CS                                                                      *
;*   IP <DDDD?                                                               *
;*   AX      3                                                               *
;*   CX      3                                                               *
;*   DX      3                                                               *
;*   BX      3                                                               *
;*   SP DDDDDY (Stack pointer points to IP above)                            *
;*   BP                                                                      *
;*   SI                                                                      *
;*   DI                                                                      *
;*   ES                                                                      *
;*   DS                                                                      *
;*   SS <DDDDD pointer passed to your routine points here                    *
;*                                                                           *
;* The pointer is two way. That is, you can read the values or set any of    *
;* them except SS. You should, however, refrain from changing CS,IP,or SP.   *
;*                                                                           *
;**************************************************************************/

_cint1_386 proc
        pusha                           ; save registers
        push es
        push ds
        push ss
        mov ax,@data                    ; point at our data segment
        mov ds,ax
        mov ax,ss
        mov ss_save,ax                  ; remember old stack location
        mov sp_save,sp
        cld
        lss sp,newsp                    ; switch stacks
        mov ax,STKHQQ                   ; save old end of stack
        mov oldstkhqq,ax
        mov ax,offset newstack          ; load new end of stack
        mov STKHQQ,ax
        sti
        mov eax,dr6                     ; put DR6 on stack for C
        push eax
        push ss_save                    ; put far pointer to stack frame
        push sp_save                    ; on new stack for C
        mov ax,es_save                  ; restore es/ds from csetup386()
        mov es,ax
        mov ax,ds_save
        mov ds,ax
        call ccall                      ; call the C program
        xor eax,eax                     ; clear DR6
        mov dr6,eax
        mov ax,@data
        mov ds,ax                       ; regain access to data
        lss sp,oldstack                 ; restore old stack
        add sp,2                        ; don't pop off SS
                                        ; (in case user changed it)
        mov ax,oldstkhqq                ; restore end of stack
        mov STKHQQ,ax
        pop ds
        pop es
        popa

; This seems complicated at first.
; You MUST insure that RF is set before continuing. If RF is not set
; you will just cause a breakpoint immediately!
; In protected mode, this is handled automatically. In real mode it
; isn't since RF is in the high 16 bits of the flags register.
; Essentially we have to convert the stack from:
;
;       16 bit Flags                 32 bit flags (top word = 1 to set RF)
;       16 bit CS       to ----->    32 bit CS    (garbage in top 16 bits)
;       16 bit IP                    32 bit IP    (top word = 0)
;
; All this so we can execute an IRETD which will change RF.

        sub esp,6                       ; make a double stack frame
        xchg ax,[esp+6]                 ; get ip in ax
        mov [esp],ax                    ; store it
        xor ax,ax
        mov [esp+2],ax                  ; eip = 0000:ip
        mov ax,[esp+6]
        xchg ax,[esp+8]                 ; get cs
        mov [esp+4],ax
        xor ax,ax
        mov [esp+6],ax
        mov ax,[esp+8]                  ; zero that stack word & restore ax
        xchg ax,[esp+10]                ; get flags
        mov [esp+8],ax
        mov ax,1                        ; set RF
        xchg ax,[esp+10]
        iretd                           ; DOUBLE IRET (32 bits!)
_cint1_386 endp
        end





[LISTING EIGHT]


/***************************************************************************
 * File: CBRKDEMO.C                                                           *
 * Example C interrupt handler for use with CBRK386                           *
 *     Williams - June, 1989                                                  *
 * Compile with: CL CBRKDEMO.C BREAK386 CBRK386                               *
 ***************************************************************************/


#include <stdio.h>
#include <conio.h>
#include <ctype.h>
#include <dos.h>
#include "break386.h"

/* functions we will reference */
int loop();
void far broke();


main()
  {
  int i;
/* declare function broke as our interrupt handler */
  csetup386(broke);
  break386(1,BP_CODE,(void far *)loop); /* set break at function loop */

  for (i=0;i<10;i++) loop(i);
  printf("Returned to main.\n");

  clear386();                           /* turn off debugging */
  }


/* This function has a breakpoint on its entry */
loop(int j)
  {
  printf("Now in loop (%d)\n",j);
  }

/**************************************************************************
 *                                                                           *
 * Here is the interrupt handler!!!                                          *
 * Note it must be a far function (normal int the LARGE, HUGE & MEDIUM       *
 * models). Two arguments are passed: a far pointer to the base of the stack *
 * frame and the complete contents of dr6 as a long unsigned int.            *
 *                                                                           *
 * The stack frame is as follows:                                            *
 *                                                                           *
 *    .                                                                      *
 *    .                                                                      *
 *   (Interrupted code's stack)                                              *
 *   FLAGS                                                                   *
 *   CS                                                                      *
 *   IP <DDDD?                                                               *
 *   AX      3                                                               *
 *   CX      3                                                               *
 *   DX      3                                                               *
 *   BX      3                                                               *
 *   SP DDDDDY (Stack pointer points to IP above)                            *
 *   BP                                                                      *
 *   SI                                                                      *
 *   DI                                                                      *
 *   ES                                                                      *
 *   DS                                                                      *
 *   SS <DDDDD pointer passed to your routine points here                    *
 *                                                                           *
 * The pointer is two way. That is, you can read the values or set any of    *
 * them except SS. You should, however, refrain from changing CS,IP,or SP.   *
 *                                                                           *
 **************************************************************************/

void far broke(void far *p,long dr6)
  {
  static int breaking=1;  /* don't do anything if breaking=0 */
  int c;
  if (breaking)
    {
    int n;
    int far *ip;
/**************************************************************************
 *                                                                           *
 * Here we will read the local variable off the interrupted program's stack! *
 * Assuming small model, the stack above our stack frame looks like this:    *
 *      i   -  variable sent to loop                                         *
 *      add -  address to return to main with                                *
 *    <our stack frame starts here>                                          *
 *                                                                           *
 * This makes i the 15th word on the stack (16th on models with far code)    *
 *                                                                           *
 **************************************************************************/

#define IOFFSET 15    /* use 16 for large, medium or huge models */
    n=*((unsigned int far *)p+IOFFSET);
    printf("\nBreakpoint reached! (DR6=%lX i=%d)\n",dr6,n);
/* Ask user what to do. */
    do {
       printf("<C>ontinue, <M>odify i, <A>bort, or <N>o breakpoint? ");
       c=getche();
       putch('\r');
       putch('\n');                     /* start a new line */
       if (!c)                          /* function key pressed */
          {
          getch();
          continue;
          }
       c=toupper(c);
/* Modify loop's copy of i (doesn't change main's i) */
       if (c=='M')
          {
          int newi;
          printf("Enter new value for i: ");
          scanf("%d",&newi);
          *((unsigned int far *)p+IOFFSET)=newi;
          continue;
          }
       if (c=='A')                      /* Exiting */
         {
         clear386();                    /* ALWAYS turn off debugging!!! */
         exit(0);
         }
       if (c=='N')
         breaking=0;  /* We could have turned off breakpoints instead */
       } while (c!='A'&&c!='N'&&c!='C');
    }
  }










Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.