Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

A Windows I/O Monitor


SP 94: A Windows I/O Monitor

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 Protection Mechanism

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.

VMM Services

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.

Inside VRKIOMON

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.

Using VRKIOMON

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.

Conclusion

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.

References

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.

Figure 1: Required additions to SYSTEM.INI to install VRKIOMON.

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

Table 1: Optional parameters that can be specified when installing VRKIOMON.

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

Listing One


;---------------------------------------------------------------    
;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 [email protected]
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


Listing Two


/*-----------------------------------------------------------------------
-----------------------------------------------------------------------*/
#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


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.