Rick is a software engineer specializing in systems programming. He is a frequent contributor to various computer publications and can be reached on CompuServe at 71020,2034.
One of the useful protection mechanisms built into the 80386 processor is the ability to restrict I/O accesses. Operating systems can utilize this to limit accesses to critical system resources, simulate hardware devices, and handle device contention between processes.
The Windows Virtual Machine Manager (VMM) supports this by making services available to virtual device drivers (VxDs), which facilitate the trapping and processing of I/O-port accesses. These services also make it possible to write a VxD which simply "listens in" on I/O activity initiated by less-privileged code. This can effectively track down problems with hardware, firmware, and drivers, as well as offer insight into just what is taking place in your system.
Only one VxD can trap a given I/O port, and that trapping can be enabled and disabled on the fly. Thus, it would normally not be possible to eavesdrop on some of the more-interesting I/O ranges, such as COM ports, because these are trapped by existing Windows drivers. However, the driver I'll present here, VRKIOMON.386, gets around this by hooking the VMM services for installing I/O handlers and enabling and disabling I/O trapping. This also lets you use VRKIOMON to verify that given VxDs are correctly virtualizing I/O ports. I'll briefly review how I/O trapping works, then delve into the services the VMM provides to support this. Finally, I'll highlight the inner workings of VRKIOMON that make use of these services.
The key to restricting I/O accesses is the I/O-permission bitmap located at the end of the task-state segment (TSS). This bitmap specifies which I/O addresses a task may access. If a task's current privilege level (CPL) is less privileged than its I/O privilege level (IOPL), the bitmap is consulted before allowing access to a given port. In V86 mode, the bitmap is always checked.
If the bit corresponding to the given port is set, a general-protection exception is generated. The Ring 0 virtual-machine monitor code must then inspect the bytes of code that attempted the instruction to ascertain the particular port and the type of I/O instruction. This involves determining if the I/O port is an immediate value within the instruction, or if the instruction is a string instruction (in which case the direction flag must be checked, and so on). Fortunately, under Windows, the VMM handles this and more.
To receive control when a given port is accessed, VxDs can call the Install_IO_Handler VMM service, specifying the port and the address of a callback routine. If multiple ports are being trapped, you can use Install_Mult_IO_Handlers (which will, in turn, call Install_IO_Handler for each port specified). When subsequent I/O accesses occur within Ring 3 code, a general-protection exception is generated, and the particular I/O instruction is decoded. The VxD's callback routine is called with register values indicating the type of I/O, the port, the VM handle, any output data, and a pointer to the client-register structure. The iomon_trap procedure in Listing One is an example of such a callback routine.
As if providing this useful information to your callback routine weren't enough, the VMM can go to even greater lengths to serve you. If, for example, the I/O access is via a string instruction, and getting every ounce of performance isn't an issue, you can avoid emulating it by using the Simulate_IO service. Jumping to Simulate_IO causes the VMM to enter your callback multiple times, breaking up the complex string instruction into individual in or out instructions. It will adjust all client registers appropriately for you.
Once I/O ports are trapped, VxDs can use VMM services to enable and disable the trapping on a global basis or per virtual machine. These services are useful in managing device contention. For example, by allowing only one VM to access a given hardware device at a time, a VxD can disable trapping for the VM that currently owns the device and reenable the trapping when the device is released.
Typically, I/O handlers are installed during the Device_Init phase of VxD initialization, but a few may do so during Sys_Critical_Init. To ensure that its I/O handlers get installed first, VRKIOMON uses an initialization order of VMM_INIT_ORDER+1 and performs I/O-handler installation during Sys_Critical_Init. During this initialization phase, VRKIOMON also hooks the Install_IO_Handler service and the services for enabling and disabling trapping; see hook_enab_dis in Listing One.
When subsequent calls are made to Install_IO_Handler by other VxDs, VRKIOMON's version of the service, iomon_iohand, passes through requests that don't involve monitored ports. For those that are monitored, it stores the requested callback address and returns a status indicating success (provided that it hasn't already seen a request for the particular port). Thus, VRKIOMON is called back when all monitored I/O ports are trapped. If another VxD has not requested a callback for the port, VRKIOMON performs the I/O and stores the data. If ports are trapped by other VxDs, VRKIOMON calls their callback routines and stores data that is read or written by those routines. The code that handles this (iomon_trap) has to deal with the possibility that the other callback may jump to the Simulate_IO service, or that the other VxD has disabled I/O trapping.
VRKIOMON provides an API for DOS or Windows applications to allow the logged data to be retrieved and displayed. (See the API_call table in Listing One for a list of API functions.) Since one of my goals was to make the logged data available to a DOS utility, the logging buffer is allocated in V86 address space using the _Allocate_Global_V86_Data_Area service.
Since VRKIOMON is not a full-blown commercial product (with all the extensive QA that goes with it), I strongly recommend you only run it on a test system. Also, if you use VRKIOMON to monitor the hard-disk I/O (1f0 through 1f7), be sure to use a test platform! In this case, set 32BitDiskAccess=off in your SYSTEM.INI file. If you monitor 1f1, you must also monitor 1f0 (1f1 by itself will still cause a trap when word accesses are performed at 1f0).
To install the I/O monitor, copy VRKIOMON.386 to your \windows\system directory and make the additions shown in Figure 1 to your SYSTEM.INI file. In addition to adding device=vrkiomon.386 to SYSTEM.INI, you can specify several parameters (see Table 1) and up to ten ranges (VIOBEG0/VIOEND0 through VIOBEG9/VIOEND9) for a total of 64 ports (default value of MAX_PORTS equates).
To display the results, go into a DOS box and run IODISP (Listing Two), which reports I/O activity information to the screen. Output can also be redirected to a file (for example, IODISP > myfile). The same API used by IODISP is also available to Windows applications. Finally, if you need to isolate a particular activity (which could be lost when the logging buffer wraps), use the API for initializing the buffer and controlling wrapping.
The ability to monitor I/O ports can offer insights when debugging or studying system activity. The API provided by this VxD can add this aspect to other analysis tools you may be using. For example, my DOS device-driver monitor (see "Device Driver Monitoring," DDJ, March 1992) could use this to report the commands output to a controller as the result of a driver receiving a particular request. In the future, I may also provide an object-oriented I/O-logging buffer-analysis tool that will report activity in more high-level terms.
80386 Programmer's Reference Manual. Santa Clara, CA: Intel Corp., 1986.
Thielen, David and Bryan Woodruff. Writing Windows Virtual Device Drivers. Reading, MA: Addison-Wesley, 1993.
[386Enh]
device=vrkiomon.386 <-- Create entry for I/O Monitor driver.
VIOBUF=6 <-- Number of 4K pages for buffer.
VIOBEG0=378 <-- Specify ranges you want to listen to.
VIOEND0=37f You can have up to 10 ranges
VIOBEG0--VIOEND0, VIOBEG1--VIOEND1, and so on.
maximum of 64 ports may be trapped The example
shown here specifies the ports for LPT1.
Parameter Description
VIOBUF=nnn Where nnn is the number of 4K pages to be allocated for
the logging buffer.
VIOBEG0=nnnn Beginning I/O address to be monitored (first range).
VIOEND0=nnnn Ending I/O address (first range).
.
.
.
VIOBEG9=nnnn Beginning I/O address to be monitored (last range).
VIOEND9=nnnn Ending I/O address (last range).
;---------------------------------------------------------------
;vriomon.asm - I/O Monitoring VxD |
;Copyright 1994 ASMicro Co. |
;01/09/94 Rick Knoblaugh |
;|
.386p
;----- include files |
include vmm.inc
include debug.inc
;------ equates -----------------------------------------------|
VRIOMON_VER_HI EQU 1
VRIOMON_VER_LO EQU 0
MAX_PORTS EQU 64 ;increase to log more
MAX_RANGES EQU 10 ;VIOBEGn VIOENDn (n < MAX_RANGES)
MAX_VM_TRACKED EQU 1fh ;track I/O for this many VMs
; VxD ID assigned by vxdid@microsoft.com
VRIOMON_Dev_ID EQU 317eh
;----- structures ---------------------------------------------|
buf_record struc ;format of logged data
buf_info db ?
buf_port dw ?
buf_data db ?
buf_record ends
doub_word struc
d_offset dw ?
d_segment dw ?
doub_word ends
port_info struc
vrio_port dw ? ;port number
vrio_callb dd 0 ;callback of other trapper
;(if any, zero if none)
vio_enab_flags dd 0 ;bitmap enable/disable status
port_info ends
get_buf_info struc
buf_beg_ptr dd ?
buf_data_end dd ?
buf_size dd ?
buf_flags db ?
get_buf_info ends
enab_disab_flag record glob_io_bit:1, local_ios:31
wrap_flag_bits record yo_unused:6, dont_wrap:1, it_wrapped:1
flag_bits record fill0:14, vmbit:1, resumef:1, fill1:1, nest_taskf:1,\
iopl:2, overf:1, direcf:1, inter:1, trapf:1, sign:1, \
zero:1, fill3:1, auxcarry:1, fill4:1, parity:1, \
fill5:1, carry:1
;---------------------------------------------------------------
; Virtual Device Decalaration |
;---------------------------------------------------------------
Declare_Virtual_Device VRKIOMON, VRIOMON_VER_HI , VRIOMON_VER_LO,
VRIOMON_Control, VRIOMON_Dev_ID, \
VMM_Init_Order, API_handler, API_handler
;---------------------------------------------------------------
; Local Data |
;---------------------------------------------------------------
VxD_LOCKED_DATA_SEG
buffer_beg_ptr dd 0
buffer_end_ptr dd 0
buffer_cnt_ptr dd 0
buffer_wrk_ptr dd 0
the_vmm_iohand dd 0
old_glob_disab dd 0
old_loc_disab dd 0
old_glob_enab dd 0
old_loc_enab dd 0
in_process_cnt dw 0
buf_capacity dd 0
buf_wrap_flag db 0
number_ports dw 0
hold_string_info dw 0
hold_string_ptr dd 0
hold_string_cnt dw 0
port_data port_info MAX_PORTS dup(<>)
API_call label dword
dd offset32 VxDversion
dd offset32 VxDget_bufptr
dd offset32 VxDinit_buf
MAX_API_CALLS EQU ($ - API_call)/ size doub_word
VxD_LOCKED_DATA_ENDS
;---------------------------------------------------------------
; Initialization Data |
;---------------------------------------------------------------
VxD_IDATA_SEG
Viomon_Buf_String db 'VIOBUF',0
Viomon_Beg_String db 'VIOBEG0',0
Viomon_End_String db 'VIOEND0',0
CNT_POSITION EQU ($ - Viomon_End_String) - 2
VxD_IDATA_ENDS
;---------------------------------------------------------------
; Initialization Code |
;---------------------------------------------------------------
VxD_ICODE_SEG
;--------------------------------------------------------------
;VRIOMON_Crit_Init - Trap all the ports specified by the |
; parms in SYSTEM.INI. Look for values |
; specifying up to MAX_RANGES ranges |
; (e.g. |
; VIOBEG0=xxxx |
; VIOEND0=xxxx |
; VIOBEG1=xxxx |
; VIOEND1=xxxx |
; ... ). |
; Also, we need to hook VMM services for |
; Install_IO_Handler, Disable_Local_Trapping |
; Enable_Local_Trapping, Disable_Global_Trapping,|
; and Enable_Global_Trapping. This allows us |
; to continue to listen in on port activity |
; even when another VxD has trapped the same |
; port or disabled the trapping. |
; Enter: |
; Exit: |
; port_data = filled with ports we're trapping|
; if unable to trap ports or hook services |
; carry is set. |
;--------------------------------------------------------------
BeginProc VRIOMON_Crit_Init
xor ecx, ecx ;range counter
xor eax, eax
mov ebx, OFFSET32 port_data ;area to store values
VRIOMON_Crit_I025:
mov edi, OFFSET32 Viomon_Beg_String
xor esi, esi ;[386enh] section
VMMCall Get_Profile_Hex_Int ;get begin range
jz short VRIOMON_Crit_I050 ;if no value
jnc short VRIOMON_Crit_I100 ;if found
VRIOMON_Crit_I050:
cmp cx, (MAX_RANGES - 1) ;end of ranges?
je short VRIOMON_Crit_I400
jmp short VRIOMON_Crit_I300 ;try another range
VRIOMON_Crit_I100:
mov dx, ax ;save range start
mov edi, OFFSET32 Viomon_End_String
xor esi, esi ;[386enh] section
VMMCall Get_Profile_Hex_Int ;get end range
jc short VRIOMON_Crit_I150 ;if not found
jz short VRIOMON_Crit_I150 ;or no value
cmp ax, dx ;cmp with begin
jae short VRIOMON_Crit_I200 ;if valid range
VRIOMON_Crit_I150:
IFDEF DEBUG
Trace_Out "VRKIOMON: Invalid range specifed in SYSTEM.INI"
ENDIF
VRIOMON_Crit_I180:
jcxz VRIOMON_Crit_I800 ;exit if none at all
jmp short VRIOMON_Crit_I400 ;go trap valid ports
VRIOMON_Crit_I200:
push ecx ;save range count
call store_ports
pop ecx ;restore range count
jc short VRIOMON_Crit_I180
;Change "VIOBEG1" to "VIOBEG2", etc.
VRIOMON_Crit_I300:
inc [Viomon_Beg_String + CNT_POSITION]
inc [Viomon_End_String + CNT_POSITION]
inc cx ;next range
cmp cx, MAX_RANGES ;done all?
jne VRIOMON_Crit_I025 ;if not, get more
VRIOMON_Crit_I400:
; hook the ports to watch
movzx ecx, number_ports
jcxz VRIOMON_Crit_I800 ;if no ports
mov ebx, OFFSET32 port_data ;area to store values
mov esi, OFFSET32 iomon_trap ;address of our handler
VRIOMON_Crit_I450:
movzx edx, [ebx].vrio_port ;port number
VMMCall Install_IO_Handler
jnc short VRIOMON_Crit_I500 ;if trapped ok
IFDEF DEBUG
Trace_Out "VRKIOMON: Unable to trap port #EDX"
ENDIF
VRIOMON_Crit_I500:
add ebx, size port_info ;get port entry
loop VRIOMON_Crit_I450 ;go do next port
mov eax, Install_IO_Handler ;hook I/O
mov esi, OFFSET32 iomon_iohand ;trap install
VMMCall Hook_Device_Service
jnc short VRIOMON_Crit_I550 ;if trapped ok
IFDEF DEBUG
Trace_Out "VRKIOMON: Unable to hook Install_IO_Handler"
ENDIF
;
VRIOMON_Crit_I550:
mov the_vmm_iohand, esi
call hook_enab_dis
ret
VRIOMON_Crit_I800:
;No valid ports specified
IFDEF DEBUG
Trace_Out "VRKIOMON: No valid ports specified in SYSTEM.INI"
ENDIF
stc
ret
EndProc VRIOMON_Crit_Init
BeginProc hook_enab_dis
mov eax, Disable_Global_Trapping
mov esi, OFFSET32 iomon_glob_dis
VMMCall Hook_Device_Service
jc short hook_enab_dis_575
mov old_glob_disab, esi
hook_enab_dis_575:
mov eax, Disable_Local_Trapping
mov esi, OFFSET32 iomon_loc_dis
VMMCall Hook_Device_Service
jc short hook_enab_dis_600
mov old_loc_disab, esi
hook_enab_dis_600:
mov eax, Enable_Local_Trapping
mov esi, OFFSET32 iomon_loc_enab
VMMCall Hook_Device_Service
jc short hook_enab_dis_625
mov old_loc_enab, esi
hook_enab_dis_625:
mov eax, Enable_Global_Trapping
mov esi, OFFSET32 iomon_glob_enab
VMMCall Hook_Device_Service
jc short hook_enab_dis_650
mov old_glob_enab, esi
hook_enab_dis_650:
ret
EndProc hook_enab_dis
;--------------------------------------------------------------
;store_ports - Store ports for I/O trapping. If ports |
; have not previously been specified, store |
; the values. |
; Enter: |
; ebx = ptr to next record for storing |
; port numbers |
; ecx = range number being processed |
; dx = start of range of I/O ports |
; (range has been validated) |
; ax = end of range |
; number_ports = count of ports trapped so far |
; Exit: |
; ebx = advanced to next record |
; number_ports = updated count of ports |
; If error, return with carry set |
; esi, ax, cx, dx trashed |
;--------------------------------------------------------------
BeginProc store_ports
mov esi, OFFSET32 port_data ;area to store values
movzx ecx, number_ports
jcxz store_p250 ;if none stored
store_p100:
cmp [esi].vrio_port, ax ;below end of range?
ja short store_p200 ;if not, not a dup
cmp [esi].vrio_port, dx ;below start range?
jb short store_p200 ;if so, not a duplicate
IFDEF DEBUG
Trace_Out "VRKIOMON: Overlapping ranges specified in SYSTEM.INI"
ENDIF
stc
ret
store_p200:
add esi, size port_info
loop store_p100
store_p250:
movzx ecx, ax ;end of range
inc cx
sub cx, dx ;number in range
mov ax, number_ports
add ax, cx ;get number of ports
cmp ax, MAX_PORTS ;cmp with max supported
jna short store_p275
mov ax, MAX_PORTS
IFDEF DEBUG
Trace_Out "VRKIOMON: Too many ports specified in SYSTEM.INI (max is #ax)"
ENDIF
stc
ret
store_p275:
mov number_ports, ax ;add in to total
store_p300:
mov [ebx].vrio_port, dx ;store port
inc dx ;next port in range
add ebx, size port_info
loop store_p300
store_p500:
clc
ret
EndProc store_ports
;--------------------------------------------------------------
;VRIOMON_Device_Init - Retrieve buffer size parm from |
; SYSTEM.INI and allocate the buffer |
; within the global v86 data area. |
; Enter: |
; Exit: |
; buffer_beg_ptr = start of buffer |
; buffer_wrk_ptr = start of buffer |
; buffer_end_ptr = end of buffer |
; If error, return with carry set |
;--------------------------------------------------------------
BeginProc VRIOMON_Device_Init
mov edi, OFFSET32 Viomon_Buf_String
xor esi, esi ;[386enh] section
VMMCall Get_Profile_Hex_Int ;get buffer size
and eax, 0ffh ;Get # of 4k
jnz short VRIOMON_D100 ;if legal value
mov al, 2 ;else default to 2
VRIOMON_D100:
mov cl, 12 ; * 4K
shl eax, cl
mov ecx, eax ;save size in bytes
mov buf_capacity, eax
push ecx ;save size
VMMcall _Allocate_Global_V86_Data_Area, <eax, GVDAZeroInit>
pop ecx
or eax, eax ;got the memory?
jnz short VRIOMON_D200 ;if so, continue
IFDEF DEBUG
Trace_Out "VRKIOMON: Unable to allocate #CX bytes"
ENDIF
stc
ret
VRIOMON_D200:
mov buffer_beg_ptr, eax
mov buffer_wrk_ptr, eax
add eax, ecx
sub eax, ((size buf_record) + (size doub_word) )
mov buffer_end_ptr, eax
clc
ret
EndProc VRIOMON_Device_Init
VxD_ICODE_ENDS
;---------------------------------------------------------------
; Locked Code |
;---------------------------------------------------------------
VxD_LOCKED_CODE_SEG
BeginProc VRIOMON_Control
Control_Dispatch Sys_Critical_Init, VRIOMON_Crit_Init
Control_Dispatch Device_Init, VRIOMON_Device_Init
clc
ret
EndProc VRIOMON_Control
VxD_LOCKED_CODE_ENDS
;---------------------------------------------------------------
; Code Segment |
;---------------------------------------------------------------
VxD_CODE_SEG
;--------------------------------------------------------------------
;API_handler - API handler for both V86 and protected mode callers |
; Enter: |
; caller's ax = API function |
; Exit: |
; caller's CY set if invalid function |
;--------------------------------------------------------------------
BeginProc API_handler
movzx eax, [ebp].Client_AX
cmp ax, MAX_API_CALLS ;valid function?
ja short API_hand_900
and [ebp.Client_EFlags], not (mask carry) ;success
call API_call[eax * 4]
ret
API_hand_900:
or [ebp.Client_EFlags], (mask carry) ;error
ret
EndProc API_handler
BeginProc VxDversion
mov [ebp.Client_AX], ((VRIOMON_VER_HI shl 8) or VRIOMON_VER_LO)
ret
EndProc VxDversion
BeginProc VxDget_bufptr
;First, get ptr to caller's structure to be filled with ptrs to
;the buffer, size of buffer, and indication of whether it has wrapped.
mov ax, (Client_BX shl 8) + Client_DX
VMMcall Map_Flat
cmp eax, -1 ;error?
je short VxDget_b400
mov esi, eax ;32 bit ptr to caller data
mov edx, buffer_wrk_ptr
mov eax, buffer_beg_ptr
sub edx, eax ;get count of bytes used
mov [esi].buf_data_end, edx ;give it to caller
mov ecx, buf_capacity
dec ecx ;seg limit
add eax, [ebx.CB_High_Linear]
VMMcall Map_Lin_To_VM_Addr
jnc short VxDget_b500 ;if error
VxDget_b400:
bts [ebp].Client_EFlags, carry ;error
ret
VxDget_b500:
mov [esi].buf_beg_ptr.d_segment, cx
mov [esi].buf_beg_ptr.d_offset, dx
mov eax, buf_capacity
mov [esi].buf_size, eax
mov al, buf_wrap_flag
mov [esi].buf_flags, al
ret
EndProc VxDget_bufptr
;--------------------------------------------------------------
;VxDinit_buf - Handle ADP function for initializing logging |
; buffer. Reset buffer ptr to beginning. Also, |
; set flag per user option to wrap or not wrap |
; if end of buffer is reached. |
; Enter: |
; client dx = 1 if buffer should not wrap |
; Exit: |
; buffer_wrk_ptr = buffer_beg_ptr |
; buf_wrap_flag is updated. |
;--------------------------------------------------------------
BeginProc VxDinit_buf
cli
mov eax, buffer_beg_ptr ;reset to start
mov buffer_wrk_ptr, eax
mov buf_wrap_flag, 0 ;just started, no wrap
test [ebp.Client_DX], 1 ;want buf not to wrap?
jz short VxDinit_b999 ;if want wrap, exit
or buf_wrap_flag, (mask dont_wrap) ;set not to wrap
VxDinit_b999:
sti
ret
EndProc VxDinit_buf
;--------------------------------------------------------------
;iomon_trap - callback procedure for I/O port trapping. |
; Enter: |
; ebx = current VM handle |
; ecx = type of I/O |
; edx = port number |
; ebp = ptr to client reg struc |
; eax = output data |
; Exit: |
; eax = input data (if it's a read) |
;--------------------------------------------------------------
BeginProc iomon_trap, HIGH_FREQ
call ck_handler ;other trappers?
jz short iomon_trap040 ;if not, go do I/O
;If the other trapper calls simulate I/O (to break up string I/O), it will
;in turn repeatedly call this handler (for each byte, word, or dword).
;Thus, check to see that it isn't simulate I/O calling. If it is, simply
;jump to other trapper's callback routine. When all data has been
;tranferred, it will finally return from the call to the callback.
cmp in_process_cnt, 0 ;already processing?
ja short iomon_trap030
push ebx ;save VM handle
push ecx
push edx
inc in_process_cnt ;processing
test cl, String_IO ;string I/O ?
jz short iomon_trap010 ;if not, go do call
;If other Vxd truly processes string I/O, it will adjust client index
;registers, and rep count. Thus, use call below to save this information.
push eax
push ecx
call get_string_info
pop ecx
pop eax
jmp short iomon_trap012
iomon_trap010:
test cl, Output ;is this output?
jz short iomon_trap012 ;if not, go read
;Store the value to be written now --just in case it doesn't stay in ax.
call storedat
iomon_trap012:
call dword ptr [esi].vrio_callb
pop edx
pop ecx
pop ebx
test cl, String_IO ;string I/O ?
jz short iomon_trap025 ;if not, go do other
cld
test ecx, Reverse_IO ;insure direction flag
jz short iomon_trap015
std
iomon_trap015:
push edi
push ecx
mov di, cx ;info about string I/O
movzx ecx, hold_string_cnt ;rep count
mov esi, hold_string_ptr
cmp esi, -1 ;if for any reason address
je short iomon_trap020 ;was bad, can't store
call store_string_io ;store string data
iomon_trap020:
pop ecx
pop edi
jmp short iomon_trap999
iomon_trap025:
test cl, Output ;is this output?
jnz short iomon_trap999 ;if so, got it all
jmp short iomon_trap900 ;if read, go store
iomon_trap030:
inc in_process_cnt ;processing
jmp dword ptr [esi].vrio_callb
iomon_trap040:
test cl, String_IO ;string I/O ?
jz short iomon_trap050
call process_string ;go perform string I/O
jmp short iomon_trap999
iomon_trap050:
test cl, Output ;is this output?
jnz short iomon_trap500
test cl, Dword_IO ;size is dword?
jz short iomon_trap080
in eax, dx ;input a dword
jmp short iomon_trap900
iomon_trap080:
test cl, Word_IO ;size is word?
jnz short iomon_trap100
in al, dx ;input a byte
jmp short iomon_trap900
iomon_trap100:
in ax, dx ;input a word
iomon_trap200:
jmp short iomon_trap900
iomon_trap500:
test cl, Dword_IO ;size is dword?
jz short iomon_trap550
out dx, eax ;output a dword
jmp short iomon_trap900
iomon_trap550:
test cl, Word_IO ;size is word?
jnz short iomon_trap600
out dx, al
jmp short iomon_trap900
iomon_trap600:
out dx, ax
iomon_trap900:
cmp in_process_cnt, 1 ;simulate_IO ?
jbe short iomon_trap910 ;if not, go store
ret
iomon_trap910:
call storedat ;store the data
iomon_trap999:
mov in_process_cnt, 0 ;no longer processing
ret
EndProc iomon_trap
;--------------------------------------------------------------
;process_string - Perform the string I/O operation and update |
; client registers appropriately. |
; Enter: |
; ebx = current VM handle |
; ecx = type of I/O |
; bit 6 indicates if repeat |
; prefix is present |
; bit 8 indicates if the |
; direction flag is set |
; edx = port number |
; ebp = ptr to client reg struc |
; eax = output data |
; Exit: |
; Client registers updated. |
;--------------------------------------------------------------
BeginProc process_string, HIGH_FREQ
push eax
push ecx
push edi
push ebp
call get_string_info
cmp eax, -1 ;error getting address?
je process_s999
test hold_string_info, Rep_IO ;rep prefix?
jz short process_s100
mov [ebp.Client_CX], 0 ;if so, zero client's cx
process_s100:
mov edi, eax ;address for string data
mov esi, eax
mov ax, hold_string_info
push ecx ;save count
test al, Output ;outs?
jnz short process_s500
process_s150:
add ebp, Client_DI ;point to client di on stack
test al, Dword_IO ;dword I/O ?
jnz short process_s300
test al, Word_IO ;word I/O ?
jnz short process_s200
rep insb
jmp short process_s400
process_s200:
rep insw
jmp short process_s400
process_s300:
rep insd
process_s400:
jmp short process_s800 ;go store the data
process_s500:
add ebp, Client_SI ;point to client si on stack
test al, Dword_IO ;dword I/O ?
jnz short process_s700
test al, Word_IO ;word I/O ?
jnz short process_s600
rep outsb
jmp short process_s800
process_s600:
rep outsw
jmp short process_s800
process_s700:
rep outsd
process_s800:
pop eax ;get count
push eax
mov cx, hold_string_info
and cl, (Word_IO or Dword_IO)
shr cl, 3 ;convert to shift count
;(i.e. dword=2, word=1)
shl eax, cl ;adjust index by this amt
test hold_string_info, Reverse_IO ;direction flag set?
jz short process_s850
neg ax ;if so, subtract
process_s850:
add [ebp], ax ;adjust user index reg
process_s900:
pop ecx ;restore count
mov esi, hold_string_ptr
mov di, hold_string_info
call store_string_io
process_s999:
pop ebp
pop edi
pop ecx
pop eax
ret
EndProc process_string
;--------------------------------------------------------------
;store_string_io - Called by process_string and iomon_trap to |
; store string I/O data. |
; Enter: |
; cx = rep count |
; edx = port number |
; di = I/O type |
; esi = ptr to start of data that was |
; input or output via string I/O |
; Direction flag appropriately set or clear |
; Exit: |
;--------------------------------------------------------------
BeginProc store_string_io, HIGH_FREQ
store_s100:
push ecx ;save count
test di, Dword_IO ;dword I/O ?
jnz short store_s300
test di, Word_IO ;word I/O ?
jnz short store_s200
lodsb
jmp short store_s400
store_s200:
lodsw
jmp short store_s400
store_s300:
lodsd
store_s400:
mov cx, di
call storedat
pop ecx
loop store_s100
ret
EndProc store_string_io
;--------------------------------------------------------------
;get_string_info - Called by process_string and iomon_trap to |
; determine address for string I/O operation |
; and save away information. |
; Enter: |
; ecx = type of I/O |
; edx = port number |
; ebp = ptr to client reg struc |
; Exit: |
; eax = 32 bit address for I/O data |
; ecx = rep count (1 if no rep) |
; values also saved away: |
; hold_string_ptr = 32 bit address |
; hold_string_info = I/O type |
; hold_string_cnt = rep count |
;--------------------------------------------------------------
BeginProc get_string_info
mov ax, cx ;get I/O type
mov hold_string_info, ax ;save it also
mov ecx, 1 ;default to no rep count
test al, Rep_IO ;repeats?
jz short get_s100
movzx ecx, [ebp.Client_CX] ;if so, get the count
get_s100:
mov hold_string_cnt, cx
test al, Output ;outs?
jnz short get_s500 ;if so, go get ds:si ptr
mov ax, (Client_ES shl 8) + Client_DI
VMMcall Map_Flat
jmp short get_s999
get_s500:
mov ax, (Client_DS shl 8) + Client_SI
VMMcall Map_Flat
get_s999:
mov hold_string_ptr, eax ;save ptr to data
ret
EndProc get_string_info
;--------------------------------------------------------------
;storedat - Store the data into the buffer. Buffer records |
; have the following format: |
; buf_info db <- Tells size and direction of |
; of port access. |
; See "type of I/O" in VMM.INC |
; for definitions. |
; buf_port dw <- Port number |
; buf_data db <- Contains the data (this value |
; can be also be a word or dword) |
; Enter: |
; cl = indicator of direction of access |
; and size of data transfer |
; eax = data value |
; edx = port |
; buffer_wrk_ptr = next buffer position |
; buffer_beg_ptr = start of buffer |
; buffer_end_ptr = end of buffer |
; Exit: |
; buffer_wrk_ptr = next buffer position |
; buf_wrap_flag = Bit 0 set if buffer wrapped |
; All registers saved. |
;--------------------------------------------------------------
BeginProc storedat, HIGH_FREQ
push ecx
push edi
cmp buffer_beg_ptr, 0 ;have buffer?
je short stored999 ;if not, can't store
mov edi, buffer_wrk_ptr
cmp edi, buffer_end_ptr ;at end?
jb short stored100
test buf_wrap_flag, (mask dont_wrap) ;if set up not to wrap
jnz short stored999 ;then exit
mov edi, buffer_beg_ptr ;if so, wrap
mov buffer_wrk_ptr, edi
or buf_wrap_flag, (mask it_wrapped) ;note that it wrapped
stored100:
add edi, [ebx.CB_High_Linear] ;address in VM
sub ch, ch
movzx ecx, cx
mov [edi].buf_info, cl
mov [edi].buf_port, dx
test cl, Dword_IO ;size is dword?
jz short stored130
mov dword ptr [edi].buf_data, eax
and cl, Dword_IO
jmp short stored140
stored130:
and cl, Word_IO
jcxz stored150 ;jump if byte
mov word ptr [edi].buf_data, ax ;store if size is word
stored140: ;convert size to
shr cx, 2 ;number of bytes
dec cx ;minus 1
jmp short stored160 ;go add in to total
stored150:
mov [edi].buf_data, al
stored160:
add ecx, size buf_record ;size of info
add buffer_wrk_ptr, ecx ;add size data
stored999:
pop edi
pop ecx
ret
EndProc storedat
;--------------------------------------------------------------
;ck_handler - Determine if another VxD has requested a call- |
; back for this port. Also, if another VxD has |
; trapped the port, see if it has disabled |
; the trapping via global or local disable. If |
; so, treat it as if it's not trapped. |
; Enter: |
; edx = port number |
; ebx = vm handle |
; Exit: |
; esi = ptr to port info structure |
; ZR if no other trappers or the other |
; trapper has disabled trapping |
;--------------------------------------------------------------
BeginProc ck_handler, HIGH_FREQ
;
;Already know this is one of the ports we're trapping, use determine
;routine to get index into info about port
;
call determin_r_port
cmp [esi].vrio_callb, 0 ;callback for this port?
jz short ck_hand900
push ebx
bt [esi].vio_enab_flags, glob_io_bit ;global disable?
jc short ck_hand800
mov ebx, [ebx].CB_VMID ;get vm id
dec ebx ;zero relative
and ebx, MAX_VM_TRACKED
bt [esi].vio_enab_flags, ebx
;carry set if local or global disable
ck_hand800:
mov ebx, 2
sbb ebx, 1
;return zero if local disable
pop ebx
ck_hand900:
ret
EndProc ck_handler
;--------------------------------------------------------------
;iomon_iohand - This routine replaces the VMM's |
; install_io_handler. If requested port is one |
; that is being monitored, save callback |
; address and return success. |
; Enter: |
; esi = callback address |
; edx = port |
; Exit: |
;--------------------------------------------------------------
BeginProc iomon_iohand
push eax
push esi
mov eax, esi ;save callback address
call determin_r_port
jnz short iomon_io900 ;if not one of ours
cmp [esi].vrio_callb, 0 ;is there already a client?
jne short iomon_io900 ;if so, let vmm reject it
mov [esi].vrio_callb, eax ;store callback address
pop esi
pop eax
ret ;return carry clear
iomon_io900:
pop esi
pop eax
jmp dword ptr the_vmm_iohand
ret
EndProc iomon_iohand
;--------------------------------------------------------------
; iomon_glob_dis - Gets control when VMM service for |
; globally disabling I/O trapping is called. |
; If this is for a port being logged, |
; make a note of it, but don't really do it. |
; If not a port being logged, simply |
; transfer control to the original handler. |
; Enter: |
; edx = port |
; Exit: |
;--------------------------------------------------------------
BeginProc iomon_glob_dis
push esi
call determin_r_port
jnz short iomon_gd900
bts [esi].vio_enab_flags, glob_io_bit
pop esi
ret
iomon_gd900:
pop esi
jmp dword ptr old_glob_disab
EndProc iomon_glob_dis
;--------------------------------------------------------------
; iomon_loc_dis - Gets control when VMM service for |
; local disabling of I/O trapping is called. |
; If this is for a port being logged, |
; make a note of it, but don't really do it. |
; Bit map in vio_enab_flags is used for |
; flagging per vm id number. |
; If not a port being logged, simply |
; transfer control to the original handler. |
; Enter: |
; edx = port |
; ebx = vm handle |
; Exit: |
;--------------------------------------------------------------
BeginProc iomon_loc_dis
push esi
call determin_r_port
jnz short iomon_ld900
push ebx ;save vm handle
mov ebx, [ebx].CB_VMID ;get vm id
dec ebx ;zero relative
and ebx, MAX_VM_TRACKED
bts [esi].vio_enab_flags, ebx
pop ebx
pop esi
clc
ret
iomon_ld900:
pop esi
jmp dword ptr old_loc_disab
EndProc iomon_loc_dis
BeginProc iomon_glob_enab
push esi
call determin_r_port
jnz short iomon_ge900
btr [esi].vio_enab_flags, glob_io_bit
pop esi
ret
iomon_ge900:
pop esi
jmp dword ptr old_glob_enab
EndProc iomon_glob_enab
BeginProc iomon_loc_enab
push esi
call determin_r_port
jnz short iomon_le900
push ebx ;save vm handle
mov ebx, [ebx].CB_VMID ;get vm id
dec ebx ;zero relative
and ebx, MAX_VM_TRACKED
btr [esi].vio_enab_flags, ebx
pop ebx
pop esi
clc
ret
iomon_le900:
pop esi
jmp dword ptr old_loc_enab
EndProc iomon_loc_enab
;--------------------------------------------------------------
;determin_r_port - Check for port in list of ports we're |
; trapping. |
; Enter: |
; edx = port to check |
; number_ports = number of ports trapped |
; port_data = array of port info |
; Exit: |
; NZ if not one of our ports |
; ESI = offset of entry if found |
;--------------------------------------------------------------
BeginProc determin_r_port
push ecx
movzx ecx, number_ports ;number we trapped
mov esi, OFFSET32 port_data
determin_r_p100:
cmp [esi].vrio_port, dx
je short determin_r_p999
add esi, size port_info
loop determin_r_p100
determin_r_p999:
pop ecx
ret
EndProc determin_r_port
VxD_CODE_ENDS
END
/*-----------------------------------------------------------------------
-----------------------------------------------------------------------*/
#include <stdio.h>
#include <bios.h>
#include <dos.h>
#pragma pack(1)
#define FALSE 0
#define TRUE 1
#define GET_BUFFER_PTR 1
#define INPUT 0
#define OUTPUT 4
#define ABYTE 0
#define AWORD 8
#define ADWORD 0x10
#define VMIOD_DEV_ID 0x317e
#define GET_VXD_API 0x1684
#define BUF_REC_SIZE 4 //actually, it's variable length (see below)
struct buf_record {
unsigned char io_attrib;
unsigned short int io_port;
union {
unsigned char bio_data;
unsigned short wio_data;
unsigned long dio_data;
} io_data;
};
struct buf_info {
struct buf_record far * buf_beg_ptr;
unsigned long int buf_bytes_used;
unsigned long int buf_size;
unsigned char buf_flags;
};
struct buf_info buf_ptrs;
char * text_direc[]= {"Read",
"Write"};
char * text_size[]= {"Byte",
"Word",
"Dword"
};
main()
{
void (far * vxd_code_ptr) ();
union REGS inregs, outregs;
struct SREGS segs;
register unsigned char attrib;
struct buf_record far * buf_wrk_ptr;
unsigned incr;
unsigned long int wrk_count = 0;
inregs.x.bx=VMIOD_DEV_ID;
inregs.x.ax=GET_VXD_API;
segs.es=0;
outregs.x.di=0;
int86x ( 0x2f, &inregs, &outregs, &segs ); //Get VxD API
if ( segs.es == 0) //If VxD not installed
{
printf ("%s", "VRKIOMON.386 not installed.");
exit(1);
}
FP_SEG(vxd_code_ptr)=segs.es;
FP_OFF(vxd_code_ptr)=outregs.x.di;
__asm
{
mov ax, GET_BUFFER_PTR ;VxD API function for get ptr
lea dx, buf_ptrs
mov bx, ds ;bx:dx ptr to buf info data
}
(*vxd_code_ptr)(); //Go get the buffer ptr
if (!(buf_wrk_ptr=buf_ptrs.buf_beg_ptr)) exit(1);
while(wrk_count < buf_ptrs.buf_bytes_used)
{
attrib=buf_wrk_ptr->io_attrib;
printf ("%s \t", text_direc[ (attrib & OUTPUT) >> 2 ]);
printf ("%s \t", text_size[ ((attrib & ( ADWORD | AWORD) ) >> 3) ]);
attrib &= ( ADWORD | AWORD);
switch ( (char) attrib)
{
case ABYTE:
printf ("%4x \t %4x \n", buf_wrk_ptr->io_port,
buf_wrk_ptr->io_data.bio_data);
break;
case AWORD:
printf ("%4x \t %4x \n", buf_wrk_ptr->io_port,
buf_wrk_ptr->io_data.wio_data );
break;
case ADWORD:
printf ("%4x \t %8lx \n", buf_wrk_ptr->io_port,
buf_wrk_ptr->io_data.dio_data);
break;
}
if (attrib)
{
attrib = attrib >> 2;
--attrib;
}
incr= BUF_REC_SIZE + attrib;
buf_wrk_ptr=(struct buf_record far *) (
(unsigned char far *) buf_wrk_ptr + incr);
wrk_count += incr;
}
}
Copyright © 1994, Dr. Dobb's Journal