下邊我們用到的V86即指虛擬8086模式。 在以前的教程中,你學習了怎樣模擬V86中斷,但還有一個問題沒有解決:在VxD和V86代碼之間交換數據。我們將在此學習如何使用V86內存管理器來實現這個功能。在這裡下載例子程序
理論
假如你的VxD和一些V86程序一起運行,如何傳送大量數據到V86程序中或從V86程序中傳送大量數據遲早是一個大問題。通過寄存器傳送大量數據是不現實的。可能你的下一個想法是在ring0中分配一大塊內存,並且通過一些寄存器傳送其指針到V86程序,使其能訪問這些數據。假如你這樣做,可能會破壞你的系統,因為V86的地址定位方式需要segment:offset對,而不是線性定位方式。對這個問題,有很多解決的方法。然而,我選擇了一個由V86內存管理器提供的一種簡便的方法。
如你能在你可使用的V86內存范圍內找到一個空閒的內存塊作為通訊緩沖區,這將解決其中的一個問題。然而,指針傳送的問題依然存在。你可以通過V86內存管理器的服務來解決這兩個問題。V86內存管理器是為V86應用管理內存的靜態VxD。它還為V86應用提供EMS和XMS服務和為其他VxD提供API傳送服務。API傳送是一個從ring0拷貝數據到V86范圍內的緩沖區並且傳送V86緩沖區地址到V86代碼的過程。V86內存管理器有一個在V86內存范圍內的傳送緩沖區,其含有VxD拷貝到V86內存范圍內的數據,反之亦然。初始的緩沖區是4K。你以調用V86MMGR_Set_Mapping_Info來增加它的大小。
現在你知道了傳送緩沖區,我們如何拷入或拷出數據呢?這個問題通過調用兩個服務來解決:V86MMGR_Allocate_Buffer和V86MMGR_Free_Buffer。
V86MMGR_Allocate_Buffer從傳送緩沖區分配一塊內存並且從ring0拷貝一些數據到分配的V86緩沖區。V86MMGR_Free_Buffer正好相反:它從分配的V86內存塊拷貝一些數據到ring0緩沖區並且釋放由V86MMGR_Allocate_Buffer分配的內存塊。
記住,V86在內存管理器象堆棧一樣管理被分配的緩沖區。這意味著分配/釋放必須按先進後出的規則。所以如你調用了兩次V86MMGR_Allocate_Buffer,第一個V86MMGR_Free_Buffer將釋放由第二個V86MMGR_Allocate_Buffer調用而分配的緩沖區。
我們來看一下V86MMGR_Allocate_Buffer的定義,它是一個基本寄存器傳送參數的服務。
EBX 當前VM的句柄
EBP 指向當前VM的客戶寄存器結構的指針
ECX 從傳送緩沖區分配的字節數 CARRY FLAG 進位標志位,如你不想從ring0緩沖區拷貝數據到分配的內存塊就清零, 如你想從ring0緩沖區拷貝數據到分配的內存塊就置1
FS:ESI 指向ring0緩沖區的selector:offset指針,緩沖區中有要被拷貝到被分配的 緩沖區中的數據如果進位標志位被清零,則忽略它。
假如調用成功,進位標志位被清零並且ECX包含在傳送緩沖區中的字節數。這個數值應小於你要求的數值,所以你應保持這個數值,V86MMGR_Free_Buffer待會要用到它。EDI的高字包含被分配的內存塊的V86段地址,偏移地址在在低字中。進位標志位當錯誤發生時被置位。
V86MMGR_Free_Buffer和V86MMGR_Allocate_Buffer接受同樣的參數。
當你調用V86MMGR_Allocate_Buffer時,你在當前VM的V86內存范圍內分配了一塊內存,並且把其地址放到了EDI中。你可以使用這些服務傳送數據到V86中斷中或從V86中斷中取得數據。
在附加的API傳送中,V86內存管理器也給其他VxDs提供了API映射服務。API映射服務是映射一些在擴展內存中的頁到每個VM的V86內存范圍。你可以使用V86MMGR_Map_Pages執行API映射。使用這個服務,頁被映射到每個VM的同一線性地址空間上。如你僅僅工作在一個VM上,這將浪費地址空間。因為API映射比API傳送要慢,所以你盡可能使用API傳送方式。API映射僅僅使用在一些要訪問同一線性地址空間並作用到所有VM的V86操作上。
例子:
這個例子演示了API傳送方式,使用了int 21h的440Dh功能(從代碼66h)。這個中斷調用得到媒體ID,你的第一個固定磁盤的卷標號。
;---------------------------------------------------------------
; VxDLabel.asm
;---------------------------------------------------------------
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\v86mmgr.inc
VxDName TEXTEQU
ControlName TEXTEQU
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
講解
我們首先分析lable.asm,它是一個加載了VxD的WIN32應用程序。
invoke DeviceIoControl,hVxD,1,NULL,0,addr DiskLabel,12,addr BytesReturned,NULL
它調用DeviceIoControl,設備代碼是1,沒有輸入緩沖區,一個指向輸出緩沖區的指針及其大小。DiskLable是一個接收由VxD返回的卷標號的緩沖區。BytesReturned變量存有返回的字節數。這個例子說明了怎樣傳送數據和從VxD接收數據:你傳送輸入/輸出緩沖區給VxD並且VxD讀取/寫入數據到指定的緩沖區。
我們下面看看VxD代碼。
VMMCall Get_Sys_VM_Handle
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]
當一個VxD接收W32_DeviceIoControl消息,它調用Get_Sys_VM_Handle得到系統VM的句柄並把它存在一個叫Handle的變量中。下面將從VM控制塊中提取指向客戶寄存器結構的指針到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
下面,准備傳送到V86MMGR_Allocate_Buffer的參數。我們必須初始化被分配的緩沖區。我們把MediaID的偏移量送到ESI中,並且把選擇子放在FS中,然後調用V86MMGR_Allocate_Buffer。你等會要恢復指向DIOCParams的指針,所以我們必須通過push esi 和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
我們在客戶寄存器結構中准備參數值來執行int 21h的440Dh功能(從代碼66h),得到盤C的媒體ID。我們拷貝edi的值到edx中(edi中有由V86MMGR_Allocate_Buffer分配的內存塊的V86地址)。
mov [ebp].Client_dx,dx
shr edx,16
mov [ebp].Client_ds,dx
調用了int 21h的440Dh功能(從代碼66h)後,在ds:dx中得到一指向一個MID結構的指針,我們必須把在edx中的segment:offset對轉換成兩個部分並把它們放到合適的寄存器映象中。
mov eax,21h
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_State
當一切都准備好了,我們將運行Exec_Int去模擬一個中斷。
mov ecx,AllocSize
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esi
當Exec_Int返回時,分配的緩沖已經由我們想要的信息填滿了。下一步是檢索信息。我們使用 V86MMGR_Free_Buffer來完成這個目標。這個服務釋放由V86MMGR_Allocate_Memory分配的內存塊,並且從分配的內存塊中拷貝數據到ring0中的內存塊。象V86MMGR_Allocate_Memory,如果你想要進行拷貝操作,你必須先把進位標志位置1,再調用服務。
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
我們在ring0中得到這個信息後,拷貝這個卷標值到Win32應用程序提供的緩沖區中。我們可以用DIOCParams的成員lpvOutBuffer來訪問它。