Virtual 8086 Memory Manager
In the previous tutorial, you learn how to simulate V86 interrupt. However, there is one problem that hasn't been addressed yet: exchanging data between VxD and V86 code. We will learn how to use the V86 Memory Manager to our advantage in this regard.
Download the example.
Some Theory
If your VxD works with some V86 routines, it will be necessary sooner or later to pass large chunks of data to and from the V86 program. Passing a large amount of data via registers is out of question. Your next attempt might be to allocate a block of memory in ring 0 and pass the pointer to the memory block via some register so the V86 code can access the data. If you do so, it will probably crash your system because V86 addressing mode requires segment:offset pair, not the linear address.
There are many solutions to that problem. However, I choose an easy way here to demonstrate services provided by the V86 Memory Manager.
If somehow you can find a free memory block in the V86 region that you can use as the communcation buffer, that would solve one of the problems. However, the pointer translation problem remains. You can solve both problems by using the V86 Memory Mananger's services.
The V86 Memory Manager is the static VxD that manages memory for V86 applications. It also provides EMS and XMS services to V86 apps and API translation services to other VxDs. API translation is actually the process of copying the data from ring-0 into a buffer in V86 region and then passing the V86 address of the data to the V86 code. No magic here. The V86 Memory Manager maintains a translation buffer which is a block of memory in the V86 region for copying data from VxD to V86 region and vice versa. Initially this translation buffer is 4 kilobytes. You can increase the size of this buffer by calling V86MMGR_Set_Mapping_Info.
Now that you know about the translation buffer, how can we copy data to and from it? This question involves two services: V86MMGR_Allocate_Buffer and V86MMGR_Free_Buffer.
V86MMGR_Allocate_Buffer allocates a block of memory from the translation buffer and optionally copies the data from ring 0 into the allocated V86 block. V86MMGR_Free_Buffer does the reverse: it optionally copies the data from the allocated V86 block into a ring-0 buffer and frees the memory block allocated by V86MMGR_Allocate_Buffer.
Note that the V86 Memory Manager maintains the allocated buffers as a stack. That means the allocation/deallocation must observe the first in/first out rule. So if you make two calls to V86MMGR_Allocate_Buffer, the first V86MMGR_Free_Buffer will free the buffer allocated by the second V86MMGR_Allocate_Buffer call.
We can now proceed to examine V86MMGR_Allocate_Buffer definition. It's a register-based service.
ebx
Handle of the current VM
ebp
Pointer to the current VM's client register structure
ecx
Number of bytes to allocate from the translation buffer
carry flag
clear if you don't want to copy data from ring-0 buffer to the allocated block. Set if you want to copy data from ring-0 buffer to the allocated block
fs:esi
selector:offset of the ring-0 memory block that contains the data to be copied to the allocated buffer. Ignored if the carry flag is clear.
If the call is successful, the carry flag is clear and ecx contains the number of bytes actually allocated in the translation buffer. This value can be less than the value you requested so you should keep this value for use with V86MMGR_Free_Buffer call later. edi contains the v86 address of the allocated block with the segment in the high word, the offset in the low word. The carry flag is set if an error occurs.
V86MMGR_Free_Buffer accepts exactly the same parameters as V86MMGR_Allocate_Buffer.
What actually occurs when you call V86MMGR_Allocate_Buffer is that you allocate a block of memory in V86 region of the current VM and obtain the V86 address of that memory block in edi. We can use these services to pass data to and from V86 interrupts.
In addition to API translation, the V86 Memory Manager also offers API mapping service to other VxDs. API mapping is actually the process of mapping some pages in extended memory into V86 region of every VM. You can use V86MMGR_Map_Pages to do API mapping. With this service, the pages are mapped into the same linear address in every VM. This waste address space if you want to work with only one VM. Also API mapping is slower than API translation so you should use API translation as much as possible. API mapping is required for some V86 operation that needs to access the same linear address and must be present in all VMs.
The Example
This example uses API translation with int 21h, 440Dh minor code 66h, Get Media ID, to obtain the volume label of your first fixed disk.
;---------------------------------------------------------------
; VxDLabel.asm
;---------------------------------------------------------------
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\v86mmgr.inc
VxDName TEXTEQU <VXDLABEL>
ControlName TEXTEQU <VXDLABEL_Control>
VxDMajorVersion TEXTEQU <1>
VxDMinorVersion TEXTEQU <0>
VxD_STATIC_DATA_SEG
VxD_STATIC_DATA_ENDS
VXD_LOCKED_CODE_SEG
;----------------------------------------------------------------------------
; Remember: The name of the vxd MUST be uppercase else it won't work/unload
;----------------------------------------------------------------------------
DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion, %ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER
Begin_control_dispatch %VxDName
Control_Dispatch W32_DEVICEIOCONTROL, OnDeviceIoControl
End_control_dispatch %VxDName
VXD_LOCKED_CODE_ENDS
VXD_PAGEABLE_CODE_SEG
BeginProc OnDeviceIoControl
assume esi:ptr DIOCParams
.if [esi].dwIoControlCode==1
VMMCall Get_Sys_VM_Handle
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]
mov ecx,sizeof MID
stc
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Allocate_Buffer
pop esi
jc EndI
mov AllocSize,ecx
Push_Client_State
VMMCall Begin_Nest_V86_Exec
assume ebp:ptr Client_Byte_Reg_Struc
mov [ebp].Client_ch,8
mov [ebp].Client_cl,66h
assume ebp:ptr Client_word_reg_struc
mov edx,edi
mov [ebp].Client_bx,3 ; drive A
mov [ebp].Client_ax,440dh
mov [ebp].Client_dx,dx
shr edx,16
mov [ebp].Client_ds,dx
mov eax,21h
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_State
;-------------------------------
; retrieve the data
;-------------------------------
mov ecx,AllocSize
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esi
mov edx,esi
assume edx:ptr DIOCParams
mov edi,[edx].lpvOutBuffer
mov esi,OFFSET32 MediaID.midVolLabel
mov ecx,11
rep movsb
mov byte ptr [edi],0
mov ecx,[edx].lpcbBytesReturned
mov dword ptr [edx],11
EndI:
.endif
xor eax,eax
ret
EndProc OnDeviceIoControl
VXD_PAGEABLE_CODE_ENDS
VXD_PAGEABLE_DATA_SEG
MID struct
midInfoLevel dw 0
midSerialNum dd ?
midVolLabel db 11 dup(?)
midFileSysType db 8 dup(?)
MID ends
MediaID MID <>
Handle dd ?
AllocSize dd ?
VXD_PAGEABLE_DATA_ENDS
end
;------------------------------------------------------------
; Label.asm
; The win32 VxD loader.
;------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
Failure db "Cannot load VxDLabel.VXD",0
AppName db "Get Disk Label",0
VxDName db "\\.\vxdLabel.vxd",0
OutputTemplate db "Volume Label of Drive C",0
.data?
hInstance HINSTANCE ?
hVxD dd ?
DiskLabel db 12 dup(?)
BytesReturned dd ?
.const
IDD_VXDRUN equ 101
IDC_LOAD equ 1000
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, IDD_VXDRUN ,NULL,addr DlgProc,NULL
invoke ExitProcess,eax
DlgProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_INITDIALOG
invoke CreateFile,addr VxDName,0,0,0,0,FILE_FLAG_DELETE_ON_CLOSE,0
.if eax==INVALID_HANDLE_VALUE
invoke MessageBox,hDlg,addr Failure,addr AppName,MB_OK+MB_ICONERROR
mov hVxD,0
invoke EndDialog,hDlg,NULL
.else
mov hVxD,eax
.endif
.elseif uMsg==WM_CLOSE
.if hVxD!=0
invoke CloseHandle,hVxD
.endif
invoke EndDialog,hDlg,0
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
mov edx,wParam
shr edx,16
.if dx==BN_CLICKED
.IF ax==IDC_LOAD
invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,addr BytesReturned,NULL
invoke MessageBox,hDlg,addr DiskLabel,addr OutputTemplate,MB_OK+MB_ICONINFORMATION
.endif
.endif
.ELSE
mov eax,FALSE
ret
.ENDIF
mov eax,TRUE
ret
DlgProc endp
end start
Analysis
We will examine label.asm which is the win32 application which loads the VxD first.
invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,\
addr BytesReturned,NULL
It calls DeviceIoControl with device code equal to 1, no input buffer, a pointer to an output buffer and its size. DiskLabel is the buffer set aside for receiving the volume label returned by the VxD. The number of bytes actually returned will be stored in BytesReturned variable. This example demonstrates how to pass data to and receive data from a VxD: you pass input/output buffers to the VxD and the VxD reads from/write to the submitted buffers.
We will examine the VxD code next.
VMMCall Get_Sys_VM_Handle
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]
When the VxD receives W32_DeviceIoControl message, it calls Get_Sys_VM_Handle to obtain the system VM handle and store it into a variable named Handle. It next extracts the pointer to the client register structure from the VM control block into ebp.
mov ecx,sizeof MID
stc
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Allocate_Buffer
pop esi
jc EndI
mov AllocSize,ecx
Next, it prepares the parameters to be passed to V86MMGR_Allocate_Buffer. We must initialize the allocated buffer, hence the stc instruction. We put the offset of MediaID into esi and the selector into fs then call V86MMGR_Allocate_Buffer. You'll recall that esi contains the pointer to DIOCParams so we must preserve it by push esi and pop esi.
Push_Client_State
VMMCall Begin_Nest_V86_Exec
assume ebp:ptr Client_Byte_Reg_Struc
mov [ebp].Client_ch,8
mov [ebp].Client_cl,66h
assume ebp:ptr Client_word_reg_struc
mov edx,edi
mov [ebp].Client_bx,3 ; drive C
mov [ebp].Client_ax,440dh
We prepare the values in the client register structure for the int 21h, 440Dh minor code 66h. Specifying that we want to get the media ID of drive C. We also copy the value in edi into edx (edi contains the V86 address of the memory block allocated by V86MMGR_Allocate_Buffer).
mov [ebp].Client_dx,dx
shr edx,16
mov [ebp].Client_ds,dx
Since int 21h, 440Dh, minor code 66h expects pointer to an MID structure in ds:dx, we must break the segment:offset pair in edx into two parts and put them into the corresponding register images.
mov eax,21h
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_State
When everything is ready, we call Exec_Int to simulate the interrupt.
mov ecx,AllocSize
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esi
After Exec_Int returns, the allocated buffer is filled by the information we want. The next step is to retrieve that information. We achieve that goal by calling V86MMGR_Free_Buffer. This service frees the memory block allocated by V86MMGR_Allocate_Memory and copies the data in the allocated memory block to the specified ring-0 memory block. Like V86MMGR_Allocate_Memory, if you want the copy operation, you must set the carry flag prior to calling the service.
mov edx,esi
assume edx:ptr DIOCParams
mov edi,[edx].lpvOutBuffer
mov esi,OFFSET32 MediaID.midVolLabel
mov ecx,11
rep movsb
mov byte ptr [edi],0
mov ecx,[edx].lpcbBytesReturned
mov dword ptr [edx],11
After we have the information in the ring-0 buffer, we copy the volume label to the buffer provided by the win32 application. We can access the buffer by using lpvOutBuffer member of DIOCParams.