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.
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.
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.
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.
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.
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