Annotations IRQL pour les pilotes

Tous les développeurs de pilotes doivent prendre en compte les niveaux de demande d’interruption (IRQL). Un IRQL est un entier compris entre 0 et 31 ; PASSIVE_LEVEL, DISPATCH_LEVEL et APC_LEVEL sont normalement désignés symboliquement, et les autres par leurs valeurs numériques. L’élévation et l’abaissement de l’IRQL doivent suivre une discipline de pile stricte. Une fonction doit viser à revenir au même IRQL auquel elle a été appelée. Les valeurs IRQL doivent être non décroissantes dans la pile. Et une fonction ne peut pas abaisser l’IRQL sans l’augmenter au préalable. Les annotations IRQL sont destinées à aider à appliquer ces règles.

Lorsque le code du pilote comporte des annotations IRQL, les outils d’analyse du code peuvent faire une meilleure inférence sur la plage de niveaux auxquels une fonction doit s’exécuter et trouver plus précisément les erreurs. Par exemple, vous pouvez ajouter des annotations qui spécifient l’IRQL maximal auquel une fonction peut être appelée ; si une fonction est appelée à un IRQL supérieur, les outils d’analyse du code peuvent identifier les incohérences.

Les fonctions de pilote doivent être annotées avec autant d’informations sur l’IRQL qui peuvent être appropriées. Si les informations supplémentaires sont disponibles, cela aide les outils d’analyse du code dans la vérification ultérieure de la fonction appelante et de la fonction appelée. Dans certains cas, l’ajout d’une annotation est un bon moyen de supprimer un faux positif. Certaines fonctions, telles qu’une fonction utilitaire, peuvent être appelées à n’importe quel IRQL. Dans ce cas, l’absence d’annotation IRQL est l’annotation appropriée.

Lors de l’annotation d’une fonction pour IRQL, il est particulièrement important de considérer la façon dont la fonction peut évoluer, et pas seulement son implémentation actuelle. Par exemple, une fonction telle qu’implémentée peut fonctionner correctement à une valeur IRQL supérieure à celle prévue par le concepteur. Bien qu’il soit tentant d’annoter la fonction en fonction de ce que fait réellement le code, le concepteur peut être conscient des exigences futures, telles que la nécessité de réduire le nombre maximal d’IRQL pour une amélioration future ou la configuration système requise en attente. L’annotation doit être dérivée de l’intention du concepteur de fonctions, et non de l’implémentation réelle.

Vous pouvez utiliser les annotations du tableau suivant pour indiquer l’IRQL correct pour une fonction et ses paramètres. Les valeurs IRQL sont définies dans Wdm.h.

Annotation IRQL Description
_IRQL_requires_max_(irql) L’irql est l’IRQL maximal auquel la fonction peut être appelée.
_IRQL_requires_min_(irql) L’irql est l’IRQL minimal auquel la fonction peut être appelée.
_IRQL_requires_(irql) La fonction doit être entrée à l’IRQL spécifié par irql.
_IRQL_raises_(irql) La fonction se ferme au niveau de l’irql spécifié, mais elle peut uniquement être appelée pour augmenter (et non plus) l’IRQL actuel.
_IRQL_saves_ Le paramètre annoté enregistre l’IRQL actuel pour la restaurer ultérieurement.
_IRQL_restores_ Le paramètre annoté contient une valeur IRQL de IRQL_saves qui doit être restaurée lorsque la fonction retourne.
_IRQL_saves_global_(kind, param) L’IRQL actuel est enregistré dans un emplacement interne aux outils d’analyse du code à partir duquel l’IRQL doit être restauré. Cette annotation est utilisée pour annoter une fonction. L’emplacement est identifié par genre et affiné par param. Par exemple, OldIrql peut être le type et FastMutex peut être le paramètre qui contenait cette ancienne valeur IRQL.
_IRQL_restores_global_(kind, param) L’IRQL enregistré par la fonction annotée avec IRQL_saves_global est restauré à partir d’un emplacement interne aux outils d’analyse du code.
_IRQL_always_function_min_(valeur) La valeur IRQL est la valeur minimale à laquelle la fonction peut réduire l’IRQL.
_IRQL_always_function_max_(valeur) La valeur IRQL est la valeur maximale à laquelle la fonction peut élever l’IRQL.
_IRQL_requires_same_ La fonction annotée doit entrer et quitter au même IRQL. La fonction peut modifier l’IRQL, mais elle doit restaurer l’IRQL à sa valeur d’origine avant de quitter.
_IRQL_uses_cancel_ Le paramètre annoté est la valeur IRQL qui doit être restaurée par une fonction de rappel DRIVER_CANCEL. Dans la plupart des cas, utilisez plutôt l’annotation IRQL_is_cancel.

Annotations pour DRIVER_CANCEL

Il existe une différence entre les annotations _IRQL_uses_cancel_ et _IRQL_is_cancel_. L’annotation _IRQL_uses_cancel_ spécifie simplement que le paramètre annoté est la valeur IRQL qui doit être restaurée par une fonction de rappel DRIVER_CANCEL. L’annotation _IRQL_is_cancel_ est une annotation composite qui se compose de _IRQL_uses_cancel_ plus plusieurs autres annotations qui garantissent le comportement correct d’une fonction utilitaire de rappel DRIVER_CANCEL. En soi, l’annotation _IRQL_uses_cancel_ n’est utile qu’occasionnellement ; par exemple, si le reste des obligations décrites par _IRQL_is_cancel_ ont déjà été remplies d’une autre manière.

Annotation IRQL Description
_IRQL_is_cancel_ Le paramètre annoté est l’IRQL passé dans le cadre de l’appel à une fonction de rappel DRIVER_CANCEL. Cette annotation indique que la fonction est un utilitaire appelé à partir des routines Cancel et qui remplit les conditions requises pour les fonctions DRIVER_CANCEL, y compris la libération du verrou de rotation d’annulation.

Interaction des annotations IRQL

Les annotations de paramètres IRQL interagissent plus entre elles que les autres annotations, car la valeur IRQL est définie, réinitialisée, enregistrée et restaurée par les différentes fonctions appelées.

Spécification d’IRQL maximal et minimal

Les annotations _IRQL_requires_max_ et _IRQL_requires_min_ spécifient que la fonction ne doit pas être appelée à partir d’un IRQL supérieur ou inférieur à la valeur spécifiée. Par exemple, lorsque PREfast voit une séquence d’appels de fonction qui ne modifient pas l’IRQL, s’il en trouve un avec une valeur _IRQL_requires_max_ inférieure à une _IRQL_requires_min_ proche, il signale un avertissement sur le deuxième appel qu’il rencontre. L’erreur peut effectivement se produire lors du premier appel ; le message indique où l’autre moitié du conflit s’est produite.

Si les annotations d’une fonction mention l’IRQL et ne s’appliquent pas explicitement _IRQL_requires_max_, l’outil Analyse du code applique implicitement l’annotation _IRQL_requires_max_(DISPATCH_LEVEL), ce qui est généralement correct avec de rares exceptions. L’application implicite de cette valeur en tant que valeur par défaut élimine beaucoup d’encombres d’annotations et rend les exceptions beaucoup plus visibles.

L’annotation _IRQL_requires_min_(PASSIVE_LEVEL) est toujours implicite, car l’IRQL ne peut pas descendre plus bas ; par conséquent, il n’existe aucune règle explicite correspondante concernant l’IRQL minimal. Très peu de fonctions ont à la fois une limite supérieure autre que DISPATCH_LEVEL et une limite inférieure autre que PASSIVE_LEVEL.

Certaines fonctions sont appelées dans un contexte dans lequel la fonction appelée ne peut pas élever en toute sécurité l’IRQL au-dessus d’un certain maximum ou, le plus souvent, ne peut pas l’abaisser en toute sécurité en dessous d’un certain minimum. Les annotations _IRQL_always_function_max_ et _IRQL_always_function_min_ aident PREfast à rechercher les cas où cela se produit involontairement.

Par exemple, les fonctions de type DRIVER_STARTIO sont annotées avec _IRQL_always_function_min_(DISPATCH_LEVEL). Cela signifie que lors de l’exécution d’une fonction DRIVER_STARTIO, il s’agit d’une erreur de réduire l’IRQL sous DISPATCH_LEVEL. D’autres annotations indiquent que la fonction doit être entrée et sortie à DISPATCH_LEVEL.

Spécification d’un IRQL explicite

Utilisez l’annotation _IRQL_raises_ ou _IRQL_requires_ pour aider PREfast à mieux signaler une incohérence qui est découverte avec des annotations _IRQL_requires_max_ ou _IRQL_requires_min_, car elle connaît alors l’IRQL.

L’annotation _IRQL_raises_ indique qu’une fonction retourne avec l’IRQL défini sur une nouvelle valeur. Lorsque vous utilisez l’annotation _IRQL_raises_, elle définit également efficacement l’annotation _drv_maxFunctionIRQL sur la même valeur IRQL. Toutefois, si la fonction élève l’IRQL au-dessus de la valeur finale, puis l’abaisse à la valeur finale, vous devez ajouter une annotation _IRQL_always_function_max_ explicite après l’annotation _IRQL_raises_ pour autoriser la valeur IRQL supérieure.

Augmentation ou diminution de l’IRQL

L’annotation _IRQL_raises_ indique que la fonction doit être utilisée uniquement pour déclencher l’IRQL et ne doit pas être utilisée pour réduire l’IRQL, même si la syntaxe de la fonction l’autorise. KeRaiseIrql est un exemple de fonction qui ne doit pas être utilisée pour réduire l’IRQL.

Enregistrement et restauration d’IRQL

Utilisez les annotations _IRQL_saves_ et _IRQL_restores_ pour indiquer que l’IRQL actuel (s’il est connu exactement ou seulement approximativement) est enregistré ou restauré dans le paramètre annoté ou restauré à partir du paramètre annoté.

Certaines fonctions enregistrent et restaurent implicitement l’IRQL. Par exemple, la fonction système ExAcquireFastMutex enregistre l’IRQL dans un emplacement opaque associé à l’objet mutex rapide identifié par le premier paramètre ; l’IRQL enregistré est restauré par la fonction ExReleaseFastMutex correspondante pour cet objet mutex rapide. Pour indiquer explicitement ces actions, utilisez les annotations _IRQL_saves_global_ et _IRQL_restores_global_. Les paramètres de type et de param indiquent où la valeur IRQL est enregistrée. L’emplacement où la valeur est enregistrée n’a pas besoin d’être spécifié avec précision, tant que les annotations qui enregistrent et restaurent la valeur sont cohérentes.

Maintenance du même IRQL

Vous devez annoter toutes les fonctions créées par votre pilote qui modifient l’IRQL à l’aide de l’annotation _IRQL_requires_same_ ou de l’une des autres annotations IRQL pour indiquer que la modification dans IRQL est attendue. En l’absence d’annotations indiquant une modification de l’IRQL, les outils d’analyse du code émettent un avertissement pour toute fonction qui ne se quitte pas à la même IRQL à laquelle la fonction a été entrée. Si la modification dans IRQL est prévue, ajoutez l’annotation appropriée pour supprimer l’erreur. Si la modification dans IRQL n’est pas prévue, le code doit être corrigé.

Enregistrement et restauration d’IRQL pour les routines d’annulation d’E/S

Utilisez l’annotation _IRQL_uses_cancel_ pour indiquer que le paramètre annoté est la valeur IRQL qui doit être restaurée par une fonction de rappel DRIVER_CANCEL. Cette annotation indique que la fonction est un utilitaire appelé à partir des routines d’annulation et qu’elle répond aux exigences qui ont été effectuées sur DRIVER_CANCEL fonctions (autrement dit, elle décharge l’obligation pour l’appelant).

Par exemple, la déclaration suivante correspond au type de fonction de rappel DRIVER_CANCEL. L’un des paramètres est l’IRQL qui doit être restauré par cette fonction. Les annotations indiquent toutes les exigences d’une fonction cancel.

// Define driver cancel routine type.  //    
__drv_functionClass(DRIVER_CANCEL)  
_Requires_lock_held_(_Global_cancel_spin_lock_)  
_Releases_lock_(_Global_cancel_spin_lock_)  
_IRQL_requires_min_(DISPATCH_LEVEL)  
_IRQL_requires_(DISPATCH_LEVEL)  
typedef  
VOID  
DRIVER_CANCEL (  
    _Inout_ struct _DEVICE_OBJECT *DeviceObject,  
    _Inout_ _IRQL_uses_cancel_ struct _IRP *Irp  
    );  
  
typedef DRIVER_CANCEL *PDRIVER_CANCEL;  

Annotations SAL 2.0 pour les pilotes