Meilleures pratiques de développement du pilote Surface Team
Introduction
Ces directives de développement de pilotes ont été développées au cours de nombreuses années par les développeurs pilotes chez Microsoft. Au fil du temps où les conducteurs se sont mal comportes et que des leçons ont été apprises, ces leçons ont été capturées et ont évolué pour être cet ensemble d’instructions. 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 n’importe quel ensemble de lignes directrices, il y aura des exceptions légitimes et des approches alternatives qui seront tout aussi valides. 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 exigences uniques.
Erreurs courantes commises par les développeurs de pilotes
Gestion des E/S
- Accès aux mémoires tampons récupérées à partir de IOCTLs sans valider la longueur. Consultez Échec de la vérification de la taille des mémoires tampons.
- 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 de répartiteur de noyau.
- Envoi d’E/S synchrones à un autre pilote sans délai d’expiration. Consultez l’envoi de requêtes d’E/S de manière synchrone.
- Utilisation de iocTLs sans comprendre les implications en matière de sécurité. Consultez l’utilisation ni d’E/S mises en mémoire tampon ni d’E/S directes.
- Non case activée l’état de retour de WdfRequestForwardToIoQueue ou ne gère pas correctement l’échec et entraîne l’abandon des WDFREQUESTs.
- Conserver WDFREQUEST en dehors de la file d’attente dans un état non annulable. Consultez La gestion des files d’attente d’E/S, la fin des demandes d’E/S et l’annulation des demandes d’E/S.
- Essayez de gérer l’annulation à l’aide de la fonction Mark/UnmarkCancelable au lieu d’utiliser IoQueues. Consultez Les objets de file d’attente Framework.
- Vous ne connaissez pas la différence entre les opérations de nettoyage et de fermeture du handle de fichier. Consultez les erreurs de gestion des opérations de nettoyage et de fermeture.
- Ignorer les récursivités potentielles avec l’achèvement d’E/S et resoumettre à partir de la routine d’achèvement.
- Ne pas être explicite sur les attributs de gestion de l’alimentation des WDFQUEUEs. Vous ne documentez pas clairement le choix de gestion de l’alimentation. Il s’agit de la principale cause 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 dans différentes étapes du processus de suppression. Les files d’attente non gérées par l’alimentation sont vidées lorsque la dernière IRP_MN_REMOVE_DEVICE est reçue. Par conséquent, si vous maintenez 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 les blocages.
- Ne respectez pas les règles de gestion des irPs. Consultez les erreurs de gestion des opérations de nettoyage et de fermeture.
Synchronization
- Conservation de verrous pour le code qui n’a pas besoin de protection. Ne maintenez pas un verrou pour une fonction entière quand seul un petit nombre d’opérations doit être protégé.
- Appeler les conducteurs avec des verrous maintenus. Il s’agit des causes principales des interblocages.
- Utilisation de primitives interblocées pour créer un schéma de verrouillage au lieu d’utiliser des primitives de verrouillage appropriées fournies par le système, telles que mutex, sémaphore et spinlocks. Consultez Introduction aux objets Mutex, aux objets sémaphores et à l’introduction aux verrous de rotation.
- Utilisation d’un verrou de rotation où un certain type de verrou passif serait plus approprié. Consultez Les mutex rapides et les mutex guarded et les objets d’événement. Pour obtenir une perspective supplémentaire sur les verrous, consultez l’article OSR - État de synchronisation.
- Optez pour le modèle de synchronisation WDF et de niveau d’exécution sans comprendre pleinement les implications. Consultez Utilisation des verrous framework. Sauf si votre pilote est un pilote monolithique de niveau supérieur qui interagit directement avec le matériel, évitez d’opter pour la synchronisation WDF, car il peut entraîner des interblocages en raison de la récursivité.
- Acquisition de KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex dans le contexte de plusieurs threads sans entrer dans la 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 de répartiteur de noyau.
- Allocation de KEVENT sur la pile de threads et retour à l’appelant pendant que l’ÉVÉNEMENT est toujours en cours d’utilisation. En règle générale, lorsque vous utilisez IoBuildSyncronousFsdRequest ou IoBuildDeviceIoControlRequest. L’appelant de ces appels doit s’assurer qu’ils ne se déroulent pas de la pile tant que le gestionnaire d’E/S n’a pas signalé l’événement lorsque l’IRP est terminé.
- Attente indéfinie dans les routines de distribution. En général, toute sorte d’attente dans la routine de distribution est une mauvaise pratique.
- De façon inappropriée case activée la validité d’un objet (si blah == NULL) avant de la 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
- Pas explicitement parenter des objets WDF. Consultez Présentation des objets framework.
- Parentage d’objet WDF à WDFDRIVER au lieu de parenter à un objet qui offre une meilleure gestion de la durée de vie et optimise l’utilisation de la mémoire. Par exemple, le parentage DE WDFREQUEST à un WDFDEVICE au lieu d’IOTARGET. Consultez Utilisation d’objets framework généraux, du cycle de vie de l’objet framework et du résumé des objets framework.
- Ne pas effectuer de protection d’exécution des ressources de mémoire partagée accessibles entre les pilotes. Voir la fonction ExInitializeRundownProtection.
- Mise en file d’attente erronée du même élément de travail pendant que le précédent est déjà dans la file d’attente ou déjà en cours d’exécution. Il peut s’agir d’un problème si le client suppose que chaque élément de travail mis en file d’attente va être exécuté. Consultez Utilisation d’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.
- Minuteur de mise en file d’attente avant de publier le message que le minuteur est censé traiter. Voir Utilisation des minuteurs.
- Exécution d’une opération dans un élément de travail qui peut bloquer ou prendre indéfiniment de temps.
- Conception d’une solution qui entraîne une inondation d’éléments de travail à mettre en file d’attente. Cela peut entraîner une attaque sans réponse ou DOS si le mauvais type peut contrôler l’action (par exemple, pomper les E/S dans un pilote qui met en file d’attente un nouvel élément de travail pour chaque E/S). Consultez Utilisation des éléments de travail framework.
- Il n’est pas suivi que les rappels DPC de l’élément de travail ont été exécutés jusqu’à la fin avant de supprimer l’objet. Consultez les instructions d’écriture de routines DPC et de la fonction WdfDpcCancel.
- Création de threads au lieu d’utiliser des éléments de travail pour des tâches de courte durée/non-interrogation. Consultez threads de travail système.
- Veillez à ce que les threads soient exécutés jusqu’à la fin avant de supprimer ou de décharger le pilote. Pour plus d’informations sur la synchronisation d’exécution de thread, examinez le code associé à l’aperçu du code associé au module DMF_Thread dans le projet DMF (Driver Module Framework) - https://github.com/Microsoft/DMF.
- Utilisation d’un seul pilote pour gérer les appareils différents, mais interdépendants et utilisant des variables globales pour partager des informations.
Mémoire
- Ne pas marquer le code d’exécution passive comme PAGEABLE, si possible. Le code du pilote de pagination peut réduire la taille de l’empreinte du code du pilote, ce qui libère de l’espace système pour d’autres utilisations. Soyez prudent pour marquer le code paginable qui déclenche IRQL >= DISPATCH_LEVEL ou peut être appelé à l’irQL déclenché. Voir quand le code et les données doivent être paginables et rendre les pilotes paginables et détecter le code pouvant être paginable.
- Déclaration de grandes structures sur la pile, doit utiliser le tas/pool à la place. Consultez Utilisation de KernelStack et allocation de la mémoire de l’espace système.
- Zéro inutilement le contexte d’objet WDF. Cela peut indiquer un manque de clarté sur le moment où la mémoire est supprimée automatiquement.
Instructions générales relatives aux pilotes
- 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 rend plus important votre pilote portable sur le mode utilisateur.
- Nommage des FDO et création de liens symboliques quand cela n’est pas nécessaire. Consultez Gérer le contrôle d’accès au pilote.
- Copiez le collage et l’utilisation de GUID et d’autres valeurs constantes à partir d’exemples de pilotes.
- Envisagez l’utilisation du code code source ouvert DMF (Driver Module Framework) dans votre projet de pilote. DMF est une extension de WDF qui permet des fonctionnalités supplémentaires pour un développeur de pilotes WDF. Consultez Présentation de l’infrastructure du module pilote.
- Utilisation du Registre comme 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.
- 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.
- 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 plus tard échoue dans un modèle imprévisible.
- Recréation des bibliothèques de pilotes déjà disponibles, telles que WDF fournit pour PnP décrit dans La prise en charge de PnP et de gestion de l’alimentation dans votre pilote ou celles fournies dans l’interface de bus, comme décrit dans l’article OSR Utilisation des interfaces de bus pour la communication du pilote vers le pilote.
PnP/Power
- Interfacing with another driver in a non-pnp friendly way - not registering for pnp device change notifications. Consultez Inscription pour la notification de modification de l’interface d’appareil.
- Création de nœuds ACPI pour énumérer les appareils et créer des dépendances d’alimentation entre eux au lieu d’utiliser des interfaces de création de pilotes de bus ou de système fournies par les périphériques logiciels pour PNP et les dépendances d’alimentation d’une manière élégante. Consultez Prise en charge de PnP et de gestion de l’alimentation dans les pilotes de fonction.
- Marquage de l’appareil non désactivable : forçant un redémarrage lors de la mise à jour du pilote.
- Masquage de l’appareil dans le gestionnaire d’appareils. Consultez Masquage des appareils à partir de Gestionnaire de périphériques.
- Faire des hypothèses que le pilote sera utilisé pour une seule instance de l’appareil.
- Faire des hypothèses que le pilote ne sera jamais déchargé. Consultez la routine de déchargement du pilote PnP.
- Ne pas gérer la notification d’arrivée de l’interface impulsante. Cela peut se produire et les pilotes sont censés gérer cette condition en toute sécurité.
- Ne pas implémenter une stratégie d’alimentation inactive S0, qui est importante pour les appareils qui sont des contraintes DRIPS ou des enfants de ces derniers. Voir Prise en charge de l’arrêt de l’inactivité.
- Pas case activée’état de retour WdfDeviceStopIdle entraîne une fuite de référence de puissance en raison du déséquilibre WdfDeviceStopIdle/ResumeIdle et éventuellement du bogue 9F case activée.
- 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.
- Utilisation de PrepareHardware/ReleaseHardware pour allouer des ressources logicielles. L’allocation de ressources logicielles statique sur l’appareil doit être effectuée dans AddDevice ou dans SelfManagedIoInit si l’allocation des ressources requises interagit avec le matériel. Voir EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.
Directives de codage
- N’utilisez pas de fonctions de chaîne sécurisée et d’entier. Consultez Utilisation de Coffre fonctions de chaîne et d’utilisation de fonctions entières Coffre.
- N’utilisez pas de typedefs pour définir des constantes.
- Utilisation de variables globales et statiques. Évitez de stocker par contexte d’appareil dans des globals. Les globals sont destinés à partager des informations sur plusieurs instances d’appareils. En guise d’alternative, envisagez d’utiliser le contexte d’objet WDFDRIVER pour partager des informations sur plusieurs instances d’appareils.
- N’utilisez pas de noms descriptifs pour les variables.
- Non cohérent dans les variables d’affectation de noms : cohérence de la casse. Vous ne suivez pas le style de codage existant lors de la mise à jour du code existant. Par exemple, à l’aide de noms de variables différents pour les structures courantes dans différentes fonctions.
- Ne pas commenter les choix de conception importants : gestion de l’alimentation, verrous, gestion de l’état, utilisation d’éléments de travail, DPCs, minuteurs, utilisation globale des ressources, pré-allocation des ressources, expressions complexes/instructions conditionnelles.
- Commentaires sur les éléments qui sont évidents du nom de l’API appelée. Faire de votre commentaire l’équivalent en anglais du nom de la fonction (par exemple, écrire le commentaire « Créer l’objet d’appareil » lors de l’appel de WdfDeviceCreate).
- Ne créez pas de macros qui ont un appel de retour. Voir Functions (C++).
- Annotations de code source non ou incomplètes (SAL). Consultez les annotations SAL 2.0 pour les pilotes Windows.
- Utilisation de macros au lieu de fonctions inline.
- Utilisation de macros pour les constantes à la place de constexpr lors de l’utilisation de C++
- Compilation de votre pilote avec le compilateur C, au lieu du compilateur C++ pour vous assurer d’obtenir un type fort case activée ing.
Gestion des erreurs
- Ne signalez pas les erreurs de pilote critiques et marquez correctement l’appareil non fonctionnel.
- Impossible de retourner l’état d’erreur NT approprié qui se traduit par un état d’erreur WIN32 significatif. Consultez l’utilisation des valeurs NTSTATUS.
- N’utilisez pas de macros NTSTATUS pour case activée l’état retourné des fonctions système.
- Ne pas affirmer sur les variables d’état ou les indicateurs si nécessaire.
- Vérifiez si le pointeur est valide avant d’y accéder pour contourner les conditions de course.
- ASSERTION sur des pointeurs NULL. Si vous tentez d’utiliser un pointeur NULL pour accéder à la mémoire, Windows bogue case activée. Les paramètres du bogue case activée fournissent les informations nécessaires pour corriger le pointeur Null. Heures supplémentaires, quand de nombreuses instructions ASSERT inutiles sont ajoutées au code, elles consomment de la mémoire et ralentissent le système.
- ASSERTING sur le pointeur de contexte d’objet. L’infrastructure de pilote garantit que l’objet sera toujours alloué avec le contexte.
Traçage
- Ne pas définir de types personnalisés WPP et l’utiliser dans les appels de trace pour obtenir des messages de trace lisibles par l’homme. Consultez Ajout du suivi de logiciels WPP à un pilote Windows.
- Ne pas utiliser le suivi IFR. Consultez Utilisation de l’enregistreur de trace inflérateur (IFR) dans les pilotes KMDF et UMDF 2.
- Appel des noms de fonctions dans les appels de trace WPP. WPP effectue déjà le suivi des noms de fonctions et des numéros de ligne.
- 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 aux pilotes en mode noyau.
- Ne signalez pas les erreurs critiques dans le journal des événements et marquez correctement l’appareil non fonctionnel.
Vérification
- Impossible d’exécuter le vérificateur de pilotes avec des paramètres standard et avancés pendant le développement et les tests. Consultez Le vérificateur de pilotes. Dans les paramètres avancés, il est recommandé d’activer toutes les règles, à l’exception de celles liées à la simulation de ressources faible. Il est préférable d’exécuter les tests de simulation de ressources faibles en isolation pour faciliter le débogage des problèmes.
- L’exécution du test DevFund sur le pilote ou la classe de périphérique du pilote fait partie des paramètres avancés du vérificateur activés. Découvrez comment exécuter les tests DevFund via la ligne de commande.
- Vous ne vérifiez pas que le pilote est conforme à HVCI. Consultez Implémenter le code compatibile HVCI.
- L’exécution d’AppVerifier sur WUDFhost.exe pendant le développement et le test des pilotes en mode utilisateur. Consultez Le vérificateur d’application.
- Pas case activée’utilisation de la mémoire à l’aide de l’extension du 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 des victimes courantes de ces problèmes.
- N’utilisez pas l’extension de débogueur !wdfkd pour inspecter l’arborescence d’objets pour vérifier que les objets sont correctement parentés et case activée les attributs des objets principaux tels que WDFDRIVER, WDFDEVICE, IO.