共用方式為


教學課程:撰寫 Hello World Windows 驅動程式 (Kernel-Mode Driver Framework)

本文說明如何使用 Kernel-Mode Driver Framework (KMDF) 撰寫小型 通用 Windows 驅動程式,然後在個別的計算機上安裝您的驅動程式。

先決條件

  • 請遵循步驟來安裝 Windows 驅動程式套件 (WDK)。 當您安裝 WDK 時,會包含適用於 Windows 偵錯工具。

  • 安裝 Visual Studio 2022。 當您安裝 Visual Studio 2022 時,請選取具有 C++ 工作負載的 Desktop 開發,然後在 [個別元件] 底下 新增:

    • MSVC v143 - VS 2022 C++ ARM64/ARM64EC 防範 Spectre 的程式庫(最新)
    • MSVC v143 - VS 2022 C++ x64/x86 Spectre漏洞緩解程式庫(最新)
    • 使用應對 Spectre 風險的最新 v143 建置工具的 C++ ATL(適用於 ARM64/ARM64EC)
    • 使用適用於最新 v143 組建工具的 C++ ATL 及 Spectre 風險降低功能(x86 & x64)
    • C++ MFC 適用於最新的 v143 組建工具,並搭載 Spectre 缓解措施(ARM64/ARM64EC)
    • C++ MFC 針對包含 Spectre 風險降低功能的最新版本 v143 建置工具(x86 & x64)
    • Windows 驅動程式套件

建立和建置驅動程式

  1. 開啟 Visual Studio Microsoft。 在 [檔案] 功能表上,選擇 [新增 > 專案]。

  2. 在 [[建立新專案] 對話框中,選取左側下拉式清單中 C++,選擇中間下拉式清單中的 [Windows],然後在右側下拉式清單中選擇 [驅動程式]。

  3. 從專案類型清單中選取「核心模式驅動程式:空白 (KMDF)」。 請選擇 [下一步]

    Visual Studio 新增項目對話框的螢幕快照,其中已選取核心模式驅動程序選項。

    提示

    如果您在 Visual Studio 中找不到驅動程式專案範本,WDK Visual Studio 擴充功能並未正確安裝。 若要解決此問題,請啟動 Visual Studio Installer,選取 [修改],在 [個別元件] 索引卷標中新增 Windows 驅動程式套件,然後選取 [修改]。

  4. 在 [設定新專案] 對話框中,於 [專案名稱] 字段中輸入 “KmdfHelloWorld”。

    注意

    當您建立新的 KMDF 或 UMDF 驅動程式時,您必須選取具有 32 個字元或更少字元的驅動程式名稱。 此長度限制定義於 wdfglobals.h 中。

  5. 在 [位置] 字段中,輸入您要在其中建立新項目的目錄。

  6. 檢查 並將方案和專案放在相同的目錄,然後選取 [建立]

    Visual Studio 設定新項目對話框的螢幕快照,其中已醒目提示 [建立] 按鈕。

    Visual Studio 會建立一個專案和方案。 您可以在 [方案總管] 視窗中看到它們。 (如果看不到 [方案總管] 視窗,請從 [檢視] 功能表選擇 [方案總管] 。此解決方案有一個名為 KmdfHelloWorld 的驅動程式專案。)

    Visual Studio 方案總管視窗的螢幕快照,其中顯示解決方案和名為 KmdfHelloWorld 的空白驅動程序專案。

  7. 在 [方案總管] 視窗中,右鍵點擊 [解決方案 'KmdfHelloWorld' (1 個專案)],然後選擇 [Configuration Manager]。 選擇驅動程式專案的組態和平臺。 例如,選擇 偵錯x64

  8. 在 [方案總管] 視窗中,以滑鼠右鍵按兩下 KmdfHelloWorld 項目,選擇 [新增],然後選取 [[新增專案]

  9. 新增專案 對話框中,輸入 "Driver.c"。

    注意

    檔案的副檔名是 .c,而不是 .cpp

    選取 新增Driver.c 檔案會新增至 Source Files底下,如下所示。

    Visual Studio 方案總管視窗的螢幕快照,其中顯示已新增至驅動程式專案的 driver.c 檔案。

撰寫您的第一個驅動程式程式代碼

現在您已建立空的 Hello World 專案並新增 Driver.c 原始程式檔,接下來您會實作兩個基本事件回呼函式,撰寫驅動程式執行所需的最基本程序代碼。

  1. 在 Driver.c 中,從包含這些標頭開始:

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

    提示

    如果您無法新增 Ntddk.h,請開啟 Configuration -> C/C++ -> General -> [其他包含目錄] 並新增 C:\Program Files (x86)\Windows Kits\10\Include\<build#>\km,並以 WDK 安裝中的適當目錄取代 <build#>

    Ntddk.h 包含所有驅動程式的核心 Windows 核心定義,而 Wdf.h 包含以 Windows Driver Framework (WDF) 為基礎的驅動程式定義。

  2. 接下來,撰寫這兩個回呼函數的宣告:

    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
  3. 使用以下代碼撰寫您的 DriverEntry

    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 是所有驅動程式的進入點,例如 Main() 適用於許多使用者模式應用程式。 DriverEntry 的工作是初始化全驅動程式的結構和資源。 在此範例中,首先,您輸出「Hello World」以供 DriverEntry,設定驅動程式物件來註冊您的 EvtDeviceAdd 回呼的進入點,然後建立驅動程式物件並傳回。

    驅動程式物件可作為您在驅動程式中建立之所有其他架構物件的父物件,其中包括裝置物件、I/O 佇列、定時器、微調鎖定等等。 如需架構物件的詳細資訊,請參閱 Framework 物件簡介

    提示

    針對 DriverEntry,強烈建議您將名稱保留為 “DriverEntry”,以協助進行程式代碼分析和偵錯。

  4. 接下來,使用下列程式碼來撰寫您的 KmdfHelloWorldEvtDeviceAdd

    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 會在系統偵測到您的裝置到達時叫用。 其作業是初始化該裝置的結構和資源。 在此範例中,您已列印 EvtDeviceAdd的 “Hello World” 訊息,並建立裝置物件並傳回。 在您撰寫的其他驅動程式中,您可以為您的硬體建立 I/O 佇列、設定 裝置內容 儲存空間,或執行準備裝置所需的其他工作。

    提示

    請注意如何為裝置添加回調命名,使用您的驅動程式名稱作為前綴(KmdfHelloWorldEvtDeviceAdd)。 一般而言,建議您以這種方式命名驅動程式的函式,以區別其他驅動程式的函式。 DriverEntry 是唯一您應該精確命名為此的項目。

  5. 您的完整 Driver.c 現在看起來像這樣:

    #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. 儲存 Driver.c.

此範例說明驅動程式的基本概念:它們是「回呼集合」,一旦初始化,就坐著等候系統在需要時呼叫它們。 系統呼叫可能是新的裝置抵達事件、使用者模式應用程式的 I/O 要求、系統電源關機事件、來自另一個驅動程式的要求,或當使用者意外拔除裝置時意外移除事件。 幸運的是,若要說出 「Hello World」,您只需要擔心驅動程式和裝置的建立。

接下來,您會建置驅動程式。

建置驅動程式

  1. 在 [方案總管] 視窗中,以滑鼠右鍵點擊 [方案 'KmdfHelloWorld' (1 個專案)],然後選擇 [Configuration Manager]。 選擇驅動程式專案的組態和平臺。 在此練習中,選擇 [偵錯] 和 [x64]。

  2. 在 [方案總管] 視窗中,以滑鼠右鍵按兩下 [KmdfHelloWorld],然後選擇 [屬性]。 在 Wpp 追蹤 > 所有選項中,將 執行 Wpp 追蹤 設定為 。 選取 [] [套用],然後 [] [確定]。

  3. 若要建置驅動程式,請從 [建置] 功能表選擇 [建置方案]。 Visual Studio 會在 [輸出] 視窗中顯示建置進度。 (如果看不到 [輸出] 視窗,請從 [檢視] 功能表選擇 [輸出]。)當您確認解決方案已成功建置後,即可關閉 Visual Studio。

  4. 若要查看建置的驅動程式,請在 [檔案總管] 中,移至您的 KmdfHelloWorld 資料夾,然後移至 x64\Debug\KmdfHelloWorld。 資料夾包含:

    • KmdfHelloWorld.sys -- 內核模式驅動程式檔案
    • KmdfHelloWorld.inf -- 安裝驅動程式時 Windows 使用的資訊檔案
    • KmdfHelloWorld.cat -- 安裝程式用來驗證驅動程式測試簽章的目錄檔案

提示

如果您在建置驅動程式時看到 DriverVer set to a date in the future,請變更驅動程式項目設定,讓 Inf2Cat 設定 /uselocaltime。 若要這樣做,請使用 Configuration Properties->Inf2Cat->General->使用當地時間。 現在 Stampinf 和 Inf2Cat 都使用當地時間。

部署驅動程式

一般而言,當您測試和偵錯驅動程式時,調試程式和驅動程式會在不同的計算機上執行。 執行除錯程式的電腦稱為 主電腦,而執行驅動程式的電腦稱為 目標電腦。 目標電腦也稱為 測試電腦

到目前為止,您已使用 Visual Studio 在主電腦上建置驅動程式。 現在您需要設定目標計算機。

  1. 請遵循 的指示,為驅動程式部署和測試 (WDK 10) 佈建電腦

    提示

    當您依照步驟自動使用網路纜線佈建目標計算機時,請記下埠和密鑰。 您稍後會在偵錯步驟中使用它們。 在此範例中,您會使用 50000 作為埠,並使用 1.2.3.4 做為密鑰。

    在實際的驅動程序偵錯案例中,我們建議使用 KDNET 產生的密鑰。 如需如何使用 KDNET 產生隨機金鑰的詳細資訊,請參閱 偵錯驅動程式 - 逐步實驗室 (Sysvad 核心模式) 主題。

  2. 在主計算機上,在 Visual Studio 中開啟您的方案。 您可以在 KmdfHelloWorld 資料夾中按兩下方案檔KmdfHelloWorld.sln。

  3. 在 [方案總管] 視窗中,以滑鼠右鍵按兩下 KmdfHelloWorld 項目,然後選擇 [屬性]

  4. 轉到 Driver Install > Deployment

  5. 針對 [目標裝置名稱],選取您為測試和偵錯設定的計算機名稱。 在此練習中,我們使用名為 MyTestComputer 的計算機。

  6. 為了確保您要測試最新版本的驅動程式,請在部署 之前,請先檢查 [移除先前的驅動程式版本。

  7. 選取 [硬體標識符驅動程式更新],然後輸入驅動程式的硬體標識符。 在此練習中,硬體標識碼為 Root\KmdfHelloWorld。 選取 [確定]

    注意

    在此練習中,硬體標識碼不會識別實際的硬體片段。 它會將 裝置樹狀結構中的虛構裝置識別為根節點的子節點。 針對實際硬體,請勿選取 [硬體 ID 驅動程式更新];請改為選取 [安裝並驗證]。 您會在驅動程式的資訊 (INF) 檔案中看到硬體識別碼。 在 [方案總管] 視窗中,移至 KmdfHelloWorld > 驅動程式檔案,然後按兩下 [KmdfHelloWorld.inf]。 硬體標識碼位於 [Standard.NT$ARCH$]。

    [Standard.NT$ARCH$]
    %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
    
  8. 在 [建置 ] 功能表上,選擇 [部署解決方案]。 Visual Studio 會自動將安裝並執行驅動程式所需的檔案複製到目標計算機。 部署可能需要一兩分鐘的時間。

    當您部署驅動程式時,驅動程式檔案會複製到測試計算機上的 %Systemdrive%\drivertest\drivers 資料夾。 如果在部署期間發生問題,您可以檢查檔案是否已複製到測試計算機。 確認 .inf、.cat、test cert 和 .sys 檔案,以及任何其他必要檔案都存在於 %systemdrive%\drivertest\drivers 資料夾中。

    如需部署驅動程式的詳細資訊,請參閱 將驅動程式部署至測試電腦

安裝驅動程式

將 Hello World 驅動程式部署至目標電腦,現在您會安裝驅動程式。 當您先前使用 自動 選項布建目標計算機時,Visual Studio 會設定目標計算機,以在布建程式中執行測試簽署的驅動程式。 現在,您只需要使用 DevCon 工具安裝驅動程式。

  1. 在主計算機上,流覽至 WDK 安裝中的 [工具] 資料夾,並找出 DevCon 工具。 例如,請檢視下列資料夾:

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

    將 DevCon 工具複製到遠端電腦。

  2. 在目標電腦上,流覽至包含驅動程式檔案的資料夾,然後執行DevCon工具,以安裝驅動程式。

    1. 以下是您將用來安裝驅動程式之 devcon 工具的一般語法:

      devcon 安裝 <INF 檔案><硬體識別碼>

      安裝此驅動程式所需的 INF 檔案為 KmdfHelloWorld.inf。 INF 檔案包含用來安裝驅動程式二進位檔的硬體識別碼,KmdfHelloWorld.sys。 回想一下,位於 INF 檔案中的硬體識別碼是 Root\KmdfHelloWorld

    2. 以系統管理員身分開啟命令提示字元視窗。 瀏覽至包含已建置驅動程式的資料夾 .sys 檔案,然後輸入此命令:

      devcon 安裝 kmdfhelloworld.inf root\kmdfhelloworld

      如果您收到無法辨識 devcon 的錯誤訊息,請嘗試將路徑新增至 devcon 工具。 例如,如果您將它複製到目標計算機上名為 C:\Tools的資料夾,請嘗試使用下列命令:

      c:\tools\devcon 安裝 kmdfhelloworld.inf root\kmdfhelloworld

      此時會出現一個對話框,指出測試驅動程式是未簽署的驅動程式。 選取 [仍安裝此驅動程式] 繼續。

      驅動程式安裝程式期間顯示之安全性警告的螢幕快照。

偵錯驅動程式

現在,您已在目標計算機上安裝 KmdfHelloWorld 驅動程式,接下來請從主計算機遠端附加調試程式。

  1. 在主電腦上,以系統管理員身分開啟命令提示字元視窗。 變更為 WinDbg.exe 目錄。 您使用來自 Windows 套件安裝的一部分的 Windows 驅動程式開發套件(WDK)的 WinDbg.exe x64 版本。 以下是 WinDbg.exe的預設路徑:

    C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

  2. 使用下列命令啟動 WinDbg 以連線到目標電腦上的核心偵錯會話。 埠和金鑰的值應該與您用來佈建目標電腦的值相同。 您對埠使用 50000,並對索引鍵使用 1.2.3.4,這些是您在部署步驟中使用的值。 k 旗標表示這是核心偵錯會話。

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

  3. 在 [偵錯] 功能表上,選擇 [中斷]。 主電腦上的偵錯程式會介入到目標計算機。 在 [調試程式命令] 視窗中,您可以看到核心偵錯命令提示字元:kd>

  4. 此時,您可以在 kd> 提示字元上輸入命令,試用調試器。 例如,您可以嘗試下列命令:

  5. 若要讓目標計算機再次執行,請 從 [偵錯] 功能表選擇 [Go],或按 “g”,然後按 “enter”。

  6. 若要停止偵錯會話,請 從 [偵錯] 功能表選擇 [卸離調試程式]。

    重要

    請確定您使用 「go」 命令讓目標計算機在結束調試程式之前再次執行,或目標計算機仍然對滑鼠和鍵盤輸入沒有回應,因為它仍在與調試程式交談。

如需詳細的驅動程式偵錯步驟解說,請參閱 通用驅動程式偵錯 - 分步實驗(Echo Kernel-Mode)

如需有關使用 WinDbg 進行遠端偵錯的詳細資訊,請參閱 遠端偵錯

適用於 Windows 偵錯工具

除錯通用驅動程式 - 逐步操作練習(Echo Kernel-Mode)

撰寫您的第一個驅動程式