Introduction
Lorsqu'une application appelle ReadFile ou WriteFile sur un périphérique, la requête ne devient pas immédiatement un simple appel de fonction dans le driver. Windows transforme l'opération en une requête d'E/S représentée par un IRP (I/O Request Packet), puis l'envoie dans la pile de périphériques concernée.
Le but de ce cours est de comprendre le chemin complet d'une lecture ou d'une écriture dans un driver WDM, depuis l'arrivée de l'IRP jusqu'à sa complétion.
1. Les routines IRP_MJ_READ et IRP_MJ_WRITE
Pour recevoir les requêtes de lecture et d'écriture, le driver renseigne la table MajorFunction du DRIVER_OBJECT :
Code: Select all
DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite;
Code: Select all
NTSTATUS DispatchRead(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
);
NTSTATUS DispatchWrite(
PDEVICE_OBJECT DeviceObject,
PIRP Irp
);
- IoGetCurrentIrpStackLocation
- IoCompleteRequest
- IoMarkIrpPending
- IoSetCompletionRoutine
- IoCallDriver
Chaque IRP possède une pile d'emplacements IRP appelée IRP stack. Chaque driver de la pile travaille avec son propre IO_STACK_LOCATION.
Code: Select all
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
Code: Select all
ULONG length = stack->Parameters.Read.Length;
LARGE_INTEGER offset = stack->Parameters.Read.ByteOffset;
Code: Select all
ULONG length = stack->Parameters.Write.Length;
LARGE_INTEGER offset = stack->Parameters.Write.ByteOffset;
3. Où se trouve le buffer ?
Le pointeur utilisé dépend du modèle d'E/S choisi par le driver.
- Buffered I/O : Irp->AssociatedIrp.SystemBuffer
- Direct I/O : Irp->MdlAddress, puis mapping du MDL
- Neither I/O : pointeur utilisateur transmis directement, à manipuler avec beaucoup de précautions
4. Retourner le résultat d'une opération
Deux champs de l'IRP sont fondamentaux :
Code: Select all
Irp->IoStatus.Status
Irp->IoStatus.Information
Exemple :
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = bytesTransferred;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
5. Exemple minimal de lecture
Code: Select all
NTSTATUS DispatchRead(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG length = stack->Parameters.Read.Length;
PUCHAR buffer = (PUCHAR)Irp->AssociatedIrp.SystemBuffer;
ULONG toCopy = min(length, 4UL);
if (buffer && toCopy == 4)
{
buffer[0] = 'T';
buffer[1] = 'E';
buffer[2] = 'S';
buffer[3] = 'T';
}
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = toCopy;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
6. Opération synchrone et opération asynchrone
Une routine de dispatch n'est pas obligée de terminer l'IRP immédiatement.
Si le matériel doit attendre une interruption, un timer, un DPC ou une autre opération asynchrone, le driver peut conserver l'IRP en attente.
Code: Select all
IoMarkIrpPending(Irp);
return STATUS_PENDING;
Code: Select all
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = bytesTransferred;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
Un IRP retourné avec STATUS_PENDING doit réellement être complété plus tard. Un IRP ne doit jamais être complété deux fois.
7. File d'attente des IRP
Un périphérique matériel ne peut pas toujours traiter plusieurs opérations simultanément. Il faut alors mettre les IRP en file.
Le principe est généralement :
Code: Select all
IRP reçu
-> vérifier si le périphérique est libre
-> si libre : lancer l'opération
-> sinon : mettre l'IRP en attente
-> interruption/DPC de fin
-> compléter l'IRP courant
-> démarrer le suivant
8. Propager un IRP dans une pile de drivers
Un filter driver ou un driver intermédiaire ne traite pas forcément lui-même la lecture ou l'écriture. Il peut transmettre l'IRP au driver inférieur.
Code: Select all
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(lowerDevice, Irp);
Code: Select all
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(
Irp,
CompletionRoutine,
context,
TRUE,
TRUE,
TRUE
);
return IoCallDriver(lowerDevice, Irp);
- Compléter deux fois le même IRP.
- Retourner STATUS_SUCCESS alors que l'IRP reste en attente.
- Accéder au mauvais buffer selon le mode d'E/S.
- Ne pas vérifier les longueurs fournies.
- Écrire plus d'octets que la taille demandée.
- Utiliser directement un pointeur User Mode à un IRQL inadapté.
- Oublier de renseigner IoStatus.Information.
Le chemin général d'une lecture ou écriture WDM est :
Code: Select all
ReadFile / WriteFile
|
v
IRP_MJ_READ / IRP_MJ_WRITE
|
v
IO_STACK_LOCATION
|
v
récupération du buffer
|
v
traitement ou mise en attente
|
v
IoStatus.Status / Information
|
v
IoCompleteRequest
