Ajout du suivi d’événements aux pilotes Kernel-Mode

Cette section explique comment utiliser l’API de suivi d’événements pour Windows (ETW) en mode noyau pour ajouter le suivi d’événements aux pilotes en mode noyau. L’API en mode noyau ETW a été introduite avec Windows Vista et n’est pas prise en charge dans les systèmes d’exploitation antérieurs. Utilisez le suivi logiciel WPP ou le suivi d’événements WMI si votre pilote doit prendre en charge la fonctionnalité de suivi dans Windows 2000 et versions ultérieures.

Conseil

Pour afficher l’exemple de code qui montre comment implémenter ETW à l’aide du Kit de pilotes Windows (WDK) et de Visual Studio, consultez l’exemple Eventdrv.

Dans cette section :

Workflow - Ajout du suivi d’événements aux pilotes Kernel-Mode

1. Déterminez le type d’événements à déclencher et où les publier

2. Créer un manifeste d’instrumentation qui définit le fournisseur, les événements et les canaux

3. Compilez le manifeste d’instrumentation à l’aide du compilateur de messages (Mc.exe)

4. Ajoutez le code généré pour déclencher (publier) les événements (inscrire, désinscrire et écrire des événements)

5. Générer le pilote

6. Installer le manifeste

7. Tester le pilote pour vérifier la prise en charge d’ETW

Workflow - Ajout du suivi d’événements aux pilotes Kernel-Mode

Organigramme qui montre le processus d’ajout du suivi d’événements aux pilotes en mode noyau.

1. Déterminez le type d’événements à déclencher et où les publier

Avant de commencer le codage, vous devez décider du type d’événements que vous souhaitez que le pilote enregistre via le suivi d’événements pour Windows (ETW). Par exemple, vous pouvez enregistrer des événements qui peuvent vous aider à diagnostiquer des problèmes après la distribution de votre pilote, ou des événements qui peuvent vous aider à développer votre pilote. Pour plus d’informations, consultez Informations de référence sur le journal des événements Windows.

Les types d’événements sont identifiés avec des canaux. Un canal est un flux nommé d’événements de type Administration, Opérationnel, Analytique ou Débogage dirigé vers un public spécifique, semblable à un canal de télévision. Un canal transmet les événements du fournisseur d’événements aux journaux d’événements et aux consommateurs d’événements. Pour plus d’informations, consultez Définition de canaux.

Pendant le développement, vous êtes probablement intéressé par les événements de suivi qui vous aident à déboguer votre code. Ce même canal peut être utilisé dans le code de production pour résoudre les problèmes qui peuvent apparaître après le déploiement du pilote. Vous pouvez également suivre les événements qui peuvent être utilisés pour mesurer les performances ; ces événements peuvent aider les professionnels de l’informatique à affiner les performances du serveur et à identifier les goulots d’étranglement réseau.

2. Créer un manifeste d’instrumentation qui définit le fournisseur, les événements et les canaux

Le manifeste d’instrumentation est un fichier XML qui fournit une description formelle des événements qu’un fournisseur déclenchera. Le manifeste d’instrumentation identifie le fournisseur d’événements, spécifie le ou les canaux (jusqu’à huit), décrit les événements et modèles utilisés par les événements. En outre, le manifeste d’instrumentation permet la localisation des chaînes, ce qui vous permet de localiser les messages de trace. Le système d’événements et les consommateurs d’événements peuvent utiliser les données XML structurées fournies dans le manifeste pour effectuer des requêtes et des analyses.

Pour plus d’informations sur le manifeste d’instrumentation, consultez Écriture d’un manifeste d’instrumentation (Windows),Schéma EventManifest (Windows) et Utilisation du journal des événements Windows (Windows).

Le manifeste d’instrumentation suivant montre un fournisseur d’événements qui utilise le nom « Exemple de pilote ». Notez que ce nom n’a pas besoin d’être identique au nom du binaire du pilote. Le manifeste spécifie également un GUID pour le fournisseur et les chemins d’accès aux fichiers de message et de ressources. Les fichiers de messages et de ressources permettent à ETW de savoir où trouver les ressources nécessaires pour décoder et signaler les événements. Ces chemins pointent vers l’emplacement du fichier de pilote (.sys). Le pilote doit être installé dans le répertoire spécifié sur l’ordinateur cible.

L’exemple utilise le canal nommé System, un canal pour les événements de type Administration. Ce canal est défini dans le fichier Winmeta.xml fourni avec le Kit de pilotes Windows (WDK) dans le répertoire%WindowsSdkDir%\include\um. Le canal système est sécurisé pour les applications s’exécutant sous des comptes de service système. Le manifeste inclut les modèles d’événements qui décrivent les types de données fournies avec les événements lors de leur publication, ainsi que leur contenu statique et dynamique. Cet exemple de manifeste définit trois événements : StartEvent, SampleEventAet UnloadEvent.

En plus des canaux, vous pouvez associer des événements à des niveaux et des mots clés. Les mots clés et les niveaux permettent d’activer les événements et de fournir un mécanisme de filtrage des événements lors de leur publication. Les mots clés peuvent être utilisés pour regrouper des événements liés logiquement. Un niveau peut être utilisé pour indiquer la gravité ou le détail d’un événement, par exemple critique, erreur, avertissement ou informationnel. Le fichier Winmeta.xml contient des valeurs prédéfinies pour les attributs d’événement.

Lorsque vous créez un modèle pour la charge utile de l’événement (message d’événement et données), vous devez spécifier les types d’entrée et de sortie. Les types pris en charge sont décrits dans la section Remarques de Type complexe InputType (Windows).

<?xml version='1.0' encoding='utf-8' standalone='yes'?>
<instrumentationManifest
    xmlns="http://schemas.microsoft.com/win/2004/08/events"
    xmlns:win="http://manifests.microsoft.com/win/2004/08/windows/events"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://schemas.microsoft.com/win/2004/08/events eventman.xsd"
    >
  <instrumentation>
    <events>
      <provider
          guid="{b5a0bda9-50fe-4d0e-a83d-bae3f58c94d6}"
          messageFileName="%SystemDrive%\ETWDriverSample\Eventdrv.sys"
          name="Sample Driver"
          resourceFileName="%SystemDrive%\ETWDriverSample\Eventdrv.sys"
          symbol="DriverControlGuid"
          >
        <channels>
          <importChannel
              chid="SYSTEM"
              name="System"
              />
        </channels>
        <templates>
          <template tid="tid_load_template">
            <data
                inType="win:UInt16"
                name="DeviceNameLength"
                outType="xs:unsignedShort"
                />
            <data
                inType="win:UnicodeString"
                name="name"
                outType="xs:string"
                />
            <data
                inType="win:UInt32"
                name="Status"
                outType="xs:unsignedInt"
                />
          </template>
          <template tid="tid_unload_template">
            <data
                inType="win:Pointer"
                name="DeviceObjPtr"
                outType="win:HexInt64"
                />
          </template>
        </templates>
        <events>
          <event
              channel="SYSTEM"
              level="win:Informational"
              message="$(string.StartEvent.EventMessage)"
              opcode="win:Start"
              symbol="StartEvent"
              template="tid_load_template"
              value="1"
              />
          <event
              channel="SYSTEM"
              level="win:Informational"
              message="$(string.SampleEventA.EventMessage)"
              opcode="win:Info"
              symbol="SampleEventA"
              value="2"
              />
          <event
              channel="SYSTEM"
              level="win:Informational"
              message="$(string.UnloadEvent.EventMessage)"
              opcode="win:Stop"
              symbol="UnloadEvent"
              template="tid_unload_template"
              value="3"
              />
        </events>
      </provider>
    </events>
  </instrumentation>
  <localization xmlns="http://schemas.microsoft.com/win/2004/08/events">
    <resources culture="en-US">
      <stringTable>
        <string
            id="StartEvent.EventMessage"
            value="Driver Loaded"
            />
        <string
            id="SampleEventA.EventMessage"
            value="IRP A Occurred"
            />
        <string
            id="UnloadEvent.EventMessage"
            value="Driver Unloaded"
            />
      </stringTable>
    </resources>
  </localization>
</instrumentationManifest>

3. Compilez le manifeste d’instrumentation à l’aide du compilateur de messages (Mc.exe)

Le compilateur de messages (Mc.exe) doit être exécuté avant de compiler votre code source. Le compilateur de messages est inclus dans le Kit de pilotes Windows (WDK). Le compilateur de messages crée un fichier d’en-tête qui contient des définitions pour le fournisseur d’événements, les attributs d’événement, les canaux et les événements. Vous devez inclure ce fichier d’en-tête dans votre code source. Le compilateur de messages place également le script de compilateur de ressources généré (*.rc) et les fichiers .bin générés (ressources binaires) que le script du compilateur de ressources inclut.

Vous pouvez inclure cette étape dans le cadre de votre processus de génération de deux façons :

  • Ajout de la tâche de compilateur de messages au fichier projet du pilote (comme illustré dans l’exemple Eventdrv).

  • Utilisation de Visual Studio pour ajouter le manifeste d’instrumentation et configurer les propriétés du compilateur de messages.

Ajout d’une tâche de compilateur de messages au fichier projet Pour obtenir un exemple montrant comment inclure le compilateur de messages dans le processus de génération, examinez le fichier projet de l’exemple Eventdrv. Dans le fichier Eventdrv.vcxproj, il existe une <section MessageCompile> qui appelle le compilateur de messages. Le compilateur de messages utilise le fichier manifeste (evntdrv.xml) comme entrée pour générer le fichier d’en-tête evntdrvEvents.h. Cette section spécifie également les chemins d’accès pour les fichiers RC générés et active les macros de journalisation en mode noyau. Vous pouvez copier cette section et l’ajouter à votre fichier projet de pilote (.vcxproj).


    <MessageCompile Include="evntdrv.xml">
      <GenerateKernelModeLoggingMacros>true</GenerateKernelModeLoggingMacros>
      <HeaderFilePath>.\$(IntDir)</HeaderFilePath>
      <GeneratedHeaderPath>true</GeneratedHeaderPath>
      <WinmetaPath>"$(SDK_INC_PATH)\winmeta.xml"</WinmetaPath>
      <RCFilePath>.\$(IntDir)</RCFilePath>
      <GeneratedRCAndMessagesPath>true</GeneratedRCAndMessagesPath>
      <GeneratedFilesBaseName>evntdrvEvents</GeneratedFilesBaseName>
      <UseBaseNameOfInput>true</UseBaseNameOfInput>
    </MessageCompile>

Lorsque vous générez l’exemple Eventdrv.sys, Visual Studio crée les fichiers nécessaires au suivi des événements. Il ajoute également le manifeste evntdrv.xml à la liste des fichiers de ressources pour le projet de pilote. Vous pouvez sélectionner et maintenir (ou cliquer avec le bouton droit) le manifeste pour afficher les pages de propriétés du compilateur de messages.

Utilisation de Visual Studio pour ajouter le manifeste d’instrumentation

Vous pouvez ajouter le manifeste d’instrumentation au projet de pilote, puis configurer les propriétés du compilateur de messages pour générer les fichiers de ressources et d’en-tête nécessaires.

Pour ajouter le manifeste d’instrumentation au projet à l’aide de Visual Studio

  1. Dans le Explorateur de solutions, ajoutez le fichier manifeste au projet de pilote. Sélectionnez et maintenez enfoncée (ou cliquez avec le bouton droit) Fichiers > de ressources Ajouter un > élément existant (par exemple, evntdrv.xml ou mydriver.man).

  2. Sélectionnez et maintenez enfoncé (ou cliquez avec le bouton droit) le fichier que vous venez d’ajouter, puis utilisez les pages de propriétés pour remplacer le type d’élément par MessageCompile , puis sélectionnez Appliquer.

  3. Les propriétés du compilateur de messages s’affichent. Sous Paramètres généraux , définissez les options suivantes, puis sélectionnez Appliquer.

    Général Paramètre
    Générer des macros de journalisation en mode noyau Oui (-km)
    Utiliser le nom de base de l’entrée Oui (-b)
  4. Sous Options de fichier, définissez les options suivantes, puis sélectionnez Appliquer.

    Options de fichier Paramètre
    Générer un fichier d’en-tête pour contenir le compteur Oui
    Chemin du fichier d’en-tête $(IntDir)
    Chemin d’accès aux fichiers de messages rc et binaires générés Oui
    Chemin du fichier RC $(IntDir)
    Nom de base des fichiers générés $(Filename)

Par défaut, le compilateur de messages utilise le nom de base du fichier d’entrée comme nom de base des fichiers qu’il génère. Pour spécifier un nom de base, définissez le champ Nom de base des fichiers générés (-z). Dans l’exemple Eventdr.sys, le nom de base est défini sur evntdrvEvents afin qu’il corresponde au nom du fichier d’en-tête evntdrvEvents.h, qui est inclus dans evntdrv.c.

Notes

Si vous n’incluez pas le fichier .rc généré dans votre projet Visual Studio, vous pouvez recevoir des messages d’erreur concernant les ressources introuvables lors de l’installation du fichier manifeste.

4. Ajoutez le code généré pour déclencher (publier) les événements (inscrire, désinscrire et écrire des événements)

Dans le manifeste d’instrumentation, vous avez défini les noms du fournisseur d’événements et les descripteurs d’événements. Lorsque vous compilez le manifeste d’instrumentation avec le compilateur de messages, celui-ci génère un fichier d’en-tête qui décrit les ressources et définit également des macros pour les événements. Maintenant, vous devez ajouter le code généré à votre pilote pour déclencher ces événements.

  1. Dans votre fichier source, incluez le fichier d’en-tête d’événement qui est produit par le compilateur de messages (MC.exe). Par exemple, dans l’exemple Eventdrv, le fichier source Evntdrv.c inclut le fichier d’en-tête (evntdrvEvents.h) qui a été généré à l’étape précédente :

    #include "evntdrvEvents.h"  
    
  2. Ajoutez les macros qui inscrivent et annulent l’inscription du pilote en tant que fournisseur d’événements. Par exemple, dans le fichier d’en-tête de l’exemple Eventdrv (evntdrvEvents.h), le compilateur de messages crée des macros en fonction du nom du fournisseur. Dans le manifeste, l’exemple Eventdrv utilise le nom « Exemple de pilote » comme nom du fournisseur. Le compilateur de messages combine le nom du fournisseur avec la macro d’événement pour inscrire le fournisseur, dans ce cas, EventRegisterSample_Driver.

    //  This is the generated header file envtdrvEvents.h
    //
    //  ...
    //
    //
    // Register with ETW Vista +
    //
    #ifndef EventRegisterSample_Driver
    #define EventRegisterSample_Driver() McGenEventRegister(&DriverControlGuid, McGenControlCallbackV2, &DriverControlGuid_Context, &Sample_DriverHandle)
    #endif
    

    Ajoutez la macro du fournisseur> EventRegister< à votre fonction DriverEntry. Ajoutez cette fonction après le code qui crée et initialise l’objet d’appareil. Notez que vous devez faire correspondre l’appel à la fonction de fournisseur> EventRegister< avec un appel au fournisseur> EventUnregister<. Vous pouvez annuler l’inscription du pilote dans la routine de déchargement* de votre pilote.

       // DriverEntry function
       // ...
    
    
        // Register with ETW
        //
        EventRegisterSample_Driver();
    
  3. Ajoutez le code généré aux fichiers sources de votre pilote pour écrire (déclencher) les événements que vous avez spécifiés dans le manifeste. Le fichier d’en-tête que vous avez compilé à partir du manifeste contient le code généré pour le pilote. Utilisez les fonctions d’événement> EventWrite< définies dans le fichier d’en-tête pour publier des messages de trace sur ETW. Par exemple, le code suivant montre les macros pour les événements définis dans evntdrvEvents.h pour l’exemple Eventdrv.

    Les macros pour écrire ces événements sont appelées : EventWriteStartEvent, EventWriteSampleEventAet EventWriteUnloadEvent. Comme vous pouvez le voir dans la définition de ces macros, la définition de macro inclut automatiquement une macro d’événement> EventEnabled< qui vérifie si l’événement est activé. Le case activée éliminer la nécessité de générer la charge utile si l’événement n’est pas activé.

    
    ///
    // This is the generated header file envtdrvEvents.h
    //
    //  ...
    //
    // Enablement check macro for StartEvent
    //
    
    #define EventEnabledStartEvent() ((Sample_DriverEnableBits[0] & 0x00000001) != 0)
    
    //
    // Event Macro for StartEvent
    //
    #define EventWriteStartEvent(Activity, DeviceNameLength, name, Status)\
            EventEnabledStartEvent() ?\
            Template_hzq(Sample_DriverHandle, &StartEvent, Activity, DeviceNameLength, name, Status)\
            : STATUS_SUCCESS\
    
    //
    // Enablement check macro for SampleEventA
    //
    
    #define EventEnabledSampleEventA() ((Sample_DriverEnableBits[0] & 0x00000001) != 0)
    
    //
    // Event Macro for SampleEventA
    //
    #define EventWriteSampleEventA(Activity)\
            EventEnabledSampleEventA() ?\
            TemplateEventDescriptor(Sample_DriverHandle, &SampleEventA, Activity)\
            : STATUS_SUCCESS\
    
    //
    // Enablement check macro for UnloadEvent
    //
    
    #define EventEnabledUnloadEvent() ((Sample_DriverEnableBits[0] & 0x00000001) != 0)
    
    //
    // Event Macro for UnloadEvent
    //
    #define EventWriteUnloadEvent(Activity, DeviceObjPtr)\
            EventEnabledUnloadEvent() ?\
            Template_p(Sample_DriverHandle, &UnloadEvent, Activity, DeviceObjPtr)\
            : STATUS_SUCCESS\
    
    

    Ajoutez les macros d’événement> EventWrite< dans votre code source pour les événements que vous déclenchez. Par exemple, l’extrait de code suivant montre la routine DriverEntry de l’exemple Eventdrv. DriverEntry inclut les macros pour inscrire le pilote auprès d’ETW (EventRegisterSample_Driver) et la macro pour écrire l’événement de pilote dans ETW (EventWriteStartEvent).

    NTSTATUS
    DriverEntry(
        IN PDRIVER_OBJECT DriverObject,
        IN PUNICODE_STRING RegistryPath
        )
    /*++
    
    Routine Description:
    
        Installable driver initialization entry point.
        This entry point is called directly by the I/O system.
    
    Arguments:
    
        DriverObject - pointer to the driver object
    
        RegistryPath - pointer to a unicode string representing the path
            to driver-specific key in the registry
    
    Return Value:
    
       STATUS_SUCCESS if successful
       STATUS_UNSUCCESSFUL  otherwise
    
    --*/
    {
        NTSTATUS Status = STATUS_SUCCESS;
        UNICODE_STRING DeviceName;
        UNICODE_STRING LinkName;
        PDEVICE_OBJECT EventDrvDeviceObject;
        WCHAR DeviceNameString[128];
        ULONG LengthToCopy = 128 * sizeof(WCHAR);
        UNREFERENCED_PARAMETER (RegistryPath);
    
        KdPrint(("EventDrv: DriverEntry\n"));
    
        //
        // Create Dispatch Entry Points.  
        //
        DriverObject->DriverUnload = EventDrvDriverUnload;
        DriverObject->MajorFunction[ IRP_MJ_CREATE ] = EventDrvDispatchOpenClose;
        DriverObject->MajorFunction[ IRP_MJ_CLOSE ] = EventDrvDispatchOpenClose;
        DriverObject->MajorFunction[ IRP_MJ_DEVICE_CONTROL ] = EventDrvDispatchDeviceControl;
    
        RtlInitUnicodeString( &DeviceName, EventDrv_NT_DEVICE_NAME );
    
        //
        // Create the Device object
        //
        Status = IoCreateDevice(
                               DriverObject,
                               0,
                               &DeviceName,
                               FILE_DEVICE_UNKNOWN,
                               0,
                               FALSE,
                               &EventDrvDeviceObject);
    
        if (!NT_SUCCESS(Status)) {
            return Status;
        }
    
        RtlInitUnicodeString( &LinkName, EventDrv_WIN32_DEVICE_NAME );
        Status = IoCreateSymbolicLink( &LinkName, &DeviceName );
    
        if ( !NT_SUCCESS( Status )) {
            IoDeleteDevice( EventDrvDeviceObject );
            return Status;
        }
    
     //
     // Choose a buffering mechanism
     //
     EventDrvDeviceObject->Flags |= DO_BUFFERED_IO;
    
     //
     // Register with ETW
     //
     EventRegisterSample_Driver();
    
     //
     // Log an Event with :  DeviceNameLength
     //                      DeviceName
     //                      Status
     //
    
     // Copy the device name into the WCHAR local buffer in order
     // to place a NULL character at the end, since this field is
     // defined in the manifest as a NULL-terminated string
    
     if (DeviceName.Length <= 128 * sizeof(WCHAR)) {
    
         LengthToCopy = DeviceName.Length;
    
     }
    
     RtlCopyMemory(DeviceNameString,
                   DeviceName.Buffer,
                   LengthToCopy);
    
     DeviceNameString[LengthToCopy/sizeof(WCHAR)] = L'\0';
    
     EventWriteStartEvent(NULL, DeviceName.Length, DeviceNameString, Status);
    
     return STATUS_SUCCESS;
    }
    

Ajoutez toutes les macros d’événements> EventWrite< dans votre code source pour les événements que vous déclenchez. Vous pouvez examiner l’exemple Eventdrv pour voir comment les deux autres macros sont appelées pour les événements dans le code source du pilote.

  1. Annulez l’inscription du pilote en tant que fournisseur d’événements à l’aide de la macro du fournisseur> EventUnregister< du fichier d’en-tête généré.

    Placez cet appel de fonction dans votre routine de déchargement de pilote. Aucun appel de suivi ne doit être effectué après l’appel de la macro du fournisseur> EventUnregister<. L’échec de l’inscription du fournisseur d’événements peut entraîner des erreurs lorsque le processus est déchargé, car toutes les fonctions de rappel associées au processus ne sont plus valides.

        // DriverUnload function
        // ...
        //
    
        //  Unregister the driver as an ETW provider
        //
        EventUnregisterSample_Driver();
    

5. Générer le pilote

Si vous avez ajouté le manifeste de l’instrument au projet et que vous avez configuré les propriétés du compilateur de messages (MC.exe), vous pouvez générer le projet pilote ou la solution à l’aide de Visual Studio et MSBuild.

  1. Ouvrez la solution de pilote dans Visual Studio.

  2. Générez l’exemple à partir du menu Générer en sélectionnant Générer la solution. Pour plus d’informations sur la création de solutions, consultez Génération d’un pilote.

6. Installer le manifeste

Vous devez installer le manifeste sur le système cible afin que les consommateurs d’événements (par exemple, le journal des événements) puissent trouver l’emplacement du fichier binaire qui contient les métadonnées de l’événement. Si vous avez ajouté la tâche de compilateur de messages au projet de pilote à l’étape 3, le manifeste d’instrumentation a été compilé et les fichiers de ressources ont été générés lorsque vous avez généré le pilote. Utilisez l’utilitaire de ligne de commande d’événements Windows (Wevtutil.exe) pour installer le manifeste. La syntaxe pour installer le manifeste est la suivante :

wevtutil.exe imdrivermanifest

Par exemple, pour installer le manifeste pour l’exemple de pilote Evntdrv.sys, ouvrez une fenêtre d’invite de commandes avec des privilèges élevés (Exécuter en tant qu’administrateur) accédez au répertoire où se trouve le fichier evntdrv.xml et entrez la commande suivante :

Wevtutil.exe im evntdrv.xml

Une fois votre session de suivi terminée, désinstallez le manifeste à l’aide de la syntaxe suivante.

wevtutil.exe umdrivermanifest

Par exemple, pour désinstaller le manifeste de l’exemple Eventdrv :

Wevtutil.exe um evntdrv.xml

7. Tester le pilote pour vérifier la prise en charge d’ETW

Installez le pilote. Exercez le pilote pour générer l’activité de suivi. Affichez les résultats dans le observateur d'événements. Vous pouvez également exécuter Tracelog, puis exécuter Tracerpt, un outil de traitement des journaux de trace d’événements, pour contrôler, collecter et afficher les journaux de trace d’événements.