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