Meilleures pratiques de développement du pilote d’équipe Surface

Introduction

Ces directives de développement de pilotes ont été développées pendant de nombreuses années par les développeurs de pilotes chez Microsoft. Au fil du temps, lorsque les conducteurs se sont mal comportés et que des leçons ont été apprises, ces leçons ont été capturées et ont évolué pour être cet ensemble de lignes directrices. Ces meilleures pratiques sont utilisées par l’équipe du matériel Microsoft Surface pour développer et gérer le code du pilote de périphérique qui prend en charge les expériences matérielles Surface uniques.

Comme tout ensemble de lignes directrices, il y aura des exceptions légitimes et d’autres approches qui seront tout aussi valables. Envisagez d’incorporer ces instructions dans vos normes de développement ou de les utiliser pour démarrer vos instructions spécifiques à votre domaine pour votre environnement de développement et vos besoins uniques.

Erreurs courantes commises par les développeurs de pilotes

Gestion des E/S

  1. Accès aux mémoires tampons récupérées à partir d’IOCTL sans valider la longueur. Consultez Échec de la vérification de la taille des mémoires tampons.
  2. Exécution d’E/S bloquantes dans le contexte d’un thread utilisateur ou d’un contexte de thread aléatoire. Consultez Présentation des objets du répartiteur de noyau.
  3. Envoi d’E/S synchrones à un autre pilote sans délai d’expiration. Consultez Envoi de demandes d’E/S de manière synchrone.
  4. Utilisation des IOCTL sans io sans comprendre les implications en matière de sécurité. Consultez Utilisation des E/S directes ou mises en mémoire tampon.
  5. Ne pas vérifier la status de retour de WdfRequestForwardToIoQueue ou ne pas gérer correctement l’échec et entraîner des WDFREQUEST abandonnés.
  6. Maintien du WDFREQUEST en dehors de la file d’attente dans un état non annulable. Consultez Gestion des files d’attente d’E/S, Exécution des demandes d’E/ S et Annulation des demandes d’E/S.
  7. Essayez de gérer l’annulation à l’aide de la fonction Mark/UnmarkCancelable au lieu d’utiliser IoQueues. Consultez Objets de file d’attente du framework.
  8. Ne pas connaître la différence entre les opérations de nettoyage et de fermeture de handle de fichier. Consultez Erreurs dans la gestion des opérations de nettoyage et de fermeture.
  9. Ignorer les récursions potentielles avec l’achèvement des E/S et les réadmissions de la routine d’achèvement.
  10. Non explicite sur les attributs de gestion de l’alimentation des WDFQUEUEs. Ne pas documenter clairement le choix de gestion de l’alimentation. Il s’agit de la cause principale de la vérification des bogues 0x9F : DRIVER_POWER_STATE_FAILURE dans les pilotes WDF. Lorsque l’appareil est supprimé, l’infrastructure purge les E/S de la file d’attente gérée par l’alimentation et de la file d’attente non gérée par l’alimentation à différentes étapes du processus de suppression. Les files d’attente non gérées par l’alimentation sont vidées lors de la réception de la IRP_MN_REMOVE_DEVICE finale. Par conséquent, si vous conservez des E/S dans une file d’attente non gérée par l’alimentation, il est recommandé de vider explicitement les E/S dans le contexte d’EvtDeviceSelfManagedIoFlush pour éviter un blocage.
  11. Ne pas suivre les règles de gestion des IRP. Consultez Erreurs dans la gestion des opérations de nettoyage et de fermeture.

Synchronization

  1. Conservation des verrous pour le code qui n’a pas besoin de protection. Ne tenez pas de verrou pour l’ensemble d’une fonction quand seul un petit nombre d’opérations doit être protégé.
  2. Appel des chauffeurs avec des verrous tenus. Il s’agit des principales causes des interblocages.
  3. Utilisation de primitives interblocées pour créer un schéma de verrouillage au lieu d’utiliser des primitives de verrouillage fournies par le système approprié, telles que mutex, sémaphore et spinlocks. Consultez Introduction aux objets Mutex, Sémaphore Objects et Introduction to Spin Locks.
  4. Utilisation d’un verrouillage tournant où un type de verrou passif serait plus approprié. Consultez Mutexes rapides et Mutex surveillés et Objets d’événement. Pour plus d’informations sur les verrous, consultez l’article OSR - L’état de la synchronisation.
  5. Optez pour le modèle de synchronisation et d’exécution WDF sans comprendre complètement les implications. Consultez Utilisation de verrous d’infrastructure. À moins que votre pilote soit monolithique de niveau supérieur qui interagit directement avec le matériel, évitez d’opter pour la synchronisation WDF, car cela peut entraîner des interblocages en raison de la récursivité.
  6. Acquisition de KEVENT, Sémaphore, ERESOURCE, UnsafeFastMutex dans le contexte de plusieurs threads sans entrer dans une région critique. Cela peut entraîner une attaque DOS, car un thread contenant l’un de ces verrous peut être suspendu. Consultez Présentation des objets du répartiteur de noyau.
  7. Allocation de KEVENT sur la pile de threads et retour à l’appelant pendant que l’ÉVÉNEMENT est toujours en cours d’utilisation. Généralement effectué lorsqu’il est utilisé avec IoBuildSyncronousFsdRequest ou IoBuildDeviceIoControlRequest. L’appelant de ces appels doit s’assurer qu’ils ne sortent pas de la pile tant que le gestionnaire d’E/S n’a pas signalé l’événement lorsque l’IRP est terminé.
  8. Attente indéfinie dans les routines de dispatch. En général, tout type d’attente dans la routine de répartition est une mauvaise pratique.
  9. Vérification inappropriée de la validité d’un objet (si blah == NULL) avant de le supprimer. Cela signifie généralement que l’auteur n’a pas une compréhension complète du code qui contrôle la durée de vie de l’objet.

Gestion des objets

  1. Ne pas parenter explicitement les objets WDF. Consultez Présentation des objets framework.
  2. Parentage d’un objet WDF vers WDFDRIVER au lieu d’un parent avec un objet qui fournit une meilleure gestion de la durée de vie et optimise l’utilisation de la mémoire. Par exemple, la mise en parentation de WDFREQUEST vers un WDFDEVICE au lieu d’IOTARGET. Consultez Utilisation d’objets d’infrastructure généraux, Cycle de vie des objetsd’infrastructure et Résumé des objets framework.
  3. Ne pas effectuer de protection contre l’arrêt des ressources de mémoire partagée accessibles entre les pilotes. Consultez Fonction ExInitializeRundownProtection.
  4. Mise en file d’attente par erreur du même élément de travail alors que le précédent est déjà dans la file d’attente ou en cours d’exécution. Cela peut être un problème si le client part du principe que chaque élément de travail mis en file d’attente va être exécuté. Consultez Utilisation de Framework WorkItems. Pour plus d’informations sur la mise en file d’attente de WorkItems, consultez le module DMF_QueuedWorkitem dans le projet DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
  5. Minuteur de mise en file d’attente avant de publier le message que le minuteur est censé traiter. Consultez Utilisation de minuteurs.
  6. Exécution d’une opération dans un élément de travail qui peut bloquer ou prendre indéfiniment beaucoup de temps.
  7. Conception d’une solution qui entraîne un flot d’éléments de travail à mettre en file d’attente. Cela peut conduire à une attaque du système ou doS qui ne répond pas si l’utilisateur malveillant peut contrôler l’action (par exemple, le pompage d’E/S dans un pilote qui met en file d’attente un nouvel élément de travail pour chaque E/S). Consultez Utilisation d’éléments de travail d’infrastructure.
  8. Les rappels DPC d’élément de travail n’ont pas été exécutés avant la suppression de l’objet. Consultez Instructions pour l’écriture de routines DPC et la fonction WdfDpcCancel.
  9. Création de threads au lieu d’utiliser des éléments de travail pour des tâches de courte durée/sans interrogation. Consultez Threads de travail système.
  10. Ne pas s’assurer que les threads ont été exécutés jusqu’à l’achèvement avant de supprimer ou de décharger le pilote. Pour plus d’informations sur la synchronisation de l’exécution des threads, examinez le code associé à DMF_Thread module dans le projet Driver Module Framework (DMF) - https://github.com/Microsoft/DMF.
  11. Utilisation d’un pilote unique pour gérer des appareils différents mais interdépendants et utilisation de variables globales pour partager des informations.

Mémoire

  1. Ne pas marquer le code d’exécution passive comme PAGEABLE, lorsque cela est possible. La pagination du code du pilote peut réduire la taille de l’encombrement du code du pilote, libérant ainsi de l’espace système pour d’autres utilisations. Soyez prudent en marquant le code paginable qui déclenche IRQL >= DISPATCH_LEVEL ou peut être appelé à l’irQL élevé. Consultez Quand le code et les données doivent-ils être paginables et Rendre les pilotes paginables et Détection du code pouvant être paginable.
  2. Déclaration de structures volumineuses sur la pile, Doit utiliser le tas/pool à la place. Consultez Utilisation de KernelStack et allocation de mémoire System-Space.
  3. Zéro inutilement le contexte de l’objet WDF. Cela peut indiquer un manque de clarté quant au moment où la mémoire sera automatiquement supprimée.

Instructions générales relatives aux pilotes

  1. Mélange de primitives WDM et WDF. Utilisation de primitives WDM où les primitives WDF peuvent être utilisées. L’utilisation de primitives WDF vous protège contre les gotchas, améliore le débogage et, plus important encore, rend votre pilote portable en mode utilisateur.
  2. Nommage des FDO et création de liens symboliques lorsque cela n’est pas nécessaire. Consultez Gérer le contrôle d’accès du pilote.
  3. Copiez le collage et l’utilisation de GUID et d’autres valeurs constantes à partir d’exemples de pilotes.
  4. Envisagez d’utiliser le code DMF (Driver Module Framework) open source dans votre projet de pilote. DMF est une extension de WDF qui offre des fonctionnalités supplémentaires pour un développeur de pilotes WDF. Consultez Présentation de l’infrastructure du module pilote.
  5. Utilisation du registre en tant que mécanisme de notification interprocesseur ou en tant que boîte aux lettres. Pour obtenir une alternative, consultez DMF_NotifyUserWithEvent et DMF_NotifyUserWithRequest modules disponibles dans le projet DMF - https://github.com/Microsoft/DMF.
  6. En supposant que toutes les parties du Registre seront disponibles pour l’accès pendant la phase de démarrage précoce du système.
  7. Prise de dépendance sur l’ordre de chargement d’un autre pilote ou service. Comme l’ordre de chargement peut être modifié en dehors du contrôle de votre pilote, cela peut entraîner un pilote qui fonctionne initialement, mais qui échoue ultérieurement dans un modèle imprévisible.
  8. La recréation des bibliothèques de pilotes qui sont déjà disponibles, telles que WDF fournit le PnP décrit dans Prise en charge du PnP et de la gestion de l’alimentation dans votre pilote ou celles fournies dans l’interface de bus, comme décrit dans l’article OSR Using Bus Interfaces for Driver Communication.

PnP/Alimentation

  1. Interfaçage avec un autre pilote d’une manière non conviviale , sans inscription aux notifications de modification d’appareil pnp. Consultez Inscription à la notification de modification de l’interface d’appareil.
  2. Création de nœuds ACPI pour énumérer les appareils et création de dépendances d’alimentation entre eux au lieu d’utiliser des interfaces de création de pilotes de bus ou de périphériques logiciels fournis par le système pour les dépendances PNP et d’alimentation de manière élégante. Consultez Prise en charge du PnP et de la gestion de l’alimentation dans les pilotes de fonction.
  3. Marquage de l’appareil non désactivable : forçage d’un redémarrage lors de la mise à jour du pilote.
  4. Masquage de l’appareil dans le gestionnaire d’appareils. Consultez Masquage d’appareils de Gestionnaire de périphériques.
  5. En supposant que le pilote sera utilisé pour une seule instance de l’appareil.
  6. En partant du principe que le pilote ne sera jamais déchargé. Consultez La routine de déchargement du pilote PnP.
  7. Ne pas gérer les notifications d’arrivée d’interface fallacieuses. Cela peut se produire et les pilotes sont censés gérer cette condition en toute sécurité.
  8. N’implémente pas de stratégie d’alimentation inactive S0, ce qui est important pour les appareils qui sont des contraintes DRIPS ou des enfants de celles-ci. Consultez Prise en charge de la mise hors tension inactive.
  9. Le fait de ne pas vérifier le retour WdfDeviceStopIdle status entraîne une fuite de référence d’alimentation en raison d’un déséquilibre WdfDeviceStopIdle/ResumeIdle et éventuellement d’un bogue 9F case activée.
  10. Ne sachant pas que PrepareHardware/ReleaseHardware peut être appelé plusieurs fois en raison du rééquilibrage des ressources. Ces rappels doivent être limités à l’initialisation des ressources matérielles. Voir EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Utilisation de PrepareHardware/ReleaseHardware pour l’allocation de ressources logicielles. L’allocation de ressources logicielles statique à l’appareil doit être effectuée dans AddDevice ou dans SelfManagedIoInit si l’allocation des ressources nécessite une interaction avec le matériel. Voir EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.

Directives de codage

  1. N’utilisez pas de fonctions de chaîne sécurisée et d’entier. Consultez Utilisation de fonctions de chaîne sécurisée et Utilisation de fonctions d’entier sécurisé.
  2. N’utilisez pas de typedefs pour définir des constantes.
  3. Utilisation de variables globales et statiques. Évitez de stocker le contexte par appareil dans des globals. Les globals sont destinés au partage d’informations entre plusieurs instances d’appareils. Vous pouvez également utiliser le contexte d’objet WDFDRIVER pour partager des informations entre plusieurs instances d’appareils.
  4. N’utilisez pas de noms descriptifs pour les variables.
  5. Non-cohérence dans les variables de nommage - cohérence de la casse. Ne pas suivre le style de codage existant lors des mises à jour du code existant. Par exemple, l’utilisation de noms de variables différents pour les structures courantes dans différentes fonctions.
  6. Ne pas commenter les choix de conception importants : gestion de l’alimentation, verrous, gestion de l’état, utilisation d’éléments de travail, DPC, minuteurs, utilisation globale des ressources, pré-allocation des ressources, expressions complexes/instructions conditionnelles.
  7. Commentaires sur des éléments évidents du nom de l’API appelée. Faire de votre commentaire l’équivalent en langue anglaise du nom de la fonction (par exemple, écrire le commentaire « Créer l’objet d’appareil » lors de l’appel de WdfDeviceCreate).
  8. Ne créez pas de macros qui ont un appel de retour. Consultez Fonctions (C++).
  9. Annotations de code source (SAL) non ou incomplètes. Consultez Annotations SAL 2.0 pour les pilotes Windows.
  10. Utilisation de macros au lieu de fonctions inline.
  11. Utilisation de macros pour les constantes à la place de constexpr lors de l’utilisation de C++
  12. Compilation de votre pilote avec le compilateur C, au lieu du compilateur C++ pour vous assurer d’obtenir une vérification de type forte.

Gestion des erreurs

  1. Ne pas signaler d’erreurs de pilote critiques et marquer correctement l’appareil non fonctionnel.
  2. Ne pas retourner d’erreur NT appropriée status qui se traduit par des status d’erreur WIN32 significatives. Consultez Utilisation de valeurs NTSTATUS.
  3. N’utilisez pas de macros NTSTATUS pour case activée la status retournée des fonctions système.
  4. Ne pas s’affirmer sur les variables d’état ou les indicateurs si nécessaire.
  5. Vérification de la validité du pointeur avant d’y accéder pour contourner les conditions de course.
  6. ASSERTION sur les pointeurs NULL. Si vous tentez d’utiliser un pointeur NULL pour accéder à la mémoire, Windows présente un bogue case activée. Les paramètres du case activée de bogue fournissent les informations nécessaires pour corriger le pointeur Null. En outre, lorsque de nombreuses instructions ASSERT inutiles sont ajoutées au code, elles consomment de la mémoire et ralentissent le système.
  7. ASSERTING sur le pointeur de contexte d’objet. L’infrastructure de pilote garantit que l’objet sera toujours alloué avec le contexte.

Traçage

  1. Ne pas définir les types personnalisés WPP et les utiliser dans les appels de trace pour obtenir des messages de suivi lisibles par l’utilisateur. Consultez Ajout d’un suivi logiciel WPP à un pilote Windows.
  2. N’utilise pas le suivi IFR. Consultez Utilisation de l’enregistreur de traces en clair (IFR) dans les pilotes KMDF et UMDF 2.
  3. Appel de noms de fonction dans les appels de trace WPP. WPP effectue déjà le suivi des noms de fonctions et des numéros de ligne.
  4. N’utilisez pas d’événements ETW pour mesurer les performances et d’autres événements critiques impactant l’expérience utilisateur. Consultez Ajout d’un suivi d’événements à des pilotes Kernel-Mode.
  5. Ne pas signaler d’erreurs critiques dans le journal des événements et marquer correctement l’appareil non fonctionnel.

Vérification

  1. Ne pas exécuter le vérificateur de pilote avec les paramètres standard et avancés pendant le développement et les tests. Consultez Vérificateur de pilotes. Dans les paramètres avancés, il est recommandé d’activer toutes les règles, à l’exception de celles qui sont liées à la simulation de ressources faibles. Il est préférable d’exécuter les tests de simulation de ressources faibles de manière isolée pour faciliter le débogage des problèmes.
  2. Ne pas exécuter le test DevFund sur le pilote ou la classe de périphériques dont le pilote fait partie avec les paramètres de vérificateur avancés activés. Consultez Comment exécuter les tests DevFund via la ligne de commande.
  3. Ne pas vérifier que le pilote est conforme à HVCI. Consultez Implémenter le code de compatibilité HVCI.
  4. L’exécution d’AppVerifier sur WUDFhost.exe pendant le développement et le test des pilotes en mode utilisateur. Consultez Vérificateur d’application.
  5. Ne pas vérifier l’utilisation de la mémoire à l’aide de l’extension de débogueur !wdfpoolusage au moment de l’exécution pour vous assurer que les objets WDF ne sont pas abandonnés. La mémoire, les requêtes et les éléments de travail sont les victimes courantes de ces problèmes.
  6. N’utilisez pas l’extension de débogueur !wdfkd pour inspecter l’arborescence des objets afin de vérifier que les objets sont correctement parentés et vérifier les attributs des objets principaux tels que WDFDRIVER, WDFDEVICE, E/S.