Partager via


Ajout du suivi d’événements aux pilotes en mode noyau

Cette section explique comment utiliser l’API de suivi des é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 trace dans Windows 2000 et versions ultérieures.

Conseil

Pour afficher un 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 :

Flux de travail - Ajout d’un suivi d’événements aux pilotes en mode noyau

1. Décider du 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, annuler l’inscription et écrire des événements)

5. Générer le pilote

6. Installer le manifeste

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

Flux de travail - Ajout d’un suivi d’événements aux pilotes en mode noyau

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

1. Décider du 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 des événements pour Windows (ETW). Par exemple, vous souhaiterez peut-être consigner 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 Admin, Operational, Analytical ou Debug dirigé vers un public spécifique, similaire à un canal de télévision. Un canal remet 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 susceptibles d’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éclenche. Le manifeste d’instrumentation identifie le fournisseur d’événements, spécifie le canal ou les canaux (jusqu’à huit) et décrit les événements et modèles utilisés par les événements. En outre, le manifeste d’instrumentation permet la localisation de chaîne, afin de pouvoir 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 une analyse.

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 « Sample Driver ». Notez que ce nom n’a pas besoin d’être identique au nom du fichier 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 indiquent où localiser 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 système de canal nommé, un canal pour les événements de type Admin. 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 lorsqu’ils sont publiés, 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 lorsqu’ils sont publiés. Les mots clés peuvent être utilisés pour regrouper les événements liés logiquement. Un niveau peut être utilisé pour indiquer la gravité ou la 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 Notes du 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 du compilateur de ressources généré (*.rc) et les fichiers de .bin générés (ressources binaires) inclus par le script du compilateur de ressources.

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

  • Ajout de la tâche du compilateur de messages au fichier projet du pilote (comme indiqué 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 de la façon dont vous pouvez 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, une <section MessageCompile> 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 des fichiers RC générés et active les macros de journalisation en mode noyau. Vous pouvez copier cette section et l’ajouter au fichier projet de votre 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 pour le 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 conserver (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 ressource 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 la touche 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 et utilisez les pages de propriétés pour modifier le type d’élément en MessageCompile , puis sélectionnez Appliquer.

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

    Général Setting
    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 Setting
    Générer un fichier d’en-tête pour contenir un compteur Oui
    Chemin du fichier d’en-tête $(IntDir)
    Chemin d’accès des fichiers de messages RC et binaires générés Oui
    Chemin d’accès au 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 de 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.

Remarque

Si vous n’incluez pas le fichier .rc généré dans votre projet Visual Studio, vous pouvez recevoir des messages d’erreur sur 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, annuler l’inscription et écrire des événements)

Dans le manifeste d’instrumentation, vous avez défini les noms du fournisseur d’événements et des descripteurs d’événements. Lorsque vous compilez le manifeste d’instrumentation avec le compilateur de messages, le compilateur de messages génère un fichier d’en-tête qui décrit les ressources et définit également des macros pour les événements. À présent, 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 généré 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) 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 « Sample Driver » 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 dans ETW. Par exemple, le code suivant montre les macros pour les événements définis dans evntdrvEvents.h pour l’exemple Eventdrv.

    Les macros permettant d’écrire ces événements sont appelées : EventWriteStartEvent, EventWriteSampleEventA, et 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é. La vérification élimine 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énements> 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 permettant d’inscrire le pilote auprès d’ETW (EventRegisterSample_Driver) et de 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<à partir 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 d’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 de 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 dans le menu Générer en sélectionnant Build 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 d’événement. Si vous avez ajouté la tâche du 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énement Windows (Wevtutil.exe) pour installer le manifeste. La syntaxe d’installation du manifeste est la suivante :

wevtutil.exe im drivermanifest

Par exemple, pour installer le manifeste de 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 trace terminée, désinstallez le manifeste à l’aide de la syntaxe suivante.

wevtutil.exe um drivermanifest

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

Wevtutil.exe um evntdrv.xml

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

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