Le Manager de Mémoire Virtuelle : 8086
(Virtual 8086 Memory Manager)


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.
Downloadez l'exemple.

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).

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.
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

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
;----------------------------------------------------------------------------
; 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

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

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,\
                addr BytesReturned,NULL
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.

Examinons le VxD maintenant.
VMMCall Get_Sys_VM_Handle
mov Handle,ebx
assume ebx:ptr cb_s
mov ebp,[ebx+CB_Client_Pointer]
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 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
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.
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 ; Lecteur C
mov [ebp].Client_ax,440dh
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).
mov [ebp].Client_dx,dx
shr edx,16
mov [ebp].Client_ds,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.
mov eax,21h
VMMCall Exec_Int
VMMCall End_Nest_Exec
Pop_Client_State
Quand tout est prêt, nous appelons Exec_Int pour simuler l'interruption.
mov ecx,AllocSize
stc
mov ebx,Handle
push esi
mov esi,OFFSET32 MediaID
push ds
pop fs
VxDCall V86MMGR_Free_Buffer
pop esi
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.
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
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.

[Iczelion's Win32 Assembly Homepage]


Traduit par Morgatte