虛擬8086模式的記憶體管理(轉)

heying1229發表於2007-07-28
虛擬8086模式的記憶體管理:

  下邊我們用到的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 masmincludevmm.inc
include masmincludevwin32.inc
include masmincludev86mmgr.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 masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libuser32.lib
includelib masm32libkernel32.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來訪問它。

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10172717/viewspace-928822/,如需轉載,請註明出處,否則將追究法律責任。

相關文章