Retour menu KMD / Retour menu assembleur

Le sous-système d'E/S

4.1 Le Gestionnaire d'E/S
4.2 Le programme de contrôle pour le driver VirtToPhys
 Code source : KmdKit\examples\simple\VirtToPhys

4.1 Le gestionnaire d'E/S

A la différence du mode utilisateur, où nous pouvons appeler des fonctions directement depuis des dll, simplement en utilisant leurs adresses, en mode kernel un tel scénario serait extrêmement dangereux du point de vue de la stabilité du système. Ainsi, le système fournit un moyen intermédiaire pour communiquer avec le mode kernel. Un tel moyen est un Gestionnaire d'E/S (Entrée/Sortie) qui est un des composants du sous-système d'E/S. Ce gestionnaire d'E/S relit les applications et les composants système avec les périphériques, et défnii l'infrastructure qui supporte les modules de gestion de périphérique.

Un schéma simplifié de la façon dont le Gestionnaire d'E/S interagit avec les applications en mode utilisateur et les modules de gestion de périphérique est donné en Figure 4-1.



Figure 4-1. Architecture simplifiée du sous-système d'E/S

De la figure ci-dessus découle le fait qu'absolument tous les appels des applications en mode utilisateur vers les périphériques et, par la même, vers les modules de gestions de périphériques sont sous le contrôle du Gestionnaire d'E/S.

Le code en mode utilisateur est obligé de demander les opérations d'E/S au périphérique. Seulement et uniquement le périphérique. Le driver doit créer un périphériques (ou plusieurs) à contrôler. Dans notre cas, ce périphérique est un périphérique virtuel. Bien sûr créer un périphérique ne signifie pas la création d'un nouveau périphérique réel. Cela veut simplement dire qu'un nouvel objet va être créer en mémoire (nommément un objet de périphérique) représentant un périphérique physique ou virtuel sur le système et décrivant ses caractéristiques.

Lors de la création du périphérique, le driver dit au Gestionnaire d'E/S :"Voilà le périphérique que je dois contrôler. Si tu reçois des requêtes d'E/S vers ce périphérique, envoie-les moi et je m'occuperais du reste." Le driver sait seulement comment gérer les requêtes d'E/S pour son (ou ses) périphérique(s). La seule responsabilité du Gestionnaire d'E/S est de créer et diriger les requêtes d'E/S vers le périphérique approprié. Le code en mode utilisateur ne sait (et ne devrait) pas du tout savoir quel driver prend en charge tel ou tel périphérique(s) particulier(s).

4.2 Le programme de contrôle pour le driver VirtToPhys

4.2.1 Code source du programme de contrôle

A strictement parler ce code combine le programme de contrôle du service, responsable de l'enregistrement et du démarrage du driver, et le programme client pour communiquer avec le périphérique.

 

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                   
; VirtToPhys.asm - Programme de contrôle du driver VirtToPhys                                       
;                                                                                                   
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              I N C L U D E   F I L E S                                            
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\advapi32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\advapi32.lib

include \masm32\include\winioctl.inc

include \masm32\Macros\Strings.mac

include common.inc

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        C O D E                                                    
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      BigNumToString                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

BigNumToString proc uNum:UINT, pszBuf:LPSTR

; Cette fonction accèpte un nombre et le convertit en
; un chaîne de caractères, insérant les guillemets à la bonne place. 

local acNum[32]:CHAR
local nf:NUMBERFMT

    invoke wsprintf, addr acNum, $CTA0("%u"), uNum

    and nf.NumDigits, 0
    and nf.LeadingZero, FALSE
    mov nf.Grouping, 3
    mov nf.lpDecimalSep, $CTA0(".")
    mov nf.lpThousandSep, $CTA0(" ")
    and nf.NegativeOrder, 0
    invoke GetNumberFormat, LOCALE_USER_DEFAULT, 0, addr acNum, addr nf, pszBuf, 32

    ret

BigNumToString endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       start                                                       
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

start proc uses esi edi

local hSCManager:HANDLE
local hService:HANDLE
local acModulePath[MAX_PATH]:CHAR
local _ss:SERVICE_STATUS
local hDevice:HANDLE

local adwInBuffer[NUM_DATA_ENTRY]:DWORD
local adwOutBuffer[NUM_DATA_ENTRY]:DWORD
local dwBytesReturned:DWORD

local acBuffer[256+64]:CHAR
local acThis[64]:CHAR
local acKernel[64]:CHAR
local acUser[64]:CHAR
local acAdvapi[64]:CHAR

local acNumber[32]:CHAR

    invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
    .if eax != NULL
        mov hSCManager, eax

        push eax
        invoke GetFullPathName, $CTA0("VirtToPhys.sys"), \
                                sizeof acModulePath, addr acModulePath, esp
        pop eax

        invoke CreateService, hSCManager, $CTA0("VirtToPhys"), \
                              $CTA0("Virtual To Physical Address Converter"), \
                              SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, \
                              SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, addr acModulePath, \
                              NULL, NULL, NULL, NULL, NULL

        .if eax != NULL
            mov hService, eax

            ; La procedure DriverEntry du driver va être appelée
            invoke StartService, hService, 0, NULL
            .if eax != 0

                ; Le driver va recevoir de packet de requête d'E/S [I/O request packet (IRP)]de type IRP_MJ_CREATE 
                invoke CreateFile, $CTA0("\\\\.\\slVirtToPhys"), GENERIC_READ + GENERIC_WRITE, \
                                0, NULL, OPEN_EXISTING, 0, NULL

                .if eax != INVALID_HANDLE_VALUE
                    mov hDevice, eax

                    lea esi, adwInBuffer
                    assume esi:ptr DWORD
                    invoke GetModuleHandle, NULL
                    mov [esi][0*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("kernel32.dll", szKernel32)
                    mov [esi][1*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("user32.dll", szUser32)
                    mov [esi][2*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("advapi32.dll", szAdvapi32)
                    mov [esi][3*(sizeof DWORD)], eax

                    lea edi, adwOutBuffer
                    assume edi:ptr DWORD
                    ; Le driver va recevoir des IRP de type IRP_MJ_DEVICE_CONTROL 
                    invoke DeviceIoControl, hDevice, IOCTL_GET_PHYS_ADDRESS, \
                                            esi, sizeof adwInBuffer, \
                                            edi, sizeof adwOutBuffer, \
                                            addr dwBytesReturned, NULL

                    .if ( eax != 0 ) && ( dwBytesReturned != 0 )

                        invoke GetModuleFileName, [esi][0*(sizeof DWORD)], \
                                                  addr acModulePath, sizeof acModulePath

                        lea ecx, acModulePath[eax-5]
                        .repeat
                            dec ecx
                            mov al, [ecx]
                        .until al == '\'
                        inc ecx
                        push ecx

                        CTA0 "%s \t%08Xh\t%08Xh   ( %s )\n", szFmtMod

                        invoke BigNumToString, [edi][0*(sizeof DWORD)], addr acNumber
                        pop ecx
                        invoke wsprintf, addr acThis, addr szFmtMod, ecx, \
                                         [esi][0*(sizeof DWORD)], \
                                         [edi][0*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][1*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acKernel, addr szFmtMod, addr szKernel32, \
                                         [esi][1*(sizeof DWORD)], \
                                         [edi][1*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][2*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acUser, addr szFmtMod, addr szUser32, \
                                         [esi][2*(sizeof DWORD)], \
                                         [edi][2*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][3*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acAdvapi, addr szFmtMod, addr szAdvapi32, \
                                         [esi][3*(sizeof DWORD)], \
                                         [edi][3*(sizeof DWORD)], addr acNumber

                        invoke wsprintf, addr acBuffer, \
                                         $CTA0("Module:\t\tVirtual:\t\tPhysical:\n\n%s\n%s%s%s"), \
                                         addr acThis, addr acKernel, addr acUser, addr acAdvapi

                        assume esi:nothing
                        assume edi:nothing
                        invoke MessageBox, NULL, addr acBuffer, $CTA0("Modules Base Address"), \
                                           MB_OK + MB_ICONINFORMATION
                    .else
                        invoke MessageBox, NULL, $CTA0("Can't send control code to device."), NULL, \
                                           MB_OK + MB_ICONSTOP
                    .endif
                    ; Le driver va recevoir des IRP de type IRP_MJ_CLOSE 
                    invoke CloseHandle, hDevice
                .else
                    invoke MessageBox, NULL, $CTA0("Device is not present."), NULL, MB_ICONSTOP
                .endif
                ; La procédure de déchargement (Unload) de notre driver va être appeléé.
                invoke ControlService, hService, SERVICE_CONTROL_STOP, addr _ss
            .else
                invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_OK + MB_ICONSTOP
            .endif
            invoke DeleteService, hService
            invoke CloseServiceHandle, hService
        .else
            invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_OK + MB_ICONSTOP
        .endif
        invoke CloseServiceHandle, hSCManager
    .else
        invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), NULL, \
                           MB_OK + MB_ICONSTOP
    .endif

    invoke ExitProcess, 0

start endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                   
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end start

Sans considérer le code qui prépare les données à être envoyées vers le périphérique, et le code responsable de formatage et de l'affichage de la sortie du périphérique, il y une chose nouvelle ici - seulement trois appels : CreateFile, DeviceIoControl et CloseHandle. Toutes ces fonctions acceptent le handle du périphérique (Je répète, pas le driver) comme argument.

4.2.2 Objet de périphérique

Après son chargement, le driver VirToPhys créé un périphérique nommé "devVirtToPhys" (Le préfixe "dev" n'est pas nécessaire, mais je l'ai ajouté avec raison - je vous dirais pourquoi plus tard).

Le nom du périphérique est placé dans l'espace de nom du Gestionnaire d'objet (Object Manager namespace). Le Gestionnaire d'objet est un composant système protégeant et surveillant les objets. Par convention, les objets de périphérique sont placés dans le répertoire \Device, non accessible par les applications utilisant l'API Win32.

Pour avoir une vision de l'espace de nom maintenu par le Gestionnaire d'objet, vous pouvez utiliser mon Windows Object Explorer (WinObjEx) (disponible sur ce site) ou Object Viewer par Mark Russinovich ( http://www.sysinternals.com/ ).

Pour voir les objets créés par VirToPhys sur votre ordinateur, faites simplement fonctionner VirtToPhys.exe, mais ne fermez pas la fenêtre de dialogue.



Figure 4-2. Objet de périphérique devVirtToPhys dans l'espace de nom du Gestionnaire d'objet



Figure 4-3. Propriétés d'objet de périphérique de VirtToPhys

4.2.3 Objet driver

L'objet de driver VirtToPhys (Je n'utilise pas de préfixe pour le nom) est situé dans le répertoire \Driver.



Figure 4-4. Objet de driver VirtToPhys dans l'espace de nom du Gestionnaire d'objet

4.2.4 Objet de lien symbolique

Les noms de périphériques internes ne peuvent être utilisés par les applications Win32 (tous les répertoires exceptés "\BaseNamedObjects" et "\??", sont invisible aux programmes utilisateurs) - au lieu de cela le nom du périphérique apparaîtra dans un répertoire spécial situé dans l'espace de nom du Gestionnaire d'objet, "\??'. Ce répertoire contient des liens symboliques au nom du périphérique interne réel. Les modules de gestion de périphérique sont responsables de la création de ces liens dans le répertoire, ainsi leurs périphériques seront accessibles aux applications Win32.

Donc notre driver doit rendre possible, à un code en mode utilisateur, l'ouverture vers un objet de périphérique, pour cela il doit créer un lien symbolique dans le répertoire "\??" qui pointe sur l'objet de périphérique situé dans le répertoire "\Device". Par la suite, quand le programme appelant veut un handle de périphérique, le Gestionnaire d'E/S peut retrouver l'objet de périphérique directement.

Vous pouvez examiner ou même changer ces liens depuis le mode utilisateur avec les fonction Win32 QueryDosDevice et DefineDosDevice

Une fois ouvert le répertoire "\??", vous verrez qu'il comporte de nombreux liens symboliques. Avant Windows NT 4, ce répertoire s'appelait \DosDevices : il fut renommé en "\??" pour des raisons de performances - ce nom vient en première place dans l'ordre alphabétique.

Pour des raisons de compatibilité ascendante vous trouverez, à la racine du répertoire de l'espace de nom du Gestionnaire d'objet, un lien "\DosDevice". Celui -ci est un lien vers le répertoire "\??".

Le driver VirtToPhys créé un lien symbolique "slVirtToPhys" vers le périphérique "devVirtToPhys" dans le répertoire "\??" , dont la valeur est la chaîne de caractère "\Device\devVirtToPhys". Ici j'ai utilisé le préfixe "dev".



Figure 4-5. Lien symbolique slVirtToPhys dans l'epcace de nom du Gestionnaire d'objet



Figure 4-6. Propriétés du lien symbolique slVirtToPhys

J'ai ajouté les préfixes uniquement pour distinguer les différents types d'objet. Ceci pour montrer qu'il n'est pas nécessaire pour le nom de périphérique et pour le lien symbolique (bien que soit le cas d'habitude) de coïncider avec le nom du driver. La chose importante ici est que le nom du lien symbolique doit spécifier un nom de périphérique valide. Encore un autre point important - Il ne peut y avoir deux objets avec les mêmes nom dans un répertoire d'objet unique, comme il est impossible d'avoir deux noms de fichiers identiques dans un seul et même répertoire.

Ainsi, à la sortie de la fonction StartService nous avons trois nouveaux objets:

Si vous vous rappelez, dans la seconde partie de ce document, je vous ais promis de vous dire ce qu'était "\??" comme dans "\??\C:\masm32\...". Donc "\??\C:" est un lien symbolique a un périphérique interne nommé "\Device\HarddiskVolume1", qui est le premier disque dur sur le système.

 

4.2.5 Objet de fichier

Revenons à notre code source. Une fois que le driver est démarré nous voulons bien sûr l'appeler. Pour ce faire, nous devons simplement ouvrir un handle de fichier vers le driver en appelant la fonction CreateFile:

La description de CreateFile prend une place importante au niveau de la documentation. Seulement une petite partie nous concernant à propos des drivers sera présentée.

CreateFile proto stdcall     lpFileName:LPCSTR,            dwDesiredAccess:DWORD, \
                             dwShareMode:DWORD,            lpSecurityAttributes:LPVOID, \
                             dwCreationDistribution:DWORD, dwFlagsAndAttributes:DWORD, \
                             hTemplateFile:HANDLE

Malgré son nom la fonction peut créer ou ouvrir (beaucoup de fonctions Create* fonctionne de cette façon) des fichiers, mais aussi des objets. Microsoft aurait du nommer cette fonction CreateObject. Le périphérique peut en effet apparaître comme un objet.

Paramètre Description

lpFileName

Pointe vers une chaîne de caractère terminée par NULL qui spécifie le nom du périphérique à ouvrir. Le lien symbolique pointant vers l'objet de périphérique pour être plus exact.

dwDesiredAccess

Spécifie le type d'accès au périphérique

 

Nous aurons besoin de deux valeurs:

GENERIC_READ

Spécifie un accès en lecture. Des données peuvent être lues depuis le périphérique.

GENERIC_WRITE

Spécifie un accès en écriture. Des données peuvent être écrites sur le périphérique.

Ces drapeaux (flags) peuvent être combinés ensemble.

dwShareMode

Un jeu de drapeaux de bits spécifiant la façon dont le périphérique peut être partagé.

Trois valeurs peuvent nous être utiles :

0

Le driver ne peut être partagé. Toute opération sur le driver échouera, jusqu'a ce que le handle soit fermé. Bien que la documentation dise cela, je n'ai pu le réaliser en utilisant 0.

Si vous devez partage le périphérique, utilisez les valeurs suivantes :

FILE_SHARE_READ

Toute opération en ouverture sur le périphérique réussira seulement si un accès en lecture est demandé.

FILE_SHARE_WRITE

Toute opération en écriture sur le périphérique réussira seulement si un accès en écriture est demandé.

lpSecurityAttributes

Pointeur vers une structure SECURITY_ATTRIBUTES.

Comme aucune protection spéciale n'est nécessaire dans notre cas, et comme nous ne voulons pas que le handle retourné soit hérité par un processus enfant, nous spécifions NULL.

dwCreationDistribution

Spécifie l'action à prendre pour les fichiers qui existent et quelle action prendre pour les fichiers qui n'existent pas.

Pour les périphériques, ce paramètre doit toujours être OPEN_EXISTING.

dwFlagsAndAttributes

Spécifie les attributs et les drapeaux.

Ce paramètre doit toujours être égale à 0.

hTemplateFile

Spécifie un handle vers un fichier template .

Pour les périphériques ce paramètre doit toujours être NULL.

Si CreateFile crée ou ouvre avec succès le périphérique spécifié, un handle vers ce périphérique est retourné ; Autrement, INVALID_HANDLE_VALUE est retourné.

La plupart des fonctions de Windows qui retourne un handle retourne NULL quand elles n'ont pas fonctionnées correctement. A la différence, CreateFile retourne INVALID_HANDLE_VALUE défini comme étant -1.

Nous appelons CreateFile comme suit:

                invoke CreateFile, $CTA0("\\\\.\\slVirtToPhys"), GENERIC_READ + GENERIC_WRITE, \
                                0, NULL, OPEN_EXISTING, 0, NULL

J'espère que tout est clair pour les cinq derniers paramètres. Le second paramètre est une combinaison des drapeaux GENERIC_READ + GENERIC_WRITE, car nous voulons à la fois envoyer des données au périphérique mais aussi recevoir le résultat de son travail.

Examinons à présent le premier paramètre. C'est un pointeur vers le nom du lien symbolique, de la forme "\\.\slVirtToPhys". Le "\\.\" est un alias Win32 défini pour l'ordinateur local. La fonction CreateFile est une enveloppe autour de l'autre fonction NtCreateFile (qui ce situe dans \%SystemRoot%\System32\ntdll.dll), qui à son tour accède au service système correspondant (ne confondez pas avec les processus de services Win32).

Un service système est un point d'entrée dans le kernel depuis un sous-système d'environnement. Un "dispatch" de service système est déclenché lors de l'exécution d'une int 2Eh (Windows NT/2000) ou de l'instruction sysenter (Windows XP/2003) sur les processeurs X86. Exécuter cette instruction résulte en une "trap" (interruption) provoquant la transition du thread s'exécutant dans le mode kernel et l'entrée dans le dispatcher de service système.

NtCreateFile substitue un alias pour l'ordinateur local "\\.\" avec "\??" (Ainsi "\\.\slVirtToPhys" devient "\?? \slVirtToPhys") et appel la fonction du kernel ObOpenObjectByName. Au trvaers du lien symbolique ObOpenObjectByName trouve l'objet "\Device\devVirtToPhys" et retourne un pointeur vers celui-ci (ainsi le lien symbolique visible depuis le code en mode utilisateur est utilisé par le Gestionnaire d'objet pour une conversion en un nom interne). En utilisant ce pointeur NtCreateFile crée le nouveau fichier objet représentant le périphérique et retourne son handle.

Le système d'exploitation abstrait toutes les requêtes d'E/S comme des opérations sur des fichiers virtuels, cachant le fait que la cible d'une opération d'E/S n'est peut être pas un périphérique avec une structure de fichier. Le driver convertit les requêtes depuis un fichier virtuel en requêtes matérielles spécifiques. Toute donnée lu ou écrite est vue comme un simple flux d'octets dirigé vers ces fichiers virtuels.

Avant que CreateFile ne retourne, le gestionnaire d'E/S crée une IRP de type IRP_MJ_CREATE et l'envoi vers le driver pour traitement. La routine définie pour procéder à ce genre d'opération sur les IRP exécutera le même contexte de thread que l'initiateur des requêtes d'E/S (celui qui appel CreateFile) à IRQL = PASSIVE_LEVEL. Si cette routine de driver retourne avec succès, le Gestionnaire d'objet crée un handle pour l'objet de fichier dans la table des handles du processus, et le handle se propage à rebours de la chaîne (logique) appelante, pour finalement atteindre l'application comme une valeur de retour pour CreateFile.

Le nouvel objet de fichier est l'objet exécutif et n'entre pas de l'espace de nom du Gestionnaire d'objet. Vous pouvez utiliser Process Explorer par Mark Russinovich ( http://www.sysinternals.com ) pour explorer de tels objets.



Figure 4-7. Objets de fichier



Figure 4-8. Propriétés de l'objet de fichier.

Résumons nous. Donc "\\.\slVirtToPhys" devient un lien symbolique "\??\slVirtToPhys" et est finalement utilisé pour trouver le périphérique approprié "\Device\devVirtToPhys". Depuis l'objet de périphérique DEVICE_OBJECT on extrait l'information pour savoir quel driver est responsable de la gestion de ce périphérique. Alors le Gestionnaire d'E/S envoi des requêtes IRP_MJ_CREATE directement au driver. De cette façon le driver sait qu'un morceau de code essai d'accéder à son périphérique. Si le driver veut autoriser l'accès il retourne "succès". Maintenant le Gestionnaire d'objet crée un handle pour l'objet "fichier virtuel" représentant le périphérique et le retourne au code en mode utilisateur.

Les Handles et les liens symboliques servent de pointeurs indirects pour les ressources systèmes; cette indirection empêche les programmes applicatifs de jouer directement avec les structures de donnée du système.

4.2.6 Communiquer avec le périphérique

                .if eax != INVALID_HANDLE_VALUE
                    mov hDevice, eax

Si CreateFile retourne un handle de périphérique valide, nous sauvons ce dernier dans la variable hDevice. A présent nous pouvons communiquer avec les périphériques en appelant ReadFile, WriteFile, et DeviceIoControl. DeviceIoControl est une fonction universelle pour communiquer avec les périphériques. En voici le prototype :

DeviceIoControl proto stdcall hDevice:HANDLE,         dwIoControlCode:DWORD, \
                              lpInBuffer:LPVOID,      nInBufferSize:DWORD, \
                              lpOutBuffer:LPVOID,     nOutBufferSize:DWORD, \
                              lpBytesReturned:LPVOID, lpOverlapped:LPVOID

Cette fonction accepte même plus de paramètre que CreateFile, mais elle est somme toute assez simple.

Paramètre Description

hDevice

Handle vers le périphérique ;

dwIoControlCode

-Code de contrôle indiquant quelle opération de contrôle faire ;

Nous discuterons de ceci un peu plus tard.

lpInBuffer

Pointeur vers un buffer contenant les données requises pour faire l'opération. Ce paramètre peut être NULL si le paramètre dwIoControlCode spécifie une opération que ne requière aucune donnée en entrée ;

nInBufferSize

Spécifie la taille, en octets, du buffer pointé par lpInBuffer ;

lpOutBuffer

Pointeur vers un buffer recevant les données de sortie de l'opération. Ce paramètre peut être NULL si le paramètre dwIoControlCode spécifie une opération qui ne produit aucune donnée en sortie.

nOutBufferSize

Spécifie la taille, en octets, du buffer pointé par lpOutBuffer;

lpBytesReturned

Pointeur vers une variable qui reçoit la taille, en octets, des données stockées dans le buffer pointé par lpOutBuffer;

lpOverlapped

Pointeur vers une structure OVERLAPPED.

Cette structure est requise pour aider à contrôler une opération asynchrone. Comme nous voulons seulement appeler notre driver de manière synchrone (DeviceIoControl ne retournera pas tant que la routine du driver appropriée ne soit complétée), nous passons NULL.

4.2.7 Codes de contrôle d'E/S

Un module de gestion de périphérique peut être considéré un ensemble de fonction en mode kernel. Un code de contrôle d'E/S défini quel fonction sera appelée. L'argument dwIoControlCode de la fonction DeviceIoControl est utilisé à cette fin. Il indique l'opération de contrôle que nous voulons réalisée et de quelle façon elle doit l'être.

Le code de contrôle est une contant numérique sur 32 bits pouvant être définie en utilisant la macro CTL_CODE, partie intégrante des fichiers include winioctl.inc et ntddk.inc.



Figure 4-9. Disposition du code de contrôle d'E/S

Champ de bit Description

DeviceType

Le champ device type (16 bits) [type de périphérique] indique le type de périphérique qui implémente cette opération de contrôle.

Les valeurs dans la fourchette 0 - 7FFFh sont réservées par Microsoft. Les valeurs allant de 8000h - 0FFFFh sont utilisables pour les développeurs de nouveau type de driver en mode kernel.

Dans \include\w2k\ntddk.inc vous pourrez trouver un jeu de constantes symboliques FILE_DEVICE_XXX dont les valeurs sont dans la fourchette réservée par Microsoft. Nous utiliserons FILE_DEVICE_UNKNOWN. Cependant vous pouvez définir une autre FILE_DEVICE_XXX.

Access

Le code d'accès (2 bits) indique les droits d'accès qu'une application requière sur le handle de périphérique pour effectuer l'opération de contrôle.

Comme ce champ est de seulement d'une taille de deux bits, nous avons 4 possibilités :

FILE_ANY_ACCESS (0)

Droits d'accès maximum. Le driver procédera à l'opération demandée pour tout programme appelant qui posséderait un handle pour son périphérique.

FILE_READ_ACCESS (1)

Droits d'accès en lecture. Avec ce droit d'accès, le driver de périphérique transfère des données depuis le périphérique vers le buffer mémoire.

FILE_WRITE_ACCESS (2)

Droits d'accès en écriture. Avec ce droit d'accès, le driver de périphérique transfère des données depuis le buffer mémoire vers le périphérique.

FILE_READ_ACCESS or FILE_WRITE_ACCESS (3)

Droits d'accès à la fois en lecture et en écriture. Avec ce droit d'accès, le driver de périphérique transfère des données entre le buffer mémoire et le périphérique (dans les deux sens).

Function

Le code de fonction (12 bits) indique précisément quelle opération de contrôle ce code décrit.

Il peut prendre des valeurs entre 800h - 0FFFh pour les codes de contrôle privés d'E/S. Les valeurs dans la fourchette 0 - 7FFh sont réservés par Microsoft pour les codes de contrôles publics d'E/S.

Method

La méthode de "buffering" (2 bits) indique comment le Gestionnaire d'E/S va s'occuper des buffers d'entrée et de sortie fournis par l'application.

Ce champ est de seulement d'une taille de deux bits, nous avons donc 4 possibilités (définies en tant que constantes) :

METHOD_BUFFERED (0)

buffered E/S;

METHOD_IN_DIRECT (1)

E/S directe ;

METHOD_OUT_DIRECT (2)

METHOD_NEITHER (3)

neither E/S. => E/S sans gestion

Nous aborderons un peu plus tard, et plus en détail, la gestion des buffers. A présent la chose importante est de savoir que la méthode de "buffered E/S" est la plus sûre car c'est le système qui supervise la gestion des buffers, celui ci gérant les opérations de copie en mémoire. Les drivers utilisent communément les E/S par buffers quand le programme appelant requière de petit transfères de mémoire (quelques pages), pour des raisons complexes dues à la gestion mémoire (à ce sujet voir MDL "Memory descriptor list"). Nous utiliserons la méthode de "buffered E/S" dans le driver VirtToPhys.

Vous pouvez former des codes de contrôle d'E/S manuellement, mais il est plus pratique d'utiliser la macro CTL_CODE, qui offre un mécanisme pour générer des valeurs IOCTL. La voici :

CTL_CODE MACRO DeviceType:=<0>, Function:=<0>, Method:=<0>, Access:=<0>
    EXITM %(((DeviceType) SHL 16) OR ((Access) SHL 14) OR ((Function) SHL 2) OR (Method))
ENDM

Comme je m'ai déjà dit, la macro CTL_CODE est définie à la fois dans winioctl.inc, qui est inclue dans le code source du programme de contrôle de service, et dans ntddk.inc, inclus dans le code source du driver.

Comme nous utilisons les constantes NUM_DATA_ENTRY et DATA_SIZE, ainsi que code de contrôle d'E/S IOCTL_GET_PHYS_ADDRESS à la fois dans le programme de contrôle de service et dans le driver, ils sont placés dans un fichier include nommé common.inc. Ainsi, tout changement dans ce fichier sera répercuté dans les deux codes source.

NUM_DATA_ENTRY         equ 4
DATA_SIZE              equ (sizeof DWORD) * NUM_DATA_ENTRY
IOCTL_GET_PHYS_ADDRESS equ CTL_CODE(FILE_DEVICE_UNKNOWN, 800h, METHOD_BUFFERED, FILE_READ_ACCESS + FILE_WRITE_ACCESS)

4.2.8 Echange de données

Retournons à présent au code source du driver.

                    lea esi, adwInBuffer
                    assume esi:ptr DWORD
                    invoke GetModuleHandle, NULL
                    mov [esi][0*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("kernel32.dll", szKernel32)
                    mov [esi][1*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("user32.dll", szUser32)
                    mov [esi][2*(sizeof DWORD)], eax
                    invoke GetModuleHandle, $CTA0("advapi32.dll", szAdvapi32)
                    mov [esi][3*(sizeof DWORD)], eax

Ici nous remplissons le buffer adwInBuffer avec les adresses virtuelles à convertir.

                    lea edi, adwOutBuffer
                    assume edi:ptr DWORD
                    invoke DeviceIoControl, hDevice, IOCTL_GET_PHYS_ADDRESS, \
                                            esi, sizeof adwInBuffer, \
                                            edi, sizeof adwOutBuffer, \
                                            addr dwBytesReturned, NULL

En appelant DeviceIoControl nous passons un buffer au driver qui convertira chaque adresse virtuelle en adresse physique.

                    .if ( eax != 0 ) && ( dwBytesReturned != 0 )

                        invoke GetModuleFileName, [esi][0*(sizeof DWORD)], \
                                                  addr acModulePath, sizeof acModulePath

                        lea ecx, acModulePath[eax-5]
                        .repeat
                            dec ecx
                            mov al, [ecx]
                        .until al == '\'
                        inc ecx
                        push ecx

                        CTA0 "%s \t%08Xh\t%08Xh   ( %s )\n", szFmtMod

                        invoke BigNumToString, [edi][0*(sizeof DWORD)], addr acNumber
                        pop ecx
                        invoke wsprintf, addr acThis, addr szFmtMod, ecx, \
                                         [esi][0*(sizeof DWORD)], \
                                         [edi][0*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][1*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acKernel, addr szFmtMod, addr szKernel32, \
                                         [esi][1*(sizeof DWORD)], \
                                         [edi][1*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][2*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acUser, addr szFmtMod, addr szUser32, \
                                         [esi][2*(sizeof DWORD)], \
                                         [edi][2*(sizeof DWORD)], addr acNumber

                        invoke BigNumToString, [edi][3*(sizeof DWORD)], addr acNumber
                        invoke wsprintf, addr acAdvapi, addr szFmtMod, addr szAdvapi32, \
                                         [esi][3*(sizeof DWORD)], \
                                         [edi][3*(sizeof DWORD)], addr acNumber

                        invoke wsprintf, addr acBuffer, \
                                         $CTA0("Module:\t\tVirtual:\t\tPhysical:\n\n%s\n%s%s%s"), \
                                         addr acThis, addr acKernel, addr acUser, addr acAdvapi

                        assume esi:nothing
                        assume edi:nothing
                        invoke MessageBox, NULL, addr acBuffer, $CTA0("Modules Base Address"), \
                                           MB_OK + MB_ICONINFORMATION
                    .else
                        invoke MessageBox, NULL, $CTA0("Can't send control code to device."), NULL, \
                                           MB_OK + MB_ICONSTOP
                    .endif

Si DeviceIoControl retourne avec succès dwBytesReturned est égale au nombre d'octets dont le buffer adwOutBuffer aura été rempli par le driver. Ensuite il nous faut prendre les données et les afficher à l'utilisateur. Je suis sûr que vous êtes assez futé pour comprendre ce qui se passe ici. Les séquences d'échappement utilisées dans $CTA0 sont des séquences communes (voir \Macros\Strings.mac pour les détails)



Figure 4-10. La sortie de VirtToPhys.exe

4.2.9 Nettoyage

                    invoke CloseHandle, hDevice

A présent nous avons juste à fermer le handle ouvert sur le périphérique. A ce point, le Gestionnaire d'E/S envoi deux IRP au driver de périphérique. Dans un premier temps il s'agit de IRP_MJ_CLEANUP pour dire au driver que le handle de périphérique va être fermé. Ensuite vient IRP_MJ_CLOSE, pour dire au driver que le handle de périphérique a bien été fermé. Vous pouvez aussi évitez la fermeture du handle de périphérique en retournant un code d'erreur depuis la routine responsable de la gestion de la requête IRP_MJ_CLEANUP. La routine du driver définie à cet effet (gestion de ce type d'IRP) exécutera le même contexte de thread que l'initiateur des requêtes d'E/S (celui qui appelle CloseHandle) à IRQL = PASSIVE_LEVEL.

Nous parlerons dans la prochaine partie de la façon dont le driver gère les IRP.

Pour faire fonctionner le driver sur des environnements précédent les dernières versions de Windows NT (donc NT 4) vous devrez changer "\??" en "\DosDevices" et recompiler le driver, car comme il à été mentionné, sur Windows NT4 le répertoire "\??" s'appelait "\DosDevices".


Copyright © 2002-2004 Four-F, four-f@mail.ru

Traduction par Neitsa , tzcorporation@hotmail.com