Schreiben eines Hallo Welt Windows-Treibers (KMDF)

In diesem Artikel wird beschrieben, wie Sie einen kleinen universellen Windows-Treiber mithilfe von Kernel-Mode Driver Framework (KMDF) schreiben und ihren Treiber dann auf einem separaten Computer bereitstellen und installieren.

Bevor Sie fortfahren, führen Sie die unter Herunterladen des Windows Driver Kit (WDK) aufgeführten Installationsschritte aus.

Debugtools für Windows sind bei der Installation des WDK enthalten.

Erstellen und Erstellen eines Treibers

  1. Öffnen Sie Microsoft Visual Studio. Wählen Sie im Menü Datei die Option Neues > Projekt aus.

  2. Wählen Sie im Dialogfeld Neues Projekt erstellen in der linken Dropdownliste C++ aus, wählen Sie in der mittleren Dropdownliste Windows und in der rechten Dropdownliste Treiber aus.

  3. Wählen Sie in der Liste der Projekttypen die Option Kernelmodustreiber, Leer (KMDF) aus. Wählen Sie Weiter aus.

    Screenshot des Dialogfelds

  4. Geben Sie im Dialogfeld Neues Projekt konfigurieren den Namen "KmdfHelloWorld" in das Feld Projektname ein.

    Hinweis

    Wenn Sie einen neuen KMDF- oder UMDF-Treiber erstellen, müssen Sie einen Treibernamen auswählen, der maximal 32 Zeichen umfasst. Diese Längenbegrenzung ist in wdfglobals.h definiert.

  5. Geben Sie im Feld Speicherort das Verzeichnis ein, in dem Sie das neue Projekt erstellen möchten.

  6. Aktivieren Sie Projektmappe und Projekt im gleichen Verzeichnis platzieren , und wählen Sie Erstellen aus.

    Screenshot des Dialogfelds

    Visual Studio erstellt ein Projekt und eine Projektmappe. Diese werden im Fenster Projektmappen-Explorer angezeigt. (Wenn das fenster Projektmappen-Explorer nicht angezeigt wird, wählen Sie im Menü Ansichtdie Option Projektmappen-Explorer aus.) Die Lösung verfügt über ein Treiberprojekt mit dem Namen KmdfHelloWorld.

    Screenshot des Visual Studio-Projektmappen-Explorer-Fensters mit der Projektmappe und dem leeren Treiberprojekt mit dem Namen KmdfHelloWorld.

  7. Wählen Sie im fenster Projektmappen-Explorer die KmdfHelloWorld-Lösung aus, halten Sie sie gedrückt (oder wählen Sie sie mit der rechten Maustaste aus), und wählen Sie Configuration Manager aus. Wählen Sie eine Konfiguration und Plattform für das Treiberprojekt aus. Wählen Sie z. B . Debuggen und x64 aus.

  8. Wählen Sie im fenster Projektmappen-Explorer erneut das Projekt KmdfHelloWorld aus, halten Sie es gedrückt (oder wählen Sie es mit der rechten Maustaste aus), wählen Sie Hinzufügen und dann Neues Element aus.

  9. Wählen Sie im Dialogfeld Neues Element hinzufügen die Option C++-Datei aus. Geben Sie unter Name "Driver.c" ein.

    Hinweis

    Die Dateinamenerweiterung ist .c, nicht .cpp.

    Wählen Sie Hinzufügen. Die Datei Driver.c wird unter Quelldateien hinzugefügt, wie hier gezeigt.

    Screenshot des Visual Studio-Projektmappen-Explorer-Fensters mit der Datei driver.c, die dem Treiberprojekt hinzugefügt wurde.

Schreiben Des ersten Treibercodes

Nachdem Sie ihr leeres Hallo Welt-Projekt erstellt und die Quelldatei Driver.c hinzugefügt haben, schreiben Sie den grundlegendsten Code, der für die Ausführung des Treibers erforderlich ist, indem Sie zwei grundlegende Ereignisrückruffunktionen implementieren.

  1. Beginnen Sie in Driver.c mit den folgenden Headern:

    #include <ntddk.h>
    #include <wdf.h>
    

    Tipp

    Wenn Sie nicht hinzufügen Ntddk.hkönnen, öffnen Sie Konfiguration –> C/C++ –> Allgemein –> Zusätzliche Includeverzeichnisse, und fügen Sie hinzu, und <build#> ersetzen C:\Program Files (x86)\Windows Kits\10\Include\<build#>\kmSie durch das entsprechende Verzeichnis in Ihrer WDK-Installation.

    Ntddk.h enthält Kerndefinitionen des Windows-Kernels für alle Treiber, während Wdf.h Definitionen für Treiber enthält, die auf dem Windows Driver Framework (WDF) basieren.

  2. Geben Sie als Nächstes Deklarationen für die beiden Rückrufe an, die Sie verwenden:

    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
  3. Verwenden Sie den folgenden Code, um DriverEntry zu schreiben:

    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    

    DriverEntry ist der Einstiegspunkt für alle Treiber, z. Main() B. für viele Benutzermodusanwendungen. DriverEntry hat die Aufgabe, treiberweite Strukturen und Ressourcen zu initialisieren. In diesem Beispiel haben Sie "Hallo Welt" für DriverEntry ausgegeben, das Treiberobjekt so konfiguriert, dass der Einstiegspunkt Ihres EvtDeviceAdd-Rückrufs registriert wird, und anschließend das Treiberobjekt erstellt und zurückgegeben.

    Das Treiberobjekt fungiert als übergeordnetes Objekt für alle anderen Frameworkobjekte, die Sie möglicherweise in Ihrem Treiber erstellen, darunter Geräteobjekte, E/A-Warteschlangen, Timer, Spinlocks und vieles mehr. Weitere Informationen zu Frameworkobjekten finden Sie unter Einführung in Framework-Objekte.

    Tipp

    Für DriverEntry wird dringend empfohlen, den Namen "DriverEntry" beizubehalten, um die Codeanalyse und das Debuggen zu unterstützen.

  4. Verwenden Sie als Nächstes den folgenden Code, um Ihre KmdfHelloWorldEvtDeviceAdd zu schreiben:

    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    

    EvtDeviceAdd wird vom System aufgerufen, wenn es erkennt, dass Ihr Gerät eingetroffen ist. Seine Aufgabe besteht darin, Strukturen und Ressourcen für dieses Gerät zu initialisieren. In diesem Beispiel haben Sie einfach eine "Hallo Welt"-Nachricht für EvtDeviceAdd ausgegeben, das Geräteobjekt erstellt und zurückgegeben. In anderen Treibern, die Sie schreiben, können Sie E/A-Warteschlangen für Ihre Hardware erstellen, einen Gerätekontextspeicherplatz für gerätespezifische Informationen einrichten oder andere Aufgaben ausführen, die zum Vorbereiten Ihres Geräts erforderlich sind.

    Tipp

    Beachten Sie für den Geräterückruf, wie Sie ihn mit dem Namen Ihres Treibers als Präfix (KmdfHelloWorldEvtDeviceAdd) benannt haben. Im Allgemeinen wird empfohlen, die Funktionen Ihres Treibers auf diese Weise zu benennen, um sie von den Funktionen anderer Treiber zu unterscheiden. DriverEntry ist das einzige, das Sie genau benennen sollten.

  5. Ihr vollständiger Driver.c-Code sieht nun wie folgt aus:

    #include <ntddk.h>
    #include <wdf.h>
    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    
    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    
  6. Speichern Sie Driver.c.

Dieses Beispiel veranschaulicht ein grundlegendes Konzept von Treibern: Sie sind eine "Sammlung von Rückrufen", die sich nach der Initialisierung befinden und warten, bis das System sie aufruft, wenn es etwas benötigt. Ein Systemaufruf kann ein neues Geräteankunftsereignis, eine E/A-Anforderung von einer Benutzermodusanwendung, ein Systembetriebsabschaltungsereignis, eine Anforderung eines anderen Treibers oder ein unerwartetes Entfernungsereignis sein, wenn ein Benutzer das Gerät unerwartet absteckt. Glücklicherweise, um "Hallo Welt" zu sagen, mussten Sie sich nur um die Erstellung von Treibern und Geräten kümmern.

Als Nächstes erstellen Sie Ihren Treiber.

Erstellen des Treibers

  1. Wählen Sie im Projektmappen-Explorer-Fenster die Projektmappe "KmdfHelloWorld" (1 Projekt) aus( oder wählen Sie sie mit der rechten Maustaste aus), und wählen Sie Configuration Manager aus. Wählen Sie eine Konfiguration und Plattform für das Treiberprojekt aus. Für diese Übung wählen wir Debuggen und x64 aus.

  2. Wählen Sie im fenster Projektmappen-ExplorerkmdfHelloWorld aus, halten Sie es gedrückt (oder wählen Sie mit der rechten Maustaste aus), und wählen Sie Eigenschaften aus. Legen Sie in Wpp Tracing All Options (Wpp-Ablaufverfolgung > alle Optionen)die Option Run Wpp tracing (Wpp-Ablaufverfolgung ausführen ) auf Nein fest. Wählen Sie Apply (Übernehmen) und anschließend OK aus.

  3. Wählen Sie zum Erstellen Ihres Treibers im Menü Erstellen die Option Projektmappeerstellen aus. Visual Studio zeigt den Buildfortschritt im Fenster Ausgabe an. (Wenn das Ausgabefenster nicht angezeigt wird, wählen Sie im Menü Ansicht die Option Ausgabe aus.) Wenn Sie überprüft haben, ob die Projektmappe erfolgreich erstellt wurde, können Sie Visual Studio schließen.

  4. Um den integrierten Treiber in Explorer anzuzeigen, wechseln Sie zu Ihrem KmdfHelloWorld-Ordner und dann zu x64\Debug\KmdfHelloWorld. Der Ordner enthält Folgendes:

    • KmdfHelloWorld.sys : Die Treiberdatei im Kernelmodus
    • KmdfHelloWorld.inf – eine Informationsdatei, die Windows bei der Installation des Treibers verwendet
    • KmdfHelloWorld.cat : Eine Katalogdatei, die das Installationsprogramm verwendet, um die Testsignatur des Treibers zu überprüfen.

Tipp

Wenn beim Erstellen des Treibers angezeigt DriverVer set to a date in the future wird, ändern Sie die Einstellungen des Treiberprojekts, sodass Inf2Cat festlegt /uselocaltime. Verwenden Sie dazu Configuration Properties-Inf2Cat-General-Use>>> Local Time. Jetzt verwenden sowohl Stampinf als auch Inf2Cat die Ortszeit.

Bereitstellen des Treibers

Wenn Sie einen Treiber testen und debuggen, werden der Debugger und der Treiber in der Regel auf separaten Computern ausgeführt. Der Computer, auf dem der Debugger ausgeführt wird, wird als Hostcomputer bezeichnet, und der Computer, auf dem der Treiber ausgeführt wird, wird als Zielcomputer bezeichnet. Der Zielcomputer wird auch als Testcomputer bezeichnet.

Bisher haben Sie Visual Studio verwendet, um einen Treiber auf dem Hostcomputer zu erstellen. Jetzt müssen Sie einen Zielcomputer konfigurieren.

  1. Befolgen Sie die Anweisungen unter Bereitstellen eines Computers für die Treiberbereitstellung und -tests (WDK 10).

    Tipp

    Wenn Sie die Schritte zum automatischen Bereitstellen des Zielcomputers mithilfe eines Netzwerkkabels ausführen, notieren Sie sich den Port und den Schlüssel. Sie verwenden sie später im Debugschritt. In diesem Beispiel verwenden wir 50000 als Port und 1.2.3.4 als Schlüssel.

    In szenarien mit echtem Treiberdebuggen wird die Verwendung eines von KDNET generierten Schlüssels empfohlen. Weitere Informationen zur Verwendung von KDNET zum Generieren eines zufälligen Schlüssels finden Sie im Thema Debug Drivers – Step by Step Lab (Sysvad Kernel Mode).

  2. Öffnen Sie auf dem Hostcomputer Ihre Projektmappe in Visual Studio. Sie können in Ihrem KmdfHelloWorld-Ordner auf die Projektmappendatei KmdfHelloWorld.sln doppelklicken.

  3. Wählen Sie im Projektmappen-Explorer Fenster das Projekt KmdfHelloWorld aus, halten Sie es gedrückt (oder klicken Sie mit der rechten Maustaste darauf), und wählen Sie Eigenschaften aus.

  4. Navigieren Sie im Fenster KmdfHelloWorld-Eigenschaftenseiten zu Konfigurationseigenschaften > Treiberinstallation>, wie hier gezeigt.

  5. Aktivieren Sie Vorherige Treiberversionen vor der Bereitstellung entfernen.

  6. Wählen Sie unter Zielgerätname den Namen des Computers aus, den Sie zum Testen und Debuggen konfiguriert haben. In dieser Übung verwenden wir einen Computer namens MyTestComputer.

  7. Wählen Sie Hardware-ID-Treiberupdate aus, und geben Sie die Hardware-ID für Ihren Treiber ein. Für diese Übung lautet die Hardware-ID Root\KmdfHelloWorld. Klicken Sie auf OK.

    Screenshot: Fenster der kmdfhelloworld-Eigenschaftenseiten mit ausgewählter Installationsoption des Bereitstellungstreibers

    Hinweis

    In dieser Übung identifiziert die Hardware-ID kein echtes Hardwarestück. Es identifiziert ein imaginäres Gerät, das einen Platz in der Gerätestruktur als untergeordnetes Element des Stammknotens erhält. Wählen Sie für echte Hardware nicht Hardware-ID-Treiberupdate aus. Wählen Sie stattdessen Installieren und Überprüfen aus. Die Hardware-ID wird in der INF-Datei (InF) Ihres Treibers angezeigt. Navigieren Sie im Projektmappen-Explorer Fenster zu KmdfHelloWorld > Driver Files, und doppelklicken Sie auf KmdfHelloWorld.inf. Die Hardware-ID befindet sich unter [Standard.NT$ARCH$].

    [Standard.NT$ARCH$]
    %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
    
  8. Wählen Sie im Menü Erstellen die Option Lösung bereitstellen aus. Visual Studio kopiert automatisch die Dateien, die zum Installieren und Ausführen des Treibers erforderlich sind, auf den Zielcomputer. Die Bereitstellung kann ein oder zwei Minuten dauern.

    Wenn Sie einen Treiber bereitstellen, werden die Treiberdateien auf dem Testcomputer in den Ordner %Systemdrive%\drivertest\drivers kopiert. Wenn während der Bereitstellung etwas schief geht, können Sie überprüfen, ob die Dateien auf den Testcomputer kopiert werden. Vergewissern Sie sich, dass die Dateien .inf, .cat, test cert und .sys sowie alle anderen erforderlichen Dateien im Ordner %systemdrive%\drivertest\drivers vorhanden sind.

    Weitere Informationen zum Bereitstellen von Treibern finden Sie unter Bereitstellen eines Treibers auf einem Testcomputer.

Installieren des Treibers

Nachdem Ihr Hallo Welt Treiber auf dem Zielcomputer bereitgestellt wurde, installieren Sie jetzt den Treiber. Wenn Sie den Zielcomputer zuvor mit Visual Studio mithilfe der automatischen Option bereitgestellt haben, richtet Visual Studio den Zielcomputer so ein, dass testsignierte Treiber im Rahmen des Bereitstellungsprozesses ausgeführt werden. Jetzt müssen Sie nur noch den Treiber mithilfe des DevCon-Tools installieren.

  1. Navigieren Sie auf dem Hostcomputer in Ihrer WDK-Installation zum Ordner Tools, und suchen Sie das DevCon-Tool. Sehen Sie sich beispielsweise den folgenden Ordner an:

    C:\Programme (x86)\Windows Kits\10\Tools\x64\devcon.exe

    Kopieren Sie das DevCon-Tool auf Ihren Remotecomputer.

  2. Installieren Sie auf dem Zielcomputer den Treiber, indem Sie zu dem Ordner navigieren, der die Treiberdateien enthält, und führen Sie dann das DevCon-Tool aus.

    1. Hier sehen Sie die allgemeine Syntax für das devcon-Tool, das Sie zum Installieren des Treibers verwenden:

      hardware-ID der devcon-Installation <der INF-Datei><>

      Die für die Installation dieses Treibers erforderliche INF-Datei ist KmdfHelloWorld.inf. Die INF-Datei enthält die Hardware-ID zum Installieren der Treiberbinärdatei ,KmdfHelloWorld.sys. Denken Sie daran, dass die Hardware-ID, die sich in der INF-Datei befindet, Root\KmdfHelloWorld lautet.

    2. Öffnen Sie ein Eingabeaufforderungsfenster als Administrator. Navigieren Sie zu Ihrem Ordner, der den erstellten Treiber .sys Datei enthält, und geben Sie den folgenden Befehl ein:

      devcon install kmdfhelloworld.inf root\kmdfhelloworld

      Wenn Sie eine Fehlermeldung erhalten, dass devcon nicht erkannt wird, versuchen Sie, den Pfad zum devcon-Tool hinzuzufügen. Wenn Sie sie beispielsweise in einen Ordner auf dem Zielcomputer mit dem Namen C:\Tools kopiert haben, versuchen Sie, den folgenden Befehl zu verwenden:

      c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld

      Es wird ein Dialogfeld angezeigt, das angibt, dass der Testtreiber ein nicht signierter Treiber ist. Wählen Sie Diesen Treiber trotzdem installieren aus, um fortzufahren.

      Screenshot der Sicherheitswarnung, die während des Treiberinstallationsprozesses angezeigt wird.

Debuggen des Treibers

Nachdem Sie nun Ihren KmdfHelloWorld-Treiber auf dem Zielcomputer installiert haben, fügen Sie einen Debugger remote vom Hostcomputer an.

  1. Öffnen Sie auf dem Hostcomputer ein Eingabeaufforderungsfenster als Administrator. Wechseln Sie zum verzeichnis WinDbg.exe. Wir verwenden die x64version von WinDbg.exe aus dem Windows Driver Kit (WDK), das im Rahmen der Installation des Windows-Kits installiert wurde. Dies ist der Standardpfad für WinDbg.exe:

    C:\Programme (x86)\Windows Kits\10\Debuggers\x64

  2. Starten Sie WinDbg, um mithilfe des folgenden Befehls eine Verbindung mit einer Kerneldebugsitzung auf dem Zielcomputer herzustellen. Der Wert für Port und Schlüssel sollte mit dem wert identisch sein, den Sie zum Bereitstellen des Zielcomputers verwendet haben. Wir verwenden 50000 für den Port und 1.2.3.4 für den Schlüssel, die Werte, die wir während des Bereitstellungsschritts verwendet haben. Das k-Flag gibt an, dass es sich um eine Kerneldebugsitzung handelt.

    WinDbg -k net:port=50000,key=1.2.3.4

  3. Wählen Sie im Menü Debuggen die Option Umbruch aus. Der Debugger auf dem Hostcomputer wird in den Zielcomputer eingebrochen. Im Fenster Debuggerbefehl wird die Kerneldebugging-Eingabeaufforderung kd> angezeigt.

  4. An diesem Punkt können Sie mit dem Debugger experimentieren, indem Sie An der kd-Eingabeaufforderung> Befehle eingeben. Sie können beispielsweise die folgenden Befehle ausprobieren:

  5. Um den Zielcomputer erneut ausführen zu lassen, wählen Sie im Menü Debuggendie Option Go aus, oder drücken Sie "g", und drücken Sie dann die EINGABETASTE.

  6. Um die Debugsitzung zu beenden, wählen Sie im Menü Debuggen die Option Debugger trennen aus.

    Wichtig

    Stellen Sie sicher, dass Sie den Befehl "Go" verwenden, um den Zielcomputer erneut ausführen zu lassen, bevor Sie den Debugger beenden, sonst reagiert der Zielcomputer nicht auf Ihre Maus- und Tastatureingabe, da er weiterhin mit dem Debugger spricht.

Eine ausführliche schrittweise exemplarische Vorgehensweise zum Debuggen des Treibers finden Sie unter Debuggen von universellen Treibern – Schritt-für-Schritt-Lab (Echo-Kernel-Modus).

Weitere Informationen zum Remotedebuggen finden Sie unter Remotedebuggen mithilfe von WinDbg.

Debuggingtools für Windows

Debuggen universeller Treiber – Schritt-für-Schritt-Lab (Echo Kernel-Modus)

Schreiben Des ersten Treibers