Si le Call est couronné de succès, le flag Carry est mis à 0 et ecx contient le nombre d'octets en réellement réservés pour le buffer de traduction. Cette valeur peut être plus petite que la valeur que vous avez demandée donc vous devez garder quelque part cette valeur pour l'utiliser avec le call V86MMGR_Free_buffer par la suite. edi contient l'adresse du bloc V86 réservé avec en particulier son segment dans le mot de poids fort, et l'offset dans le mot de poids faible. Le flag Carry est mis à 1 si une erreur se produit.
VxDName TEXTEQU <VXDLABEL>
VxD_STATIC_DATA_SEG
VXD_LOCKED_CODE_SEG
Begin_control_dispatch %VxDName
VXD_LOCKED_CODE_ENDS
VXD_PAGEABLE_CODE_SEG
VXD_PAGEABLE_DATA_SEG
end
;------------------------------------------------------------
include \masm32\include\windows.inc
DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data?
.const
.code
DlgProc proc hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
A la fin du Tutorial précédent, vous avez vu comment simuler une interruption V86. Cependant, il y a un problème qui n'a pas été adressé encore : comment échanger des données entre le VxD et le code V86. Nous allons apprendre comment employer le Manager de Mémoire (V86) à notre avantage.
Un peu de théorie
Si votre VxD travaille avec certaines routines V86, il sera nécessaire tôt ou tard pour passer de gros volumes de données vers et en provenance du programme V86. Le passage d'une grande quantité de données via les registres est exclu. La tentative suivante de réserver un bloc de mémoire en Ring 0 et de passer le pointer du bloc de mémoire via un quelconque registre pour que le code du V86 puisse y avoir accès est voué à l'échec. Si essayez ça, votre système s'effondrera probablement parce que l'adressage du mode V86 exige la paire 'segment:offset', et non pas une adresse linéaire.
Il y a beaucoup de solutions à ce problème. Cependant, ici je choisis une voie facile pour vous montrer quelques services fournis par Le Manager de Mémoire V86.
Si d'une façon ou d'une autre vous pouvez trouver un bloc de mémoire libre dans la région V86 que vous pouvez employer en tant que buffer de communication, qui résoudrait un ces problèmes, essayez-le. Cependant, le problème de traduction du pointer reste toujours vrai (adresse linéaire/adresse segment:offset). Vous pouvez résoudre tous les problèmes en employant les services du Mananger de Mémoire V86.
The Le Manager de Mémoire V86 est un VxD statique qui gère la mémoire pour des applications (programmes)V86. Il fournit aussi des services EMS et XMS aux applications V86 et des services de traduction d'APIs à d'autre VxDs. La traduction d'APIs est en réalité un processus de copie des données Ring-0 dans un buffer de la région V86 et ensuite un passage de l'adresse des données V86 au code du V86.
Aucune magie ici. Le Manager de Mémoire V86 entretient le buffer de traduction qui est un bloc de mémoire dans la région V86 pour la reproduction des données du VxD à la région V86 et vice versa. Au commencement ce buffer de traduction est de 4 kilo-octets. Vous pouvez augmenter sa taille en appelant V86MMGR_Set_Mapping_Info.
Maintenant que vous connaissez le buffer de traduction, comment pouvons-nous en copier le contenu ? Cette question implique deux services : V86MMGR_Allocate_Buffer et V86MMGR_Free_buffer.
V86MMGR_Allocate_buffer réserve un bloc de mémoire pour le buffer de traduction et copie facultativement les données Ring 0 dans ce bloc V86 ainsi réservé.
V86MMGR_Free_Buffer fait le virement : il copie facultativement les données du bloc V86 réservé dans le buffer Ring-0 et libère le bloc de mémoire réservé par V86MMGR_Allocate_buffer.
Note que le Manager de Mémoire V86 maintient les Buffers réservés comme une pile. Ça signifie que l'empilement/désempilement doit repecter la règle 'Premier empilé/Dernier désempilé'. Ainsi si vous faites deux appels consécutifs avec V86MMGR_Allocate_Buffer, le premier V86MMGR_Free_Buffer libérera le 2e buffer réservé par l'appel V86MMGR_Allocate_Buffer.
Nous pouvons maintenant continuer à examiner la définition V86MMGR_Allocate_buffer. C'est un service de registre-basé. C'est à dire un service qui utilise les registres et non pas la pile pour passer ses paramètres.
ebx
Handle de la VM en fonctionnement
ebp
Pointer sur la structure Client Register de la VM actuelle
ecx
Nombre d'octets à réserver pour le buffer de traduction
Le Flag 'carry'
Mettez-le à 0 si vous ne voulez pas copier de données du buffer Ring-0 vers le bloc réservé. Mettez-le à 1 si vous voulez copier des données du buffer Ring-0 vers le bloc réservé
fs:esi
Adresse du bloc mémoire Ring-0 qui contient les données qui doivent être copiées dans le buffer réservé. A ignorer si le flag Carry est clair (0).
V86MMGR_Free_buffer accepte exactement les mêmes paramètres que V86MMGR_Allocate_buffer.
Ce qui arrive en réalité lorsque vous appelez V86MMGR_Allocate_buffer c'est que vous réservez un bloc de mémoire dans la région V86 de la VM (Machine Virtuelle) courante et obtenez l'adresse V86 de ce bloc de mémoire dans edi. Nous pouvons utiliser ces services pour passer des données à une interruption V86 (dans un sens et dans l'autre).
En plus de la traduction d'APIs, le Manager de Mémoire V86 offre également à d'autre VxDs un service permettant de 'Mapper' les APIs. Mapper, c'est par exemple prendre un morceau de programme (physiquement écrit sous forme de fichier) et le recopier quelque part en mémoire. On fait une copie d'un original physique en Mémoire. Ici il s'agit de Mapper une certaine zone de mémoire vers une autre zone de mémoire ,la zone V86, utilisable pour nous.
Le faite de Mapper des APIs est en réalité un processus de 'Mapping' de quelques pages de la mémoire étendue dans la région V86 de chaque VM. Vous pouvez employer V86MMGR_Map_Pages pour Mapper des APIs. Avec ce service, les pages sont Mappées à partir de le même adresse linéaire dans chaque VM. C'est un espace d'adresses superflu si vous voulez travailler avec seulement une VM. Aussi le Mappage d'APIs est plus lent que la traduction d'APIs, donc vous devez employer la traduction d'APIs autant que possible. Mapper des APIs est exigé pour quelque opération V86 qui ont besoin d'avoir accès à la même adresse linéaire et doivent être présent dans toutes les VMs.
Exemple
Cet exemple emploie la traduction d'API grâce à l'int 21h, code 66h, mot de poids faible 440Dh, pour obtenir le n°ID des lecteurs, et obtenir le nom de votre premier disque fixe (le disque dur).
;---------------------------------------------------------------
;
VxDLabel.asm
;---------------------------------------------------------------
.386p
include \masm\include\vmm.inc
include \masm\include\vwin32.inc
include \masm\include\v86mmgr.inc
ControlName TEXTEQU <VXDLABEL_Control>
VxDMajorVersion TEXTEQU <1>
VxDMinorVersion TEXTEQU <0>
VxD_STATIC_DATA_ENDS
;----------------------------------------------------------------------------
; Souvenez-vous : le nom du VxD DOIT ÊTRE majuscule sinon il ne se chargera/déchargera pas.
;----------------------------------------------------------------------------
DECLARE_VIRTUAL_DEVICE %VxDName,%VxDMajorVersion,%VxDMinorVersion,
%ControlName,UNDEFINED_DEVICE_ID,UNDEFINED_INIT_ORDER
Control_Dispatch
W32_DEVICEIOCONTROL, OnDeviceIoControl
End_control_dispatch %VxDName
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
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
;
Label.asm
; The win32 VxD loader.
;------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.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
hInstance HINSTANCE ?
hVxD dd ?
DiskLabel db 12 dup(?)
BytesReturned dd ?
IDD_VXDRUN equ 101
IDC_LOAD equ 1000
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke DialogBoxParam, hInstance, IDD_VXDRUN ,NULL,addr
DlgProc,NULL
invoke ExitProcess,eax
.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
Analyse
Nous allons d'abord examiner label.asm qui est l'application win32 qui charge le VxD.
invoke DeviceIoControl,hVxD,1,NULL,0,addr
DiskLabel,12,\
Il appelle DeviceIoControl avec le code du Device égal à 1, aucune entrée buffer, un Pointer sur une sortie buffer ainsi que sa taille. DiskLabel
est un buffer mis qu'on vient d'initialiser pour réceptionner le nom du volume (du lecteur) renvoyé par le VxD. Le nombre réel d'octets retourné sera stocké dans la variable BytesReturned. Cet exemple montre comment passer des données vers un VxD et recevoir des données d'un VxD : vous passez des Buffers d'Entrée/Sortie au VxD et le VxD Lit/Ecrit dans ces Buffers qui lui sont réservés.
addr BytesReturned,NULL
Examinons le VxD maintenant.
VMMCall Get_Sys_VM_Handle
Quand le VxD reçoit le message W32_DeviceIoControl, il appelle Get_Sys_VM_handle pour obtenir le handle système VM (la Machine Virtuelle Principale) et le stocker dans une variable nommée handle. Il extrait ensuite le pointer de la structure 'Client Register' à partir du bloc de contrôle de la VM, vers ebp.
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]mov ecx,sizeof MID
Ensuite, on prépare les paramètres qui doivent être passé à V86MMGR_Allocate_buffer. Nous devons réserver le buffer, de là l'instruction stc. Nous mettons l'offset de MediaID dans esi et le sélecteur dans fs, on appelle alors V86MMGR_Allocate_buffer. Vous vous rappellerez qu'esi contient le pointer sur DIOCParams donc nous devons le préserver en le mettant dans esi puis pousser esi sur la pile et enfin passer esi.
stc
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Allocate_Buffer
pop esi
jc EndI
mov AllocSize,ecxPush_Client_State
Nous préparons les valeurs dans la structure 'Client Register' pour l'int 21h, 440Dh code 66h. En spécifiant ça nous souhaitons récupérer le n°ID du lecteur C. Nous copions aussi la valeur de edi dans edx (edi contient l'adresse V86 du bloc de mémoire réservé par V86MMGR_Allocate_Buffer).
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 ; Lecteur C
mov [ebp].Client_ax,440dhmov [ebp].Client_dx,dx
Puisque l'int 21h, 440Dh, code 66h s'attend à recevoir le pointer au niveau du MILIEU de ds:dx, nous devons casser cette paire 'segment:offset' dans edx en deux parties et les mettre dans les images des registres correspondantes.
shr edx,16
mov [ebp].Client_ds,dxmov eax,21h
Quand tout est prêt, nous appelons Exec_Int pour simuler l'interruption.
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_Statemov ecx,AllocSize
Après le retour de Exec_Int, le buffer réservé est rempli par l'information que nous voulons. L'étape suivant c'est de retrouver cette information. On fait ça en appelant V86MMGR_Free_buffer.
Ce service libère le bloc de mémoire réservé par V86MMGR_Allocate_Memory et copie les données du bloc de mémoire réservé vers le bloc de mémoire Ring-0 indiqué. Comme pour V86MMGR_Allocate_Memory, si vous voulez effectuer une opération de copie, vous devez mettre le flag Carry avant l'appel de ce service.
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esimov edx,esi
Après que nous ayons l'information dans le buffer Ring-0, nous copions le nom du volume (du lecteur) dans le buffer de l'application win32. Nous pouvons avoir accès à ce buffer en employant le membre lpvOutBuffer appartenant à DIOCParams.
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