IRP : architecture, I/O Stack, forwarding et completion

Ce cours dédiée au développement de drivers en kernel mode sans Framework

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 144
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

IRP : architecture, I/O Stack, forwarding et completion

Post by Hydraxx »

IRP : architecture, I/O Stack, forwarding et completion

But du cours : maîtriser le trajet normal d’un IRP dans une pile WDM, comprendre exactement ce que contient un IRP, comment lire sa IO_STACK_LOCATION, comment transmettre une requête au driver inférieur, quand utiliser IoSkipCurrentIrpStackLocation ou IoCopyCurrentIrpStackLocationToNext, et comment fonctionnent IoCompleteRequest, les completion routines et le mécanisme STATUS_PENDING.

Ce cours ne réexplique pas DriverEntry, les IRQL, les spinlocks ou les bases d’un DEVICE_OBJECT.

1. Modèle mental d’un IRP

Un IRP représente une opération d’I/O en cours. Il est créé soit par l’I/O Manager, soit par un driver, puis il traverse une pile de drivers.

Code: Select all

Application
   |
   v
I/O Manager
   |
   v
IRP
   |
   v
Driver A
   |
   v
Driver B
   |
   v
Driver C
   |
   v
Périphérique / bus
L’IRP est unique pour la requête, mais chaque driver possède sa propre IO_STACK_LOCATION. C’est cette distinction qui permet à plusieurs drivers de manipuler la même requête sans partager exactement les mêmes paramètres de travail.

2. Ce qu’il faut vraiment retenir de la structure IRP

Les champs les plus utiles en pratique sont :
  • Code: Select all

    Irp->IoStatus.Status
    : résultat NTSTATUS final.
  • Code: Select all

    Irp->IoStatus.Information
    : information supplémentaire, souvent un nombre d’octets.
  • Code: Select all

    Irp->PendingReturned
    : indique qu’un driver inférieur a traité la requête de manière pending.
  • Code: Select all

    Irp->Cancel
    : indique qu’une annulation a été demandée.
  • Code: Select all

    Irp->CancelRoutine
    : routine d’annulation installée.
  • Code: Select all

    Irp->MdlAddress
    : MDL utilisé avec direct I/O.
  • Code: Select all

    Irp->AssociatedIrp.SystemBuffer
    : buffer système utilisé avec buffered I/O.
  • Code: Select all

    Irp->UserBuffer
    : buffer utilisateur dans certains modes.
  • Code: Select all

    Irp->Tail.Overlay.ListEntry
    : peut être utilisé pour placer l’IRP dans une liste.
Attention : tous les champs de l’IRP ne doivent pas être modifiés directement. Une grande partie de la logique WDM passe par des helpers du noyau afin de préserver les invariants de l’I/O Manager.

3. IO_STACK_LOCATION : vue propre à chaque driver

Chaque driver de la pile reçoit une stack location contenant notamment : La stack location courante se récupère avec

Code: Select all

IoGetCurrentIrpStackLocation
.

L’idée est la suivante :

Code: Select all

IRP
 |
 +-- Stack location Driver A
 |
 +-- Stack location Driver B
 |
 +-- Stack location Driver C
Quand l’IRP descend, le pointeur de stack se déplace vers la location suivante. Lors de la complétion, le mouvement se fait en sens inverse.

4. MajorFunction et MinorFunction

Le champ

Code: Select all

MajorFunction
décrit la grande catégorie de requête.

Exemples :

Code: Select all

IRP_MJ_CREATE
IRP_MJ_CLOSE
IRP_MJ_CLEANUP
IRP_MJ_READ
IRP_MJ_WRITE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_PNP
IRP_MJ_POWER
Le

Code: Select all

MinorFunction
sert surtout pour les familles où une seule major function regroupe plusieurs sous-opérations, par exemple PnP ou Power.

5. Le champ Parameters

Code: Select all

stack->Parameters
est une union. La sous-structure à utiliser dépend de la MajorFunction.

Exemples :

Code: Select all

ULONG ioctl =
    stack->Parameters.DeviceIoControl.IoControlCode;

Code: Select all

ULONG readLength =
    stack->Parameters.Read.Length;

Code: Select all

ULONG writeLength =
    stack->Parameters.Write.Length;
Une erreur d’interprétation de Parameters revient à lire la mauvaise union, donc à interpréter des bits sans rapport avec la requête courante.

IoGetCurrentIrpStackLocation

À quoi sert cette API ?

Retourne la IO_STACK_LOCATION correspondant au driver qui traite actuellement l’IRP. C’est généralement la première fonction appelée dans une dispatch routine lorsque le driver a besoin de connaître le type exact de requête ou ses paramètres.

Quand l’utiliser ?

À utiliser dans presque toute dispatch routine qui doit lire MajorFunction, MinorFunction, un IOCTL, la taille d’une lecture, un FileObject ou un autre paramètre porté par la stack location.

Paramètres importants : Valeur de retour / résultat :

Retourne un

Code: Select all

PIO_STACK_LOCATION
.

IRQL / contexte :

Utilisable aux IRQL compatibles avec la routine appelante. La fonction elle-même ne bloque pas.

Propriété de l’IRP / durée de vie :

Le pointeur retourné reste lié à l’IRP. Il ne doit pas être utilisé après que l’IRP a été complété ou transmis d’une manière qui rend la stack courante invalide pour le code appelant.

Pièges classiques :
  • Conserver le pointeur puis utiliser l’IRP après complétion.
  • Lire la mauvaise sous-structure de Parameters.
  • Supposer que FileObject est toujours non NULL.
Exemple minimal :

Code: Select all

PIO_STACK_LOCATION stack =
    IoGetCurrentIrpStackLocation(Irp);

if (stack->MajorFunction == IRP_MJ_DEVICE_CONTROL)
{
    ULONG code =
        stack->Parameters.DeviceIoControl.IoControlCode;
}
IoGetNextIrpStackLocation

À quoi sert cette API ?

Retourne la stack location qui sera utilisée par le driver inférieur. Contrairement à IoGetCurrentIrpStackLocation, cette API sert à préparer la prochaine étape de l’IRP.

Quand l’utiliser ?

À utiliser lorsqu’un driver construit lui-même un IRP ou lorsqu’il doit remplir explicitement les paramètres destinés au driver inférieur.

Paramètres importants : Valeur de retour / résultat :

Retourne un

Code: Select all

PIO_STACK_LOCATION
vers la prochaine location.

IRQL / contexte :

Ne bloque pas.

Propriété de l’IRP / durée de vie :

Le pointeur retourné appartient à la structure de l’IRP. Il ne doit pas être conservé au-delà de la durée de vie de l’IRP.

Pièges classiques :
  • Modifier la next stack alors qu’un helper comme IoCopyCurrentIrpStackLocationToNext suffit.
  • Oublier d’initialiser MajorFunction ou les paramètres requis lorsqu’on crée soi-même un IRP.
Exemple minimal :

Code: Select all

PIO_STACK_LOCATION next =
    IoGetNextIrpStackLocation(Irp);

next->MajorFunction = IRP_MJ_DEVICE_CONTROL;
next->Parameters.DeviceIoControl.IoControlCode = IOCTL_MY_DEVICE;
IoCompleteRequest

À quoi sert cette API ?

Termine le traitement de l’IRP au niveau courant et lance le mécanisme de complétion de l’I/O Manager. Cela peut appeler les completion routines installées dans les drivers supérieurs, puis rendre les ressources de l’IRP à l’I/O Manager lorsque la complétion arrive en haut de la pile.

Quand l’utiliser ?

À utiliser quand le driver est le dernier propriétaire de l’IRP et que l’opération est terminée, en succès, erreur ou annulation.

Paramètres importants :
  • Code: Select all

    Irp
    : IRP à compléter.
  • Code: Select all

    PriorityBoost
    : boost de priorité à appliquer au thread ayant initié l’I/O, souvent

    Code: Select all

    IO_NO_INCREMENT
    pour un IOCTL simple.
Valeur de retour / résultat :

Pas de valeur de retour.

IRQL / contexte :

Peut être appelée à différents IRQL selon le type d’IRP et le chemin de complétion, mais tout le code exécuté autour doit respecter les contraintes du chemin courant.

Propriété de l’IRP / durée de vie :

Après l’appel, le driver doit considérer l’IRP comme n’étant plus sa propriété. Le pointeur ne doit plus être déréférencé.

Pièges classiques :
  • Double completion.
  • Accès à Irp après IoCompleteRequest.
  • Compléter avec STATUS_PENDING.
  • Oublier de remplir IoStatus.Information lorsque l’appelant attend une taille.
Exemple minimal :

Code: Select all

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = bytesTransferred;

IoCompleteRequest(
    Irp,
    IO_NO_INCREMENT
);

return STATUS_SUCCESS;
6. IoStatus.Status et IoStatus.Information

La paire :

Code: Select all

Irp->IoStatus.Status
Irp->IoStatus.Information
forme le résultat principal de la requête.

Exemples de Status :

Code: Select all

STATUS_SUCCESS
STATUS_INVALID_PARAMETER
STATUS_INVALID_DEVICE_REQUEST
STATUS_INSUFFICIENT_RESOURCES
STATUS_CANCELLED
STATUS_DELETE_PENDING
Pour une lecture réussie de 512 octets :

Code: Select all

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 512;
Pour un IOCTL qui ne retourne rien :

Code: Select all

Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
Information n’est pas un booléen. C’est un ULONG_PTR dont la signification dépend du type d’opération.

7. Priority boost de IoCompleteRequest

Le deuxième argument de IoCompleteRequest sert à favoriser le thread qui attend l’I/O.

Exemples :

Code: Select all

IO_NO_INCREMENT
IO_DISK_INCREMENT
IO_KEYBOARD_INCREMENT
IO_MOUSE_INCREMENT
IO_NETWORK_INCREMENT
IO_SERIAL_INCREMENT
IO_SOUND_INCREMENT
Le choix doit correspondre au type réel de périphérique. Pour un contrôle interne ou un IOCTL simple,

Code: Select all

IO_NO_INCREMENT
est souvent utilisé.

8. Transmission d’un IRP dans une pile

Un driver empilé a généralement trois choix :
  • traiter et compléter l’IRP lui-même ;
  • le transmettre sans traitement postérieur ;
  • le transmettre et installer une completion routine.
Le choix entre

Code: Select all

IoSkipCurrentIrpStackLocation
et

Code: Select all

IoCopyCurrentIrpStackLocationToNext
est donc fondamental.

IoSkipCurrentIrpStackLocation

À quoi sert cette API ?

Avance logiquement l’IRP vers la stack location suivante sans copier la location courante. Le driver indique en pratique qu’il ne veut pas conserver sa propre stack location pour un travail de complétion.

Quand l’utiliser ?

À utiliser dans un pass-through simple : le driver ne modifie pas les paramètres, n’installe pas de completion routine et veut uniquement transmettre l’IRP au driver inférieur.

Paramètres importants : Valeur de retour / résultat :

Macro / helper sans valeur de retour utile.

IRQL / contexte :

Ne bloque pas.

Propriété de l’IRP / durée de vie :

Après le futur IoCallDriver, le driver courant ne doit plus supposer qu’il possède l’IRP.

Pièges classiques :
  • Utiliser IoSkipCurrentIrpStackLocation puis installer une completion routine comme si une stack dédiée avait été conservée.
  • Toucher l’IRP après IoCallDriver sans garantie de propriété.
Exemple minimal :

Code: Select all

IoSkipCurrentIrpStackLocation(Irp);

return IoCallDriver(
    devExt->LowerDeviceObject,
    Irp
);
IoCopyCurrentIrpStackLocationToNext

À quoi sert cette API ?

Copie les paramètres pertinents de la stack location courante vers la suivante, afin que le driver inférieur reçoive la même requête tout en laissant au driver courant la possibilité d’utiliser sa propre stack location pour une completion routine.

Quand l’utiliser ?

À utiliser lorsqu’un driver transmet l’IRP mais doit exécuter du code lors de la remontée, ou lorsqu’il souhaite modifier certains paramètres dans la next stack après copie.

Paramètres importants :
  • Code: Select all

    Irp
    : IRP dont la stack courante doit être copiée vers la suivante.
Valeur de retour / résultat :

Pas de valeur de retour.

IRQL / contexte :

Ne bloque pas.

Propriété de l’IRP / durée de vie :

La stack courante reste associée au driver courant et la suivante sera utilisée par le driver inférieur.

Pièges classiques :
  • Copier puis oublier d’installer la completion routine alors qu’on avait besoin d’un post-traitement.
  • Modifier la mauvaise stack après la copie.
Exemple minimal :

Code: Select all

IoCopyCurrentIrpStackLocationToNext(Irp);

IoSetCompletionRoutine(
    Irp,
    CompletionRoutine,
    context,
    TRUE,
    TRUE,
    TRUE
);

return IoCallDriver(
    devExt->LowerDeviceObject,
    Irp
);
IoCallDriver

À quoi sert cette API ?

Envoie l’IRP au driver représenté par le DEVICE_OBJECT cible. En pratique, c’est l’équivalent du passage de relais vers le niveau inférieur de la pile.

Quand l’utiliser ?

À utiliser après avoir préparé correctement la prochaine stack location.

Paramètres importants :
  • Code: Select all

    DeviceObject
    : device cible, souvent le lower device conservé dans la device extension.
  • Code: Select all

    Irp
    : IRP à transmettre.
Valeur de retour / résultat :

Retourne un NTSTATUS. Il peut s’agir de STATUS_PENDING ou d’un autre statut. Cette valeur n’implique pas que l’IRP est encore valide pour le code appelant.

IRQL / contexte :

Peut être appelée à des IRQL élevés selon le type de requête, sous réserve que la pile appelée supporte ce contexte.

Propriété de l’IRP / durée de vie :

Le point critique : le driver inférieur peut compléter l’IRP avant que IoCallDriver ne retourne. Il faut donc considérer l’IRP comme potentiellement déjà parti dès l’appel.

Pièges classiques :
  • Use-after-completion après IoCallDriver.
  • Supposer que STATUS_PENDING signifie forcément que l’IRP existe encore sous la même forme.
  • Appeler IoCallDriver sans préparer la next stack.
Exemple minimal :

Code: Select all

IoCopyCurrentIrpStackLocationToNext(Irp);

return IoCallDriver(
    devExt->LowerDeviceObject,
    Irp
);
9. Exemple comparatif : skip ou copy ?

Cas 1 : le driver ne fait absolument rien après le driver inférieur.

Code: Select all

IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(lower, Irp);
Cas 2 : le driver doit vérifier le résultat au retour.

Code: Select all

IoCopyCurrentIrpStackLocationToNext(Irp);

IoSetCompletionRoutine(
    Irp,
    MyCompletion,
    context,
    TRUE,
    TRUE,
    TRUE
);

return IoCallDriver(lower, Irp);
La différence n’est donc pas esthétique : elle reflète la façon dont la stack location du driver courant sera utilisée pendant la remontée de l’IRP.

IoSetCompletionRoutine

À quoi sert cette API ?

Installe une fonction appelée lorsque l’IRP remonte après traitement par le driver inférieur. Cette routine permet au driver courant d’observer ou de modifier le résultat, libérer un contexte, signaler un événement, ou interrompre temporairement la complétion.

Quand l’utiliser ?

À utiliser lorsque le driver a besoin d’un post-traitement après le driver inférieur.

Paramètres importants :
  • Code: Select all

    Irp
    : IRP concerné.
  • Code: Select all

    CompletionRoutine
    : callback.
  • Code: Select all

    Context
    : contexte privé transmis au callback.
  • Trois booléens : appel sur succès, erreur et annulation.
Valeur de retour / résultat :

Pas de NTSTATUS de retour significatif pour l’installation elle-même.

IRQL / contexte :

Le callback de completion peut s’exécuter à un IRQL allant jusqu’à DISPATCH_LEVEL selon le chemin de l’IRP. Il ne faut donc pas supposer PASSIVE_LEVEL.

Propriété de l’IRP / durée de vie :

Le contexte doit rester valide jusqu’à l’exécution de la completion routine. Si le contexte pointe vers de la mémoire libérée trop tôt, le callback provoquera une corruption.

Pièges classiques :
  • Utiliser un contexte alloué sur une stack qui n’existera plus.
  • Bloquer dans la completion routine sans vérifier l’IRQL.
  • Oublier que le DeviceObject passé au callback peut être NULL dans certains cas de création manuelle d’IRP.
Exemple minimal :

Code: Select all

IoSetCompletionRoutine(
    Irp,
    MyCompletion,
    context,
    TRUE,
    TRUE,
    TRUE
);
STATUS_MORE_PROCESSING_REQUIRED

À quoi sert cette API ?

Ce n’est pas une fonction mais une valeur de retour spéciale d’une completion routine. Elle indique à l’I/O Manager : « arrête la remontée ici, je prends la responsabilité de la suite ».

Quand l’utiliser ?

À utiliser lorsqu’une completion routine doit stopper temporairement la complétion normale, par exemple pour réveiller un thread qui attend et reprendre ensuite la gestion de l’IRP.

Paramètres importants :
  • Aucun paramètre : c’est une valeur NTSTATUS retournée par la completion routine.
Valeur de retour / résultat :

Empêche la continuation normale de la complétion à ce moment.

IRQL / contexte :

La completion routine elle-même peut s’exécuter jusqu’à DISPATCH_LEVEL.

Propriété de l’IRP / durée de vie :

Le driver qui retourne STATUS_MORE_PROCESSING_REQUIRED devient responsable de la suite de la vie de l’IRP : reprise, nettoyage et éventuellement complétion/libération.

Pièges classiques :
  • Retourner STATUS_MORE_PROCESSING_REQUIRED puis oublier l’IRP.
  • Le retourner sans savoir qui libérera les ressources.
Exemple minimal :

Code: Select all

NTSTATUS MyCompletion(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PVOID Context
)
{
    UNREFERENCED_PARAMETER(DeviceObject);

    PKEVENT event = (PKEVENT)Context;

    KeSetEvent(event, IO_NO_INCREMENT, FALSE);

    return STATUS_MORE_PROCESSING_REQUIRED;
}
10. Le mécanisme STATUS_PENDING

Une requête est pending lorsqu’elle ne sera pas terminée avant le retour de la routine courante.

Exemple :

Code: Select all

Dispatch
   |
   +--> IoMarkIrpPending
   |
   +--> mise en queue
   |
   +--> return STATUS_PENDING

... plus tard ...

DPC / thread / matériel
   |
   +--> IoCompleteRequest
Le pending n’est donc pas une erreur. Il décrit un changement de temporalité : la requête survivra après le retour de la dispatch routine.

IoMarkIrpPending

À quoi sert cette API ?

Marque dans l’IRP que la requête a été rendue asynchrone et qu’elle sera complétée plus tard. Ce marquage permet aussi la propagation correcte de l’état pending à travers les completion routines.

Quand l’utiliser ?

À utiliser lorsqu’un driver garde l’IRP au-delà du retour de sa dispatch routine, ou dans une completion routine lorsque PendingReturned doit être propagé.

Paramètres importants : Valeur de retour / résultat :

Pas de valeur de retour.

IRQL / contexte :

Ne bloque pas.

Propriété de l’IRP / durée de vie :

Marquer l’IRP pending ne donne pas magiquement la propriété à vie : le driver doit toujours savoir quel composant possède l’IRP après chaque transfert.

Pièges classiques :
  • Oublier de le marquer avant de conserver l’IRP.
  • Le faire systématiquement sans comprendre PendingReturned.
  • Marquer pending un IRP déjà engagé dans une complétion qui ne doit plus être modifiée.
Exemple minimal :

Code: Select all

IoMarkIrpPending(Irp);
QueueIrp(Irp);
return STATUS_PENDING;
11. PendingReturned

Le champ :

Code: Select all

Irp->PendingReturned
indique qu’un driver inférieur a signalé que la requête était pending.

Pattern classique dans une completion routine :

Code: Select all

if (Irp->PendingReturned)
{
    IoMarkIrpPending(Irp);
}
Pourquoi ?

Parce que le pending doit parfois être propagé vers le niveau supérieur afin que le thread ou le driver à l’origine de la requête ne considère pas qu’elle s’est terminée de manière synchrone.

Le couple PendingReturned + IoMarkIrpPending est un mécanisme de propagation, pas une simple convention de style.

12. IoCallDriver peut compléter immédiatement

Le cas suivant est dangereux :

Code: Select all

status = IoCallDriver(lower, Irp);

// ERREUR POTENTIELLE :
Irp->IoStatus.Status = ...
Le driver inférieur a le droit de traiter et compléter l’IRP de manière synchrone avant le retour de IoCallDriver.

Donc :
  • ne jamais supposer que l’IRP est toujours vivant après l’appel ;
  • si un post-traitement est nécessaire, utiliser une completion routine ;
  • si le driver doit attendre, employer un pattern synchronisé avec événement et STATUS_MORE_PROCESSING_REQUIRED.
IoBuildSynchronousFsdRequest

À quoi sert cette API ?

Construit un IRP de type lecture, écriture, flush ou shutdown destiné à un autre driver, avec support d’un événement et d’un IO_STATUS_BLOCK pour une utilisation synchrone.

Quand l’utiliser ?

À utiliser lorsqu’un driver a besoin de demander une opération standard à un autre device et veut pouvoir attendre sa fin.

Paramètres importants :
  • MajorFunction.
  • DeviceObject cible.
  • Buffer.
  • Length.
  • StartingOffset.
  • Event.
  • IoStatusBlock.
Valeur de retour / résultat :

Retourne un PIRP ou NULL en cas d’échec d’allocation.

IRQL / contexte :

Le thread qui attend doit être dans un contexte permettant l’attente, généralement PASSIVE_LEVEL.

Propriété de l’IRP / durée de vie :

L’I/O Manager gère une grande partie de la vie de l’IRP construit par ce helper. Le driver doit respecter le modèle de synchronisation et ne pas libérer arbitrairement l’IRP.

Pièges classiques :
  • Attendre à DISPATCH_LEVEL.
  • Libérer l’IRP comme s’il venait d’IoAllocateIrp.
  • Oublier de vérifier NULL.
Exemple minimal :

Code: Select all

KEVENT event;
IO_STATUS_BLOCK iosb;

KeInitializeEvent(
    &event,
    NotificationEvent,
    FALSE
);

PIRP irp = IoBuildSynchronousFsdRequest(
    IRP_MJ_READ,
    DeviceObject,
    Buffer,
    Length,
    &Offset,
    &event,
    &iosb
);
IoBuildDeviceIoControlRequest

À quoi sert cette API ?

Construit un IRP IRP_MJ_DEVICE_CONTROL ou IRP_MJ_INTERNAL_DEVICE_CONTROL destiné à un autre driver. C’est le helper classique pour envoyer un IOCTL depuis le noyau.

Quand l’utiliser ?

À utiliser lorsqu’un driver veut invoquer un IOCTL d’un autre device et éventuellement attendre le résultat.

Paramètres importants :
  • IoControlCode.
  • DeviceObject.
  • InputBuffer / InputBufferLength.
  • OutputBuffer / OutputBufferLength.
  • InternalDeviceIoControl.
  • Event.
  • IoStatusBlock.
Valeur de retour / résultat :

Retourne un PIRP ou NULL.

IRQL / contexte :

Le chemin d’attente doit respecter PASSIVE_LEVEL.

Propriété de l’IRP / durée de vie :

Le helper construit l’IRP selon un modèle géré avec l’I/O Manager ; ne pas le traiter comme un IRP librement alloué avec IoAllocateIrp.

Pièges classiques :
  • Mauvaise valeur InternalDeviceIoControl.
  • Attendre dans un contexte arbitraire.
  • Supposer que les buffers sont toujours copiés de la même manière indépendamment de la méthode IOCTL.
Exemple minimal :

Code: Select all

PIRP irp = IoBuildDeviceIoControlRequest(
    IOCTL_MY_DEVICE,
    DeviceObject,
    inBuffer,
    inLength,
    outBuffer,
    outLength,
    FALSE,
    &event,
    &iosb
);
IoAllocateIrp

À quoi sert cette API ?

Alloue un IRP brut avec un nombre de stack locations choisi par le driver. C’est l’option la plus flexible et aussi celle qui donne le plus de responsabilités.

Quand l’utiliser ?

À utiliser lorsque les helpers IoBuild... ne conviennent pas et que le driver doit contrôler explicitement la structure de l’IRP.

Paramètres importants :
  • Code: Select all

    StackSize
    : nombre de stack locations.
  • Code: Select all

    ChargeQuota
    : comportement historique de quota, généralement FALSE dans les drivers modernes.
Valeur de retour / résultat :

Retourne un PIRP ou NULL.

IRQL / contexte :

Allocation noyau non bloquante au sens classique, mais respecter les contraintes documentées du chemin courant.

Propriété de l’IRP / durée de vie :

L’IRP appartient au driver qui l’alloue jusqu’à ce que son modèle de transfert/complétion en décide autrement. Il doit être libéré avec IoFreeIrp lorsque le driver reste responsable de sa destruction.

Pièges classiques :
  • Mauvais StackSize.
  • Oublier d’initialiser la prochaine stack.
  • Oublier IoFreeIrp.
  • Libérer deux fois.
Exemple minimal :

Code: Select all

PIRP irp = IoAllocateIrp(
    DeviceObject->StackSize,
    FALSE
);

if (irp == NULL)
{
    return STATUS_INSUFFICIENT_RESOURCES;
}
13. IoFreeIrp

Code: Select all

IoFreeIrp(Irp);
sert à libérer un IRP alloué manuellement avec IoAllocateIrp.

Ne pas appeler IoFreeIrp sur un IRP appartenant à l’I/O Manager simplement parce qu’on a reçu son pointeur dans une dispatch routine.

14. Trois modèles de dispatch fondamentaux

A. Complétion locale

Code: Select all

NTSTATUS DispatchLocal(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
)
{
    UNREFERENCED_PARAMETER(DeviceObject);

    NTSTATUS status = STATUS_SUCCESS;

    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = 0;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}
B. Pass-through pur

Code: Select all

NTSTATUS DispatchPass(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
)
{
    PDEVICE_EXTENSION devExt =
        (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    IoSkipCurrentIrpStackLocation(Irp);

    return IoCallDriver(
        devExt->LowerDeviceObject,
        Irp
    );
}
C. Pass-through avec post-traitement

Code: Select all

NTSTATUS DispatchObserve(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
)
{
    PDEVICE_EXTENSION devExt =
        (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(
        Irp,
        CompletionRoutine,
        devExt,
        TRUE,
        TRUE,
        TRUE
    );

    return IoCallDriver(
        devExt->LowerDeviceObject,
        Irp
    );
}
15. Erreurs graves à reconnaître immédiatement
  • Double completion : IoCompleteRequest appelé deux fois.
  • Use-after-completion : lecture/écriture dans l’IRP après complétion.
  • Mauvaise propriété : deux composants pensent posséder le même IRP.
  • Mauvais forwarding : next stack mal préparée.
  • Mauvais pending : requête gardée sans IoMarkIrpPending.
  • Mauvais contexte : attente bloquante à un IRQL incompatible.
  • Mauvais contexte de completion : pointer vers une variable locale qui a disparu.
16. API essentielles de ce cours
17. Résumé logique

Toujours se poser ces questions :

Code: Select all

1. Qui a créé l'IRP ?
2. Qui le possède maintenant ?
3. Quelle stack location est active ?
4. Le driver traite-t-il ou forward-t-il ?
5. Une completion routine est-elle nécessaire ?
6. L'IRP peut-il survivre après le retour ?
7. Qui le complétera ?
8. Qui le libérera ?
Si ces huit réponses sont claires, le chemin normal de l’IRP est généralement maîtrisé.

Who is online

Users browsing this forum: No registered users and 1 guest