Squelette d'un Driver de Matériel Virtuel
Ou squelette d'un VxD
(Virtual Device Driver
Skeleton)
Maintenant que vous connaissez quelques bases à propos du VMM (Manager de Machines Virtuelles) et des VxDs (Drivers de Dispositifs (matériels) Virtuels), nous allons voir comment coder un VxD. Vous avez besoin du kit de Développement sur les Drivers de Dispositif de Windows 95/98 (Windows
95/98 Device Driver Development Kit). Il est essentiel que vous l'ayez. Windows 95 DDK est disponible seulement aux abonnés MSDN. Cependant, Windows 98 DDK est disponible gratuitement chez Microsoft. Vous pouvez aussi employer Windows 98 DDK pour développer des VxDs même s'il est orienté vers WDM. Vous pouvez télécharger Windows 98 DDK à partir de http://www.microsoft.com/hwdev/ddk/install98ddk.htm?
Vous pouvez télécharger le pack complet, environ 30 MO ou vous pouvez télécharger seulement les parties qui vous intéressent. Si vous voulez ne pas télécharger le paquet entier, n'oubliez pas de télécharger la documentation de Windows 95 DDK incluse dans other.exe
Windows 98 DDK contient MASM version 6.11d. Vous devez vous mettre à jour à la dernière version. Pour savoir où télécharger la dernière version, vérifiez ma page principale.
Windows 9x DDK contient plusieurs fichiers 'include' (*.inc) essentiels lesquels ne sont pas inclus dans le pack MASM32.
Vous pouvez télécharger l'exemple de ce tutorial ici.
Format de fichier 'LE'
Les VxDs emploient les formats de fichiers de type 'exécutables linéaires' (LE). C'est le format de fichier conçu pour la version 2.0 OS/2. Ce format peut supporter à la fois les code 16bit et 32bits, ce qui est une des exigences des VxDs. Rappelez-vous des vieux jours des VxDs de Windows 3.x. En ce temps là, Windows bootait sous DOS, donc les VxDs devaient être capable de faire quelques initialisations en mode réel (une seule machine réelle) avant que Windows ne commute la machine en mode protégé (plusieurs machines virtuelles). Le mode réel de code 16 bits doit être dans le même fichier exécutable que le code de mode 32 bits protégé. Donc le format de fichier LE est le choix logique. Les Drivers de Windows NT n'ont pas besoin de traiter avec l'initialisation de mode réel, c'est pourquoi ils n'utilisent pas le format de fichier LE. A la place, ils emploient le format de fichier PE.
Le .CODE et les .DATA dans un fichier 'LE' sont stockés dans des segments lesquels ont des attributs différents pendant l'exécution. Voici ci-dessous les classes de segments disponibles.
-
LCODE
Page-locked code et data. Ce segment est fermé en mémoire. Autrement dit, ce segment ne sera pas paginé sur le disque, c'est pourquoi vous devez employer cette classe de segment judicieusement afin de ne pas gaspiller la précieuse mémoire du système. Le code et les données qui doivent être présents en mémoire à tout moment doivent être dans ce segment. Particulièrement les manipulateurs d'interruption matériel. (hardware interrupt handlers)
-
PCODE
Pageable code. Ce segment est 'pageable' par le VMM (Paginable, il l'utilise le feuillete et écrit dedans comme il l'entend). Le code dans ce segment n'a pas besoin d'être tout le temps présent en mémoire. Le VMM mettera à jours ce segment sur le disque s'il a besoin de mémoire physique.
-
PDATA
Pageable data.
-
ICODE
Initialization-only code. Le code dans ce segment est employé uniquement pendant l'initialisation du VxD. Après l'initialisation, ce segment sera abandonné par le VMM pour réclamer la mémoire physique.
-
DBOCODE
debug-only code et data. Le code et des données de ce segment sont utilisés lorsque vous faîtes tourner le VxD sous un débugger. Il contient le Handler pour le message de contrôle Debug_Query, par exemple.
-
SCODE
Static code et data. Ce segment restera toujours présent en mémoire même lorsque le VxD est déchargé. Ce segment est particulièrement utile pour les VxDs dynamiques quand ils doivent être chargés/déchargés plusieurs fois pendant une session de Windows et veulent se rappeler de la dernière configuration/état.
-
RCODE
Real-mode initialization code et data. Ce segment contient le code (16 bits) et des données pour l'initialisation du mode réel. (1 seule machine réelle, pas de machines virtuelles)
-
16ICODE
USE16 protected-mode initialization data. Ce segment est un segment 16 bits qui contient le code que le VxD (le Driver de Matériel Virtuel) copiera du mode protégé vers le mode V86. Par exemple, si vous voulez coller un quelconque code V86 dans une VM (une Machine Virtuelle), le code que vous avez l'intention de coller doit être dans ce segment. Si vous mettez ce code dans un autre segment, l'assembleur reproduira mal le code, c'est-à-dire qu'il produira un code 32 bits au lieu d'un 16 bits comme ce qui est attendu.
-
MCODE
Locked message strings. Ce segment contient les chaînes de caractères (du texte) lesquelles sont compilées à l'aide de messages macros du VMM. C'est pour vous aide à créer les versions internationales de votre Driver.
Ça ne signifie pas que votre VxD doive forcément avoir TOUS ces segments. Vous pouvez choisir uniquement les segments que vous voulez employer dans votre VxD. Par exemple, si votre VxD n'a pas l'initialisation du mode réel, il ne doit pas présenter le segment RCODE.
La plupart du temps, vous utiliserez LCODE, PCODE et PDATA.
En tant qu'auteur de VxDs, c'est à vous de juger et de choisir les segments appropriés pour vos .CODE/.DATA. En général, vous devez employer PCODE et PDATA autant que possible parce que le VMM peut paginer ces segments en mémoire si nécessaire. Vous devez employer LCODE pour stocker les manipulateurs d'interruptions matériel (hardware interrupt handlers) et les services qu'ils appellent.
N'employez pas directement ces classes de segment. Vous devez en fait déclarer des segments basés sur ces classes. Ces déclarations de segment sont stockées dans un fichier de définition (.def). La totalité du fichier de définition pour un VxD est ci-dessous :
VXD FIRSTVXD
SEGMENTS
_LPTEXT
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LTEXT
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LDATA
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TEXT
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_DATA
CLASS 'LCODE' PRELOAD NONDISCARDABLE
CONST
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TLS
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_BSS
CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LMGTABLE
CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_LMSGDATA
CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_IMSGTABLE
CLASS 'MCODE' PRELOAD DISCARDABLE IOPL
_IMSGDATA
CLASS 'MCODE' PRELOAD DISCARDABLE IOPL
_ITEXT
CLASS 'ICODE' DISCARDABLE
_IDATA
CLASS 'ICODE' DISCARDABLE
_PTEXT
CLASS 'PCODE' NONDISCARDABLE
_PMSGTABLE
CLASS 'MCODE' NONDISCARDABLE IOPL
_PMSGDATA
CLASS 'MCODE' NONDISCARDABLE IOPL
_PDATA
CLASS 'PDATA' NONDISCARDABLE SHARED
_STEXT
CLASS 'SCODE' RESIDENT
_SDATA
CLASS 'SCODE' RESIDENT
_DBOSTART
CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_DBOCODE
CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_DBODATA
CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_16ICODE
CLASS '16ICODE' PRELOAD DISCARDABLE
_RCODE
CLASS 'RCODE'
EXPORTS
FIRSTVXD_DDB
@1
La première déclaration est celle du nom du VxD. Le nom d'un VxD DOIT ÊTRE tout en majuscule. J'ai fais l'expérience avec des noms minuscules et le VxD a tout refusé d'exécuter sauf son propre chargement en mémoire.
L'étape suivante c'est la déclaration des segments. La déclaration consiste en trois parties : le nom du segment, la classe du segment et la propriété souhaitée pendant l'exécution de ce segment. Vous pouvez voir qu'il y a beaucoup de segments basés sur une même classe, par exemple, _LPTEXT, _LTEXT, _LDATA sont tous basées sur la classe de segment LCODE avec exactement les mêmes propriétés.
Ces segments sont déclarés ainsi pour pouvoir comprendre plus facilement le code du programme écrit. Par exemple, LCODE peut contenir à la fois du code et des données. Alors que ce sera plus facile pour le programmeur s'il peut stocker les données dans le segment _LDATA et le code dans le segment _LTEXT. Et en fin de compte, ces deux segments seront combinés dans un unique segment, dans le fichier exécutable final.
Un VxD exporte un et seulement un symbole, son bloc descripteur de Matériel (DDB) ou (device descriptor block). Le DDB est en réalité une structure qui contient tout ce que le VMM a besoin de savoir sur un VxD. Vous DEVEZ exportez le DDB dans le fichier de définition de module.
La plupart du temps, vous pouvez employer le susdit fichier '.DEF' dans de nouveaux projets de VxDs. Vous devez seulement changer du nom du VxD dans la première et dernière ligne du fichier '.DEF'. Les déclarations de segment sont des overkills (des surplus non adaptés à lui) pour un projet de VxD en asm. Ils servent à l'écriture d'un VxD en C, cependant leur utilisation dans un projet asm reste correcte. Vous obtiendrez plusieurs messages d'avertissement mais votre VxD sera quand même assemblé. Vous pouvez vous débarrasser de ces messages d'avertissement en supprimant les déclarations de segment que vous n'employez pas dans votre projet.
vmm.inc contient beaucoup de macros pour la déclaration de segments dans votre fichier source.
_LTEXT |
VxD_LOCKED_CODE_SEG |
_PTEXT |
VxD_PAGEABLE_CODE_SEG |
_DBOCODE |
VxD_DEBUG_ONLY_CODE_SEG |
_ITEXT |
VxD_INIT_CODE_SEG |
_LDATA |
VxD_LOCKED_DATA_SEG |
_IDATA |
VxD_IDATA_SEG |
_PDATA |
VxD_PAGEABLE_DATA_SEG |
_STEXT |
VxD_STATIC_CODE_SEG |
_SDATA |
VxD_STATIC_DATA_SEG |
_DBODATA |
VxD_DEBUG_ONLY_DATA_SEG |
_16ICODE |
VxD_16BIT_INIT_SEG |
_RCODE |
VxD_REAL_INIT_SEG |
Chaque macro a son homologue de fin. Par exemple, si vous voulez déclarer le segment _LTEXT dans votre fichier source, vous le ferez ainsi :
VxD_LOCKED_CODE_SEG
<Placez votre code ici>
VxD_LOCKED_CODE_ENDS
Squelette d'un VxD
Maintenant que vous savez des connaissances sur les segments des fichier (LE), nous pouvons passer au fichier source. Une chose que vous pouvez voir à propos de la programmation d'un VxD est l'utilisation un peu lourde des macros. Vous trouverez des macros partout en programmant des VxDs. Ça prend une certaine attention pour les utiliser. Les macros nous sont fourni pour cacher certains détails de programmations plutôt ardues et dans certains cas, faire que le code source soit plus clair. Si vous êtes curieux, vous pouvez lire la définition de ces macros dans divers fichiers include (*.inc) tel que vmm.inc.
Voici le code source du squelette d'un VxD :
.386p
include
vmm.inc
DECLARE_VIRTUAL_DEVICE
FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Begin_control_dispatch
FIRSTVXD
End_control_dispatch
FIRSTVXD
end
À première vue, le code source ne ressemble pas à celui d'un code source asm. C'est à cause de l'utilisation des macros. On va analyser ce code source et vous le comprendrez bientôt.
.386p
On dit à l'assembleur que nous voulons employer jeu d'instruction 80386, incluant les instructions privilégiées du CPU. On peut aussi employer .486p
ou .586p.
include
vmm.inc
On doit inclure vmm.inc dans chaque code source d'un VxD parce qu'il contient les définitions des macros qu'on utilise dans le fichier source. On peut en inclure d'autres pour inclure des fichiers si besoin.
DECLARE_VIRTUAL_DEVICE
FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Comme vu précédemment, le VMM apprend tout ce qu'il a besoin de savoir sur un VxD en regardant leBloc Descriptif du Device (matériel)(DDB). Le bloc descripteur du Device est une structure qui contient les informations essentielles à propos du VxD tel que son nom, son n°ID, son entrypoints de ses services VxD (s'ils existent) et cetera. Vous pouvez chercher cette structure dans VMM.inc. Il est déclaré en tant que VxD_Desc_Block. Vous exporterez cette structure dans le fichier .DEF. Il y a 22 membres dans cette structure, mais vous n'aurez généralement besoin d'en remplir que quelques-uns. Ainsi VMM.inc contient une macro qui initialisera et remplira les membres de la structure pour vous. Cette macro est DECLARE_VIRTUAL_DEVICE. Elle présente le format suivant :
Declare_Virtual_Device
Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc,
RefData
Une chose que vous dont vous pouvez vous rendre compte c'est que les labels du code source d'un VxD sont insensibles. Vous pouvez aussi bien utiliser des caractères minuscules que majuscules ou bien une combinaison des deux. On va examiner chaque paramètre de Declare_virtual_device.
-
Name
est le nom du VxD. La longueur maximale est de 8 caractères. Il DOIT être en MAJUSCULES. Le nom doit être unique parmi les VxDs du système. La macro emploie aussi ce nom pour créer le nom du DDB en ajoutant _DDB au nom du DEVICE. Ainsi si vous employez FIRSTVXD comme nom du VxD, alors la macro Declare_Virtual_Device déclarera le nom du DDB en tant que FIRSTVXD_DDB. Rappelez-vous que, vous devrez aussi exporter le DDB dans le fichier .DEF également. Donc leurs noms doivent correspondre dans le fichier source et dans le fichier .DEF.
-
MajorVer
et MinorVer
sont les versions principales et mineures(secondaires) de votre VxD
-
CtrlProc
est le nom de la procédure de contrôle du device de votre VxD. Une procédure de contrôle de Device est une fonction qui reçoit et traite des messages de contrôle pour un VxD. Vous pouvez la représenter comme l'équivalent d'une procédure de fenêtre. Puisque nous emploierons la macro Begin_Control_Dispatch pour créer notre procédure de contrôle de Device, nous devons utiliser son nom standard qui est de la forme VxDName_Control. La macro Begin_Control_Dispatch ajoute _Control au nom qu'on lui a passé (et d'habitude nous lui passons le nom du VxD) donc nous devons spécifier le nom de notre VxD et lui ajouter _Control dans le paramètre CtrlProc.
-
DeviceID
est le n°ID unique (16 bits) de votre VxD. Vous avez besoin de l'ID si et seulement si votre VxD doit rencontrer une des situations suivantes:
-
Votre VxD exporte des services VxD vers d'autres VxDs pour qu'ils s'en servent. Lorsque l'interface 'int 20h' utilise le n°ID du Device pour identifier le VxD, il est impératif que votre VxD doive avoir un n° unique.
-
Votre VxD fait part de son existence aux applications qui tournent en mode réel, pendant l'initialisation, grâce à la fonction 1607h de l'interruption 2F (int 2Fh).
-
Certains logiciels en mode réel (TSR) emploiera la fonction 1605h de l'int 2Fh, pour charger votre VxD.
Si votre VxD n'a pas besoin d'un n°ID de Device unique, vous pouvez spécifier UNDEFINED_DEVICE_ID dans ce champ. Si vous avez besoin d'un n°ID unique, vous devez demander à Microsoft pour en obtenir un.
-
InitOrder
est l'ordre d'initialisation, en bref on charge l'ordre. Le VMM charge les VxDs dans l'ordre indiqué. Chaque VxD aura un numéro d'ordre de charge. Par exemple,
VMM_INIT_ORDER
EQU 000000000H
DEBUG_INIT_ORDER
EQU 000000000H
DEBUGCMD_INIT_ORDER
EQU 000000000H
PERF_INIT_ORDER
EQU 000900000H
APM_INIT_ORDER
EQU 001000000H
Vous pouvez voir que VMM, DEBUG et DEBUGCMD sont les premiers VxDs qui seront chargés, suivit par PERF puis APM.
Le VxD ayant la valeur d'ordre d'initialisation la plus basse est chargé le premier. Si votre VxD exige les services d'autres VxDs pendant l'initialisation, vous devez spécifier une valeur d'ordre d'initialisation plus grande que celle du VxD que vous souhaitez appeler, ainsi au moment où votre VxD est chargé, le VxD est déjà présent en mémoire prêt pour vous. Si votre VxD ne se soucie pas de l'ordre d'initialisation, spécifiez UNDEFINED_INIT_ORDER dans ce paramètre.
-
V86Proc et PMProc
Votre VxD peut exporter des APIs pour qu'ils soient utiliser par des Programmes en mode protégé ou V86. V86Proc et PMProc spécifient les adresses de ces APIs. Rappelez-vous que, les VxDs (Drivers de Matériel Virtuels) existent principalement pour la surveillance des VMs (des Machines Virtuelles) et qu'une VM autre que celle du système VM fait tourner des programmes DOS ou mode protégé. Il est temps de voir que les VxDs peuvent fournir l'appui d'APIs pour des PROGRAMMES en mode protégé ou DOS. Si vous n'exportez pas ces APIs, vous pouvez omettre ces deux paramètres.
-
RefData
représente des données de référence employées par l'Input Output Supervisor (IOS) (le détecteur d'entrée/sorties). La seule occasion où vous emploierez ce paramètre c'est quand vous coderez un Device par morceaux pour l'utiliser avec l'IOS. Vous pouvez omettre ce paramètre si votre VxD n'est pas un Driver en plusieurs morceaux.
Ensuite nous avons la macro Begin_Control_Dispatch.
Begin_control_dispatch
FIRSTVXD
End_control_dispatch
FIRSTVXD
Cette macro et son homologue de fermeture définissent la procédure de contrôle du Device laquelle est la fonction que le VMM appelle quand il y a des messages de contrôle pour votre VxD. Vous devez spécifier la première moitié du nom de la procédure de contrôle, dans notre exemple nous employons FIRSTVXD.
La macro ajoutera _Control au nom que vous avez fourni. Ce nom doit correspondre à celui que vous avez spécifié dans le paramètre CtrlProc de la macro Declare_virtual_device. La procédure de contrôle du Device est toujours dans un segment de type locké (VxD_LOCKED_CODE_SEG). La susdite procédure de contrôle ne fait rien. Vous devez spécifier quels sont les messages de contrôle auxquels votre VxD s'intéresse pouvoir les traiter, c'est exactement le même procédé pour une simple procédure de fenêtre). Employez la macro Control_Dispatch à cette fin.
Control_Dispatch
message, function
Par exemple, si votre VxD traite seulement le message Device_Init, alors votre procédure de contrôle de Device ressemblera à ceci :
Begin_Control_Dispatch
FIRSTVXD
Control_Dispatch
Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit
est le nom de la fonction qui traitera chaque message Device_Init. Vous pouvez nommer votre fonction comme ça vous chante.
Vous terminez le code source du VxD avec la directive end
Pour récapituler, au minimum, un VxD doit présenter un bloc de contrôle de Device et une procédure de contrôle Device.
- un 'bloc de contrôle de Device' pour détecter les messages reçus.
- une 'procédure de contrôle de Device' pour traiter le message et agir en fonction.
Déclarez un bloc de contrôle de Device avec la macro Declare_Virtual_Device et une procédure de contrôle de Device avec la macro Begin_Control_Dispatch. Vous devez exporter le bloc de contrôle de Device en spécifiant son nom sous la directive EXPORTS dans le fichier .DEF.
Assemblage du VxD
Le processus d'assemblage est le même que celui employé pour l'assemblage d'une application win32 normale (un programme 32bit). Vous invoquez ml.exe sur le code asm source et liez ensuite le fichier objet avec link.exe. Les différences sont dans les commutateurs de ligne de commande employés par ml.exe et link.exe
ml
-coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32
firstvxd.asm
-coff
spécifie un format d'objet de type COFF
-c
Assemble seulement. N'appelez pas le linker pour lier le fichier objet, puisque nous appellerons link.exe plus tard avec plus de paramètres.
-Cx
Préserve les chaînes de caractères publics, des labels externes.
-D<text>
définit un texte macro. Par exemple,-DBLD_COFF définit un texte macro BLD_COFF lequel sera employé pour des assemblages conditionnels. Si ça vous intéresse, vous pouvez chercher BLD_COFF dans les fichiers 'include' et voir vous-même quel effet il a sur le processus d'assemblage. Ainsi dans les commutateurs de ligne de commande ci-dessus, trois textes macros sont définis : BLD_COFF, IS_32 et MASM6. Si vous êtes familiers avec le C, ce processus est identique :
#define
BLD_COFF
#define
IS_32
#define
MASM6
link
-vxd
-def:firstvxd.def firstvxd.obj
-vxd
indique que nous voulons construire un VxD pour le fichier objet
-def:<.DEF File> indique le nom du fichier de définition du module du VxD
je trouve plus commode d'employer le makefile mais vous pouvez créer un fichier batch (*.bat) pour automatiser le processus d'assemblage si vous n'aimez pas l'approche du makefile. Voici mon makefile.
NAME=firstvxd
$(NAME).vxd:$(NAME).obj
link -vxd -def:$(NAME).def $(NAME).obj
$(NAME).obj:$(NAME).asm
ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm
[Iczelion's Win32 Assembly Homepage]
Traduit par Morgatte