Salut l’équipe
Dans la suite de l’introduction au kernel, on va maintenant attaquer la structure concrète d’un driver WDM.
Jusqu’ici, on a vu la grande idée :
- le système est en contrôle
- les drivers sont passifs
- les requêtes circulent sous forme d’IRP
- plusieurs couches peuvent coopérer dans une pile de drivers
Autrement dit :
- comment il est représenté
- comment il est chargé
- comment il expose un device
- comment Windows lui envoie des requêtes
- comment une application user-mode peut finir par dialoguer avec lui
Le but est d’installer les bons modèles mentaux.
1) Le bon modèle mental général
Le vrai modèle ressemble à ceci :
Code: Select all
Application user-mode
↓
CreateFile / DeviceIoControl / ReadFile / WriteFile
↓
I/O Manager
↓
IRP
↓
Device Object visé
↓
Driver stack
↓
Driver concerné
↓
Traitement / relais / complétion
un driver n’est pas juste “du code qui tourne en kernel”.
C’est du code :
- intégré dans un modèle système précis
- représenté par des objets noyau
- appelé par le kernel selon des événements déterminés
- souvent inséré dans une pile de drivers déjà existante
Le PnP Manager coordonne la détection des devices et le chargement des drivers associés.
Schéma mental :
Code: Select all
Hardware détecté
↓
PnP Manager
↓
Bus driver
↓
création d’un PDO
↓
recherche du driver adapté
↓
chargement du driver
↓
AddDevice
↓
création du FDO
↓
stack prête
IRP PnP importants à reconnaître :
Code: Select all
IRP_MN_QUERY_DEVICE_RELATIONSCode: Select all
IRP_MN_QUERY_IDCode: Select all
IRP_MN_START_DEVICE
PDO — Physical Device Object
Le PDO est créé par le bus driver.
Il représente la présence du device du point de vue du bus.
FDO — Functional Device Object
Le FDO est généralement créé par le function driver.
C’est lui qui porte la logique principale de gestion du device.
Filter drivers
Un filter driver s’insère dans la stack pour :
- observer
- modifier
- enrichir
- bloquer
- surveiller certaines requêtes
Code: Select all
Upper filter
↓
Function driver (FDO)
↓
Lower filter
↓
Bus driver / PDO
↓
Hardware
Quand une application demande une opération, la requête ne va pas forcément dans “ton driver directement”.
Elle est routée vers un device object, puis traverse potentiellement plusieurs couches.
Donc un driver doit souvent être capable de :
- traiter une requête
- la relayer plus bas
- éventuellement la modifier
- compléter correctement l’opération
5.1) DRIVER_OBJECT
Le
Code: Select all
DRIVER_OBJECTIl contient notamment :
- les pointeurs de dispatch dans
Code: Select all
MajorFunction[] Code: Select all
DriverUnloadCode: Select all
DriverExtension- d’autres informations globales sur le driver
Le
Code: Select all
DEVICE_OBJECTDonc :
Code: Select all
DRIVER_OBJECT = le driver global
DEVICE_OBJECT = une instance concrète de device
6) DriverEntry — point d’entrée du driver
Prototype classique :
Code: Select all
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
C’est ici qu’on configure notamment :
- les dispatch routines dans
Code: Select all
MajorFunction[] Code: Select all
DriverUnload- éventuellement
Code: Select all
DriverExtension->AddDevice
Code: Select all
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyDeviceControl;
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
Code: Select all
DriverEntryCode: Select all
mainC’est une routine d’initialisation qui enregistre des callbacks.
7) MajorFunction[] — le routage des IRP
Code: Select all
MajorFunction[]Il permet de dire au système :
- quelle routine appeler pour
Code: Select all
IRP_MJ_CREATE - laquelle appeler pour
Code: Select all
IRP_MJ_CLOSE - laquelle appeler pour
Code: Select all
IRP_MJ_DEVICE_CONTROL
Code: Select all
IRP reçu
↓
MajorFunction[type]
↓
ta routine
Code: Select all
CreateFile(...) -> IRP_MJ_CREATE
CloseHandle(...) -> IRP_MJ_CLOSE
DeviceIoControl(...) -> IRP_MJ_DEVICE_CONTROL
Exemple de prototype :
Code: Select all
NTSTATUS MyCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
- le concerné
Code: Select all
DEVICE_OBJECT - le à traiter
Code: Select all
IRP
- analyser la requête
- décider quoi faire
- remplir le statut
- compléter l’IRP ou le relayer selon le cas
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
un IRP reçu doit être traité correctement, puis complété ou relayé selon le contrat.
9) DriverUnload
Prototype :
Code: Select all
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
- supprimer les symbolic links créés
- supprimer les device objects créés
- libérer certaines ressources globales
- faire le cleanup final
Code: Select all
AddDevice = setup du device
DriverUnload = cleanup global / final
Code: Select all
AddDeviceCode: Select all
DriverObject->DriverExtension->AddDevice = AddDevice;
- quand le driver doit être associé à une nouvelle instance de device
Code: Select all
DriverEntry = init globale du driver
AddDevice = init d’une instance de device
Code: Select all
AddDeviceCode: Select all
IoCreateDevice- initialisation de la device extension
Code: Select all
IoAttachDeviceToDeviceStack- initialisation de certains flags
- éventuellement création d’un point d’accès user-mode
intégrer ton driver au device et à la stack du noyau.
11) IoCreateDevice
Exemple de style :
Code: Select all
status = IoCreateDevice(
DriverObject,
sizeof(MY_DEVICE_EXTENSION),
&DeviceName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&DeviceObject
);
- crée un objet device dans le noyau
- réserve une zone pour la device extension
- lie cet objet à ton driver
Elle crée un objet noyau représentant ton device.
12) DeviceExtension — ta mémoire privée par device
Le
Code: Select all
DEVICE_OBJECTCode: Select all
DeviceObject->DeviceExtension
Exemple :
Code: Select all
typedef struct _MY_DEVICE_EXTENSION {
PDEVICE_OBJECT LowerDeviceObject;
UNICODE_STRING LinkName;
BOOLEAN Started;
} MY_DEVICE_EXTENSION, *PMY_DEVICE_EXTENSION;
- des pointeurs importants
- l’état du device
- le pointeur vers le lower device
- des locks
- des noms
- des buffers
- des informations PnP / power
Cette API sert à attacher ton
Code: Select all
DEVICE_OBJECTAppel classique :
Code: Select all
ext->LowerDeviceObject = IoAttachDeviceToDeviceStack(DeviceObject, PhysicalDeviceObject);
- parce qu’un driver WDM vit rarement seul
- il est souvent inséré dans une driver stack
14) Nommer le device et l’exposer
Créer un
Code: Select all
DEVICE_OBJECTIl faut distinguer :
- le nom noyau du device
- l’accès user-mode pratique
Code: Select all
\Device\MonDriver
15) UNICODE_STRING
Code: Select all
UNICODE_STRINGPrototype simplifié :
Code: Select all
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
Code: Select all
RtlInitUnicodeString(&u, L"\Device\MonDriver");
Exemple :
Code: Select all
UNICODE_STRING DeviceName;
UNICODE_STRING LinkName;
RtlInitUnicodeString(&DeviceName, L"\Device\MonDriver");
RtlInitUnicodeString(&LinkName, L"\DosDevices\MonDriver");
IoCreateSymbolicLink(&LinkName, &DeviceName);
Code: Select all
\Device\MonDriver
↓
\DosDevices\MonDriver
↓
\\.\MonDriver côté user-mode
17) Comment une application ouvre le driver
Code: Select all
HANDLE h = CreateFile(
L"\\.\MonDriver",
GENERIC_READ | GENERIC_WRITE,
0,
nullptr,
OPEN_EXISTING,
0,
nullptr
);
Code: Select all
CreateFile("\\.\MonDriver")
↓
I/O Manager
↓
IRP_MJ_CREATE
↓
MajorFunction[IRP_MJ_CREATE]
↓
MyCreate
Code: Select all
CreateFile18) DeviceIoControl — envoyer une commande à un driver
Schéma :
Code: Select all
Application
↓
DeviceIoControl(...)
↓
I/O Manager
↓
IRP_MJ_DEVICE_CONTROL
↓
MajorFunction[IRP_MJ_DEVICE_CONTROL]
↓
MyDeviceControl
- demander un état
- configurer un comportement
- envoyer un petit buffer
- récupérer une réponse
Il demande au système d’envoyer cette commande à ce device.
19) IoGetCurrentIrpStackLocation
Dans une routine comme
Code: Select all
IRP_MJ_DEVICE_CONTROLCode: Select all
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
Code: Select all
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
Code: Select all
IRP20) Les flags du DEVICE_OBJECT
Trois flags importants au début :
Code: Select all
DO_BUFFERED_IOCode: Select all
DO_DIRECT_IOCode: Select all
DO_DEVICE_INITIALIZING
Code: Select all
DO_BUFFERED_IOCode: Select all
DeviceObject->Flags |= DO_BUFFERED_IO;
Code: Select all
DO_DIRECT_IOplus avancé, plus proche du chemin direct mémoire.
Code: Select all
DO_DEVICE_INITIALIZINGil faut l’enlever une fois l’initialisation terminée.
Code: Select all
DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
21) APIs clés à connaître à ce stade
Code: Select all
DriverEntryCode: Select all
DriverUnloadCode: Select all
DriverObject->MajorFunction[...]Code: Select all
IoCreateDeviceCode: Select all
IoAttachDeviceToDeviceStackCode: Select all
UNICODE_STRINGCode: Select all
RtlInitUnicodeStringCode: Select all
IoCreateSymbolicLinkCode: Select all
IoDeleteSymbolicLinkCode: Select all
CreateFileCode: Select all
DeviceIoControlCode: Select all
IoGetCurrentIrpStackLocationCode: Select all
IoCompleteRequest
Code: Select all
Le système charge le driver
↓
DriverEntry configure le driver
↓
Le système associe le driver à un device
↓
AddDevice crée / initialise le DEVICE_OBJECT
↓
Le driver s’attache à la stack
↓
Le driver expose éventuellement un symbolic link
↓
L’application ouvre le driver avec CreateFile
↓
Le système envoie IRP_MJ_CREATE
↓
L’application envoie un DeviceIoControl
↓
Le système envoie IRP_MJ_DEVICE_CONTROL
↓
Le driver traite et complète l’IRP
Code: Select all
#include <ntddk.h>
NTSTATUS MyCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate;
DriverObject->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
- initialise
Code: Select all
DriverEntry - route les IRP
Code: Select all
MajorFunction[] - une dispatch routine doit compléter l’IRP
- un driver existe dans Windows via des objets noyau
- les deux plus importants au début sont et
Code: Select all
DRIVER_OBJECTCode: Select all
DEVICE_OBJECT - n’est pas une boucle principale, il initialise
Code: Select all
DriverEntry - est le routeur principal des IRP
Code: Select all
MajorFunction[] - sert à créer et intégrer une instance de device
Code: Select all
AddDevice - est ta mémoire privée par device
Code: Select all
DeviceExtension - le user-mode passe généralement par un symbolic link
- et
Code: Select all
CreateFiledeviennent des IRPCode: Select all
DeviceIoControl - un IRP simple doit être correctement complété
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
Tu vois maintenant que :
- un driver est représenté par un
Code: Select all
DRIVER_OBJECT - un device est représenté par un
Code: Select all
DEVICE_OBJECT - le driver s’initialise via
Code: Select all
DriverEntry - les requêtes sont routées via
Code: Select all
MajorFunction[] - les devices sont créés et intégrés via
Code: Select all
AddDevice - l’état privé vit dans la
Code: Select all
DeviceExtension - l’accès user-mode passe souvent par un symbolic link
- et
Code: Select all
CreateFiledeviennent des IRP côté noyauCode: Select all
DeviceIoControl - une requête doit être correctement terminée
Résumé ultra condensé
WDM repose sur des objets noyau et des callbacks.
Code: Select all
DRIVER_OBJECTCode: Select all
DEVICE_OBJECTCode: Select all
DriverEntryCode: Select all
MajorFunction[]Code: Select all
AddDeviceLa
Code: Select all
DeviceExtensionCode: Select all
IoCreateDeviceCode: Select all
IoAttachDeviceToDeviceStackCode: Select all
UNICODE_STRINGCode: Select all
IoCreateSymbolicLinkCode: Select all
CreateFileCode: Select all
DeviceIoControlUn IRP simple doit être correctement complété avec
Code: Select all
IoStatusCode: Select all
IoCompleteRequest