Eavesdropping on Interrupts

Tracking down software problems by monitoring interrupt activity

Rick Knoblaugh

Rick is a software engineer specializing in systems programming, is the coauthor of Screen Machine, a screen design/prototyping/code-generation utility. He can be reached at P.O. Box 1109, Half Moon Bay, CA 94019.


INTM is an interrupt-monitoring program for MS-DOS that traps and logs interrupt activity, enabling your debugger or other program to gain control when specified interrupts occur. It accomplishes this by using 80386 protected-mode code which acts as a V86 monitor, providing protected-mode initialization and creation of a virtual-8086 task within which DOS will execute.

INTM executes at privilege-level 0 and configures the environment so that the program receives control whenever interrupts occur within the V86 task. Interrupts can then be dispatched to their original interrupt-service routines or (optionally) control can be transferred first to a user-specified interrupt. This interrupt can be one which generates a "breakpoint" in your debugger, or it can be any interrupt you choose.

I'll briefly review initialization of the protected-mode environment (for a more complete discussion, see "Your Own Protected-Mode Debugger," DDJ, September 1992), then show how your programs can communicate with INTM, directing it to conditionally log interrupt activity and/or generate breakpoints on specified interrupts. The entire program consists of a number of files; most are provided electronically; see "Availability," page 3. Among these files are: INTEQU.INC, equates for the program; INTSTRUC.INC, structures for the program; INTDAT.INC, program data; INTMAC.INC, macros; the initialization functions are provided in INTINIT.ASM, see Listing One, page 94; INTISR.ASM, interrupt-service routines; INTMAKE, the make file; INTM.LNK, the linker input file, INTDISP.C, a sample program for retrieving logged data; INTEXAMP.C, a sample program which demonstrates how to interface with the interrupt monitor; and INTM.EXE, the executable.

Initialization

The global-descriptor table (GDT) is the first area to be initialized. It contains entries for all segments to be used by INTM, including the task-state segment (TSS) and interrupt-descriptor table (IDT). After these data areas are established, virtual-8086 mode is entered by creating a protected-mode exception stack frame, setting the VM bit in the EFLAGS, and executing an IRETD. INTM then terminates and stays resident in your system.

Interrupt Handling

One of the differences between real and protected modes is that interrupts pass through gates in the IDT rather than through the interrupt vectors. INTM provides entries for all hardware interrupts, causing them to pass through a common routine (see pass_thru in INTISR.ASM, available electronically). The pass_thru routine can optionally log the interrupt and/or pass control to a user-specified breakpoint interrupt before causing a transfer to the original interrupt service routine (per the real-mode interrupt-vector table).

Besides providing the address of the interrupt-service routine, each IDT entry contains a descriptor privilege level (DPL) which is compared against the current privilege level (CPL) of code attempting to invoke an interrupt handler via the INT n instruction. INTM initializes the DPL of all IDT entries to 0. Thus, when DOS, the BIOS, or other code running within the V86 task (which executes with CPL=3) attempts to execute an INT n instruction, a general-protection exception (interrupt 0Dh) is generated.

At that point, INTM detects that a software instruction caused the exception and routes the interrupt to the pass_thru routine for processing.

Controlling Logging/Breaking

The logging of interrupts and the generation of breakpoints is controlled by the int_struc structure (See Listing Two, page 98). The log_each and brk_each structure members are bitmaps containing one bit for each interrupt to be logged or for which breakpoints are to be generated. These actions are enabled for given interrupts by setting the corresponding bits. The logging and breakpoint-generation functions can be globally enabled or disabled by setting enable_log and enable_brk True or False. brk_action specifies the breakpoint interrupt to be generated when any of the interrupts indicated in brk_each occur. int_now always holds the number of the interrupt which is currently being serviced. This can be used if you choose to create your own interrupt monitoring program, which gets control via the brk_action interrupt, when specified interrupts occur.

I've provided an interface via user-interrupt 61h, with four functions to let you communicate with INTM and set the desired values in the int_struc area (see user_int_isr, INTISR.ASM, available electronically).

Function 0 returns a pointer to the logging buffer. The pointer can be used to retrieve the logging information. The program, INTDISP.C (available electronically) demonstrates how to use this function. Function 1 can be used to directly control interrupt logging. It returns a pointer to the int_struc area which can be used to enable/disable interrupts, and so on.

Functions 2 and 3 can also be used to enable/disable the desired logging options. The program, INTEXAMP.C (also available electronically) illustrates this method of configuring INTM.

Conclusion

The capability of monitoring interrupt activity and breaking on the occurrence of interrupts can be an effective means of tracking down software problems. You may even discover some activity taking place in your system that you didn't know existed.

References

80386 Programmer's Reference Manual. Santa Clara, CA: Intel Corp., 1986

Green, Thomas. "80386 Protected Mode and Multitasking." Dr. Dobb's Journal (September, 1989)

Knoblaugh, Rick. "Your Own Protected-Mode Debugger." Dr. Dobb's Journal (September, 1992)

Margulis, Neil. "Advanced 80386 Memory Management." Dr. Dobb's Journal (April, 1989)

Turley, James L. Advanced 80386 Programming Techniques. Berkeley, CA: Osborne/McGraw-Hill, 1988.

Williams, Al. "Homegrown Debugging--386 Style!" Dr. Dobb's Journal (March 1990)

Williams, Al. "Roll Your Own DOS Extender: Part II." Dr. Dobb's Journal (November, 1990)



[LISTING ONE]

;---------------------------------------------------------------
;intinit - main module for Int Monitor                         |
;--------------------------------------------------------------|
;Copyright 1991, 1993 ASMicro Co.                              |
;--------------------------------------------------------------|
; 09/25/93                      Rick Knoblaugh                 |
;--------------------------------------------------------------|
;include files                                                 |
;---------------------------------------------------------------
.386P
                include intequ.inc
                include intstruc.inc
                include intmac.inc
                include intdat.inc

;--------------------------------------------------------------
;EXTERNALS                                                    |
;--------------------------------------------------------------
isrcode         segment para public 'icode16' use16
                extrn   int_0:far
                extrn   int_1:far
                extrn   int_2:far
                extrn   int_3:far
                extrn   int_4:far
                extrn   int_5:far
                extrn   int_6:far
                extrn   int_7:far
                extrn   except_8:far
                extrn   except_9:far
                extrn   except_0ah:far
                extrn   except_0bh:far
                extrn   except_0ch:far
                extrn   except_0dh:far
                extrn   except_0eh:far
                extrn   except_0fh:far
                extrn   int_20h:far
                extrn   int_21h:far
                extrn   int_22h:far
                extrn   int_23h:far
                extrn   int_24h:far
                extrn   int_25h:far
                extrn   int_26h:far
                extrn   int_27h:far
                extrn   int_70h:far
                extrn   int_71h:far
                extrn   int_72h:far
                extrn   int_73h:far
                extrn   int_74h:far
                extrn   int_75h:far
                extrn   int_76h:far
                extrn   int_77h:far
                extrn   user_int_isr:far
isrcode         ends

                assume cs:code, ds:nothing, es:nothing


code            segment para public 'code16' use16
                assume cs:code, ds:data, es:data
                .8086
start           proc    far
                push    ds              ;save psp seg
                mov     ax, data
                mov     ds, ax
                mov     es, ax
                call    verify_cpu
                jnc     start_200       ;continue if 386/486 in real mode


start_050:
                mov     ah, DOS_PRT_STRING
                int     21h
                mov     ax, 4c01h
                int     21h
.386P
start_200:
                call    setup_ints      ;take over user int

                call    init_gdt
                call    init_tss

                mov     ax, data
                mov     ds, ax
                assume  ds:data


                cli                     ;no ints until protected mode

                mov     ax, gdt_seg
                movzx   eax, ax
                shl     eax,  4
                mov     gdtadrs, eax

                mov     ax, idt_seg
                movzx   eax, ax
                shl     eax,  4
                mov     idtadrs, eax


                call    reprogram_pic


                lgdt    gdtl
                lidt    idtl

                mov     dx, iostack3    ;get stack segment
                mov     bx, sp          ;and pointer
                mov     eax, cr0
                or      eax, 1          ;turn on protected mode bit
                mov     cr0, eax        ;go into protected mode

;
;jump to clear prefetch queue
;
                db      0eah            ;far jump
                dw      offset code:start_400
                dw      gdt_seg:sel_code
start_400:
                mov     ax, offset gdt_seg:sel_tss
                ltr     ax
                xor     ax, ax
                lldt    ax              ;null ldt
                mov     ax, seg data
                movzx   eax, ax
                push    eax             ;gs
                push    eax             ;fs
                push    eax             ;ds
                push    eax             ;es
                xor     ax, ax

                push    ax
                push    dx              ;stack segment
                push    ax
                push    bx              ;stack pointer

                push    2               ;VM bit set in upper eflags
                push    3000h           ;NT=0, IOPL=3, CLI in lower eflags
                push    ax
                push    seg code        ;cs of where to return
                push    ax
                push    offset code:start_500  ;ip of where to return
;
;Must ensure that Nested Task bit is not set in eflags.  If it were,
;processor would attempt to switch to a task via the selector in
;the TSS backlink field.  Since that field is now zero, an invalid TSS
;fault would occur.
;
                pushf
                pop     ax
                and     ax, NOT NT_FLAG
                push    ax
                popf
                iretd

start_500:                              ;begin vm86 task here
                pop     bx              ;get saved psp seg
                sti                     ;interrupts ok now
                mov     dx, code + 1    ;init code we are dropping
                sub     dx, bx
                mov     ax, (DOS_TSR_FUNC shl 8)
                int     21h

start           endp



reprogram_pic   proc    near
                in      al, 21h
                mov     ah, al
                mov     al, 11h         ;init
                out     20h, al
                mov     al, 20h         ;irq0 to int 20h
                out     21h, al
                jmp     short $ + 2
                jmp     short $ + 2
                mov     al, 4
                out     21h, al
                jmp     short $ + 2
                jmp     short $ + 2
                mov     al, 1
                out     21h, al
                jmp     short $ + 2
                jmp     short $ + 2
                mov     al, ah
                out     21h, al
                ret
reprogram_pic   endp

.8086

verify_cpu      proc    near
                xor     ax, ax
                push    ax
                popf
                pushf
                pop     ax
                and     ax, 0f000h
                cmp     ax, 0f000h
                jz      verify_c800     ;not 386
                mov     ax, 0f000h
                push    ax
                popf
                pushf
                pop     ax
                and     ax, 0f000h
                jz      verify_c800     ;not 386
                mov     dx, offset noprot_msg
.386P
                smsw    ax              ;get pm flag into carry
                rcr     ax, 1
                jmp     short verify_c999
verify_c800:
                mov     dx, offset not386_msg
                stc
verify_c999:
                ret
verify_cpu      endp




setup_ints      proc    near
                mov     bx, USER_INT
                mov     di, offset old_user_int
                mov     cx, isrcode
                mov     dx, offset isrcode:user_int_isr
                call    get_int
                ret
setup_ints      endp

;--------------------------------------------------------------
;get_int - For a given interrupt vector, store contents and   |
;          load with new isr address.                         |
;                                                             |
;          bx = int number                                    |
;          es:di = location to store contents                 |
;          dx = offset new isr                                |
;          cx = cs of new isr
;--------------------------------------------------------------
get_int         proc    near
                cld
                push    ds
                xor     ax, ax
                mov     ds, ax
                shl     bx, 2
                mov     ax, [bx]
                stosw
                mov     ax, [bx].d_segment
                stosw
                cli
                mov     [bx].d_offset, dx
                mov     [bx].d_segment, cx
                sti
                pop     ds
                ret
get_int         endp



init_gdt        proc    near
                mov     ax, gdt_seg
                mov     ds, ax
                assume  ds:gdt_seg

                mov     dx, tss_seg
                movzx   edx, dx                 ;base data segment
                mov     ecx, (TSS_END - TSS_BEG  ) - 1  ;limit
                mov     ah, TSS_DESC
                mov     si, offset sel_tss
                call    make_entry

                mov     dx, tss_seg
                movzx   edx, dx                 ;base data segment
                mov     ecx, (TSS_END - TSS_BEG  ) - 1  ;limit
                mov     ah, RW_DATA             ;alias as r/w for editing
                mov     si, offset sel_tss_alias
                call    make_entry

                mov     dx, gdt_seg
                movzx   edx, dx                 ;base data segment
                mov     ecx, (GDT_END - GDT_BEG  ) - 1  ;limit
                mov     ah, RW_DATA             ;alias as r/w for editing
                mov     si, offset sel_gdt_alias
                call    make_entry

                mov     dx, isrcode
                movzx   edx, dx                 ;base of isr code segment
                mov     ecx, 0ffffh             ;max segment size
                mov     ah, ER_CODE
                mov     si, offset sel_isrcode
                call    make_entry

                mov     dx, code
                movzx   edx, dx                 ;base code segment
                mov     ecx, 0ffffh             ;max segment size
                mov     ah, ER_CODE
                mov     si, offset sel_code
                call    make_entry

                xor     edx, edx                ;zero base
                mov     ecx, 8fffffh            ;page granularity and 4 gig limit
                mov     ah, RW_DATA
                mov     si, offset sel_databs
                call    make_entry

                mov     dx, iostack
                movzx   edx, dx                 ;base stack segment
                mov     ecx, (STACK_END - STACK_BEG  ) - 1  ;limit
                mov     ah, RW_DATA
                mov     si, offset sel_stack
                call    make_entry

                mov     dx, data
                movzx   edx, dx                 ;base data segment
                mov     ecx, (DATA_END - DATA_BEG  ) - 1  ;limit
                mov     ah, RW_DATA
                mov     si, offset sel_data
                call    make_entry

                int     11h                     ;equipment check
                mov     edx, 0b800h             ;color segment
                and     al, 30h                 ;monitor bits
                cmp     al, 30h                 ;30h=monochrome
                jne     init_gdt500
                mov     edx, 0b000h             ;monochrome segment

init_gdt500:
                mov     ecx, VID_PAGE_SIZE - 1  ;page size - 1
                mov     ah, RW_DATA
                mov     si, offset sel_video
                call    make_entry

                ret
init_gdt        endp

;--------------------------------------------------------------
;make_entry - Load a GDT entry from information passed as     |
;             follows:                                        |
;                                                             |
;             ds=gdt segent                                   |
;             si=offset of gdt entry to load                  |
;             ah=type | dpl                                   |
;            ecx=limit (also, bits 23:20 are g, b, 0, and avl)|
;            edx=base segment (convert it to linear)          |
;--------------------------------------------------------------
make_entry      proc    near
                shl     edx, 4                  ;convert seg to linear
                mov     [si].seg_limit_low, cx
                mov     [si].seg_base_low, dx
                shr     edx, 16
                mov     [si].seg_base_mid, dl
                mov     [si].seg_type_dpl, ah
                shr     ecx, 16
                mov     [si].seg_limit_gran, cl
                mov     [si].seg_base_top, dh

                ret
make_entry      endp

;--------------------------------------------------------------
;init_tss - Initilize TSS with PL0 stack and io bit map base. |
;--------------------------------------------------------------
init_tss        proc    near
                mov     ax, tss_seg
                mov     ds, ax
                assume  ds:tss_seg
                xor     si, si

                mov     ax, offset gdt_seg:sel_stack
                mov     [si].t_ess0, ax
                mov     ax, offset iostack:io_sp
                movzx   eax, ax
                mov     [si].t_esp0, eax

                lea     bx, [si].t_iomap
                mov     [si].t_iobase, bx
                ret
init_tss        endp


code            ends
                end  start

[LISTING TWO]

;---------------------------------------------------------------
;intstruc.inc - structures for Int monitor                     |
;--------------------------------------------------------------|

idt             struc
i_offset        dw      ?
i_selector      dw      ?
i_unused        db      0
i_dpl_id        db      PRESENT + (DPL0 shl 5) + INT_GATE
i_offset2       dw      0
idt             ends

seg_descrip     struc
seg_limit_low   dw      ?
seg_base_low    dw      ?
seg_base_mid    db      ?
seg_type_dpl    db      ?
seg_limit_gran  db      ?
seg_base_top    db      ?
seg_descrip     ends

err_stack_area  struc                   ;stack with error code
e_pushed_int    dw      ?               ;int pushed by int monitor
e_pushed_bp     dw      ?               ;bp pushed by int monitor
e_errcode       dd      ?               ;error code
e_eip           dd      ?
e_cs            dw      ?
                dw      ?
e_eflags        dd      ?
e_esp           dd      ?
e_ss            dw      ?
                dw      ?
e_es            dw      ?
                dw      ?
e_ds            dw      ?
                dw      ?
e_fs            dw      ?
                dw      ?
e_gs            dw      ?
                dw      ?
err_stack_area  ends

stack_area      struc                   ;stack without error code
s_pushed_int    dw      ?               ;int pushed by I/O monitor
s_pushed_bp     dw      ?               ;bp pushed by I/O monitor
s_eip           dd      ?
s_cs            dw      ?
                dw      ?
s_eflags        dd      ?
s_esp           dd      ?
s_ss            dw      ?
                dw      ?
s_es            dw      ?
                dw      ?
s_ds            dw      ?
                dw      ?
s_fs            dw      ?
                dw      ?
s_gs            dw      ?
                dw      ?
stack_area      ends

user_stack      struc
user_ip         dw      ?
user_cs         dw      ?
user_flags      dw      ?
user_stack      ends

doub_word       struc
d_offset        dw      ?
d_segment       dw      ?
doub_word       ends

buf_record      struc                   ;format of logged data
buf_int         db      ?
buf_ax          dw      ?
buf_record      ends


int_struc       struc
enable_log      db      TRUE
int_now         db      0       ;interrupt that is occuring right now
log_each        db      (MAX_INTS/8) dup(0) ;bit on/off indicating to log
enable_break    db      TRUE
brk_each        db      (MAX_INTS/8) dup(0) ;bit on/off indicating to break
brk_action      db      0     ;int number for ALL break point
values_ax       dw      MAX_INTS dup((DONT_COMP shl 8) OR DONT_COMP)
int_struc       ends
tss_dat         struc
t_backlink      dw      ?
                dw      ?
t_esp0          dd      ?
t_ess0          dw      ?
                dw      ?
t_esp1          dd      ?
t_ess1          dw      ?
                dw      ?
t_esp2          dd      ?
t_ess2          dw      ?
                dw      ?
t_cr3           dd      ?
t_eip           dd      ?
t_eflags        dd      ?
t_eax           dd      ?
t_ecx           dd      ?
t_edx           dd      ?
t_ebx           dd      ?
t_esp           dd      ?
t_ebp           dd      ?
t_esi           dd      ?
t_edi           dd      ?
t_es            dw      ?
                dw      ?
t_cs            dw      ?
                dw      ?
t_ss            dw      ?
                dw      ?
t_ds            dw      ?
                dw      ?
t_fs            dw      ?
                dw      ?
t_gs            dw      ?
                dw      ?
t_ldt           dw      ?
                dw      ?
t_tbit          dw      ?
t_iobase        dw      ?
t_iomap         db      IO_MAP_SIZE     dup(0)
t_iopad         dw      0ffh            ;follow last map byte with 0ffh
tss_dat         ends

ex_mov_data     struc
ex_mdum         db      8 dup(?)
ex_mdat         db      8 dup(?)
ex_msource      db      8 dup(?)
ex_mdest        db      8 dup(?)
ex_mcs          db      8 dup(?)
ex_mss          db      8 dup(?)
ex_mov_data     ends



flag_bits       record  fill0:14, vmbit:1, resumef:1, fill1:1, nest_taskf:1,\
                        iopl:2, overf:1, direc:1, inter:1, trapf:1, sign:1, \
                        zero:1, fill3:1, auxcarry:1, fill4:1, parity:1, \
                        fill5:1, carry:1




;------end of intstruc.inc


Copyright © 1993, Dr. Dobb's Journal