新手如何第一次编写 “Hello World“ Windows 驱动程序 (KMDF)

这篇教程介绍了如何使用内核模式驱动程序框架(KMDF)在Windows上编写并部署一个简单的'Hello World'驱动程序。文章涵盖了从安装必备工具到编写DriverEntry和EvtDeviceAdd回调函数的步骤,以及驱动程序的构建、部署和安装过程。通过这个教程,新手可以了解驱动程序开发的基础知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本主题介绍如何使用内核模式驱动程序框架 (KMDF) 编写一个非常小的通用 Windows 驱动程序,然后在单独的计算机上部署和安装您的驱动程序。

要开始使用,请确保您已安装Microsoft Visual Studio、Windows SDK和Windows 驱动程序工具包 (WDK)。

安装 WDK 时包含适用于 Windows 的调试工具。

创建和构建驱动程序
打开 Microsoft Visual Studio。在“文件”菜单上,选择“新建”>“项目”。

在Create a new project对话框中,在左侧下拉列表中选择C++ ,在中间下拉列表中选择Windows,然后在右侧下拉列表中选择Driver。

从项目类型列表中选择Kernel Mode Driver, Empty (KMDF)。选择下一步。

在配置您的新项目对话框中,在项目名称字段中输入“KmdfHelloWorld”。

 笔记

创建新的 KMDF 或 UMDF 驱动程序时,您必须选择一个不超过 32 个字符的驱动程序名称。此长度限制在 wdfglobals.h 中定义。

在Location字段中,输入要在其中创建新项目的目录。

选中将解决方案和项目放在同一目录中,然后选择创建。

Visual Studio 创建一个项目和一个解决方案。您可以在解决方案资源管理器窗口中看到它们。(如果“解决方案资源管理器”窗口不可见,请从“视图”菜单中选择“解决方案资源管理器” 。)该解决方案有一个名为 KmdfHelloWorld 的驱动程序项目。

在解决方案资源管理器窗口中,选择并按住(或右键单击)KmdfHelloWorld项目,然后选择Configuration Manager。为驱动程序项目选择配置和平台。例如,选择Debug和x64。

在解决方案资源管理器窗口中,再次选择并按住(或右键单击)KmdfHelloWorld项目,选择Add,然后选择New Item。

在“添加新项”对话框中,选择C++ 文件。对于名称,输入“Driver.c”。

 笔记

文件扩展名是.c,而不是.cpp。

选择添加。Driver.c文件添加到Source Files下,如下所示。

编写您的第一个驱动程序代码
现在您已经创建了空的 Hello World 项目并添加了 Driver.c 源文件,您将通过实现两个基本事件回调函数来编写驱动程序运行所需的最基本代码。

在 Driver.c 中,首先包含以下标头:

C++

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

如果无法添加Ntddk.h,请打开Configuration -> C/C++ -> General -> Additional Include Directories并添加C:\Program Files (x86)\Windows Kits\10\Include\<build#>\km,替换<build#>为 WDK 安装中的相应目录。

Ntddk.h包含所有驱动程序的核心 Windows 内核定义,而Wdf.h包含基于 Windows 驱动程序框架 (WDF) 的驱动程序的定义。

接下来,为您将使用的两个回调提供声明:

C++

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;


使用以下代码编写您的DriverEntry:

C++


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的工作是初始化驱动程序范围的结构和资源。在此示例中,您为DriverEntry打印“Hello World”,配置驱动程序对象以注册您的EvtDeviceAdd回调的入口点,然后创建驱动程序对象并返回。

驱动程序对象充当您可能在驱动程序中创建的所有其他框架对象的父对象,其中包括设备对象、I/O 队列、计时器、自旋锁等。有关框架对象的更多信息,请参阅框架对象简介。

提示:

对于DriverEntry,我们强烈建议将名称保留为“DriverEntry”,以帮助进行代码分析和调试。接下来,使用以下代码编写您的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 队列,为特定于设备的信息设置设备上下文存储空间,或者执行准备设备所需的其他任务。

提示:

对于设备添加回调,请注意您如何使用驱动程序名称作为前缀 ( KmdfHelloWorld EvtDeviceAdd) 来命名它。通常,我们建议以这种方式命名您的驱动程序的功能,以将它们与其他驱动程序的功能区分开来。DriverEntry是唯一一个您应该准确命名的条目。

你完整的 Driver.c 现在看起来像这样: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;
}


保存 Driver.c。

这个例子说明了驱动程序的一个基本概念:它们是一个“回调集合”,一旦初始化,就会等待系统在需要时调用它们。这可能是新设备到达事件、来自用户模式应用程序的 I/O 请求、系统电源关闭事件、来自另一个驱动程序的请求或用户意外拔出设备时的意外移除事件。幸运的是,要说“Hello World”,您只需要担心驱动程序和设备的创建。

接下来,您将构建您的驱动程序。

构建驱动程序
在解决方案资源管理器窗口中,选择并按住(或右键单击)解决方案“KmdfHelloWorld”(1 个项目),然后选择配置管理器。为驱动程序项目选择配置和平台。对于本练习,我们选择Debug和x64。

在解决方案资源管理器窗口中,选择并按住(或右键单击)KmdfHelloWorld并选择Properties。在Wpp Tracing > All Options中,将Run Wpp tracking设置为No。选择应用,然后选择确定。

要构建您的驱动程序,请从“构建”菜单中选择“构建解决方案”。Visual Studio 在“输出”窗口中显示生成进度。(如果“输出”窗口不可见,请从“视图”菜单中选择“输出” 。)确认解决方案构建成功后,可以关闭 Visual Studio。

要查看构建的驱动程序,在文件资源管理器中,转到您的KmdfHelloWorld文件夹,然后转到C:\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->Use Local Time。现在Stampinf和 Inf2Cat 都使用本地时间。

部署驱动程序
通常,当您测试和调试驱动程序时,调试器和驱动程序在不同的计算机上运行。运行调试器的计算机称为主机,运行驱动程序的计算机称为目标计算机。目标计算机也称为测试计算机。

到目前为止,您已经使用 Visual Studio 在主机上构建了驱动程序。现在您需要配置目标计算机。

按照配置计算机以进行驱动程序部署和测试 (WDK 10)中的说明进行操作。

提示:当您按照步骤使用网络电缆自动配置目标计算机时,请记下端口和密钥。您将在稍后的调试步骤中使用它们。在此示例中,我们将使用50000作为端口,使用1.2.3.4作为密钥。

在实际的驱动程序调试场景中,我们建议使用 KDNET 生成的密钥。有关如何使用 KDNET 生成随机密钥的详细信息,请参阅调试驱动程序 - 分步实验室(Sysvad 内核模式)主题。

在主机上,在 Visual Studio 中打开您的解决方案。您可以双击 KmdfHelloWorld 文件夹中的解决方案文件 KmdfHelloWorld.sln。

在解决方案资源管理器窗口中,选择并按住(或右键单击)KmdfHelloWorld项目,然后选择Properties。

在KmdfHelloWorld 属性页窗口中,转到Configuration Properties > Driver Install > Deployment,如下所示。

选中部署前删除以前的驱动程序版本。

对于Target Device Name,选择您为测试和调试配置的计算机的名称。在本练习中,我们使用名为 MyTestComputer 的计算机。

选择Hardware ID Driver Update,然后输入驱动程序的硬件 ID。对于本练习,硬件 ID 为 Root\KmdfHelloWorld。选择确定。

 笔记

在本练习中,硬件 ID 不能识别真正的硬件。它标识了一个假想的设备,该设备将在设备树中作为根节点的子节点获得一个位置。对于真实硬件,不要选择Hardware ID Driver Update;而是选择安装并验证。您将在驱动程序信息 (INF) 文件中看到硬件 ID。在解决方案资源管理器窗口中,转到KmdfHelloWorld > 驱动程序文件,然后双击 KmdfHelloWorld.inf。硬件 ID 位于 [Standard.NT$ARCH$] 下。

C++

[Standard.NT$ARCH$]
%KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld


在构建菜单上,选择部署解决方案。Visual Studio 会自动将安装和运行驱动程序所需的文件复制到目标计算机。这可能需要一两分钟。

部署驱动程序时,驱动程序文件将复制到测试计算机上的 %Systemdrive%\drivertest\drivers 文件夹中。如果在部署过程中出现问题,您可以检查文件是否已复制到测试计算机。验证 .inf、.cat、测试证书和 .sys 文件以及任何其他必要的文件是否存在于 %systemdrive%\drivertest\drivers 文件夹中。

有关部署驱动程序的更多信息,请参阅将驱动程序部署到测试计算机。

安装驱动程序
将您的 Hello World 驱动程序部署到目标计算机后,现在您将安装驱动程序。当您之前使用自动选项使用 Visual Studio 预配目标计算机时,Visual Studio 将目标计算机设置为运行测试签名的驱动程序作为预配过程的一部分。现在您只需要使用 DevCon 工具安装驱动程序。

在主机上,导航到 WDK 安装中的 Tools 文件夹并找到 DevCon 工具。例如,查看以下文件夹:

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

将 DevCon 工具复制到远程计算机。

在目标计算机上,通过导航到包含驱动程序文件的文件夹来安装驱动程序,然后运行 DevCon 工具。

以下是用于安装驱动程序的 devcon 工具的一般语法:

devcon install <INF 文件> <硬件 ID>

安装此驱动程序所需的 INF 文件是 KmdfHelloWorld.inf。INF 文件包含用于安装驱动程序二进制文件KmdfHelloWorld.sys的硬件 ID 。回想一下,位于 INF 文件中的硬件 ID 是Root\KmdfHelloWorld。

以管理员身份打开命令提示符窗口。导航到包含已构建驱动程序 .sys 文件的文件夹并输入以下命令:

devcon install kmdfhelloworld.inf root\kmdfhelloworld

如果您收到有关devcon无法识别的错误消息,请尝试添加devcon工具的路径。例如,如果您将其复制到目标计算机上名为C:\Tools的文件夹中,请尝试使用以下命令:

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

将出现一个对话框,指示测试驱动程序是未签名的驱动程序。选择仍然安装此驱动程序以继续。

调试驱动程序
现在您已经在目标计算机上安装了 KmdfHelloWorld 驱动程序,您将从主机远程连接一个调试器。

在主机上,以管理员身份打开命令提示符窗口。切换到 WinDbg.exe 目录。我们将使用作为 Windows 工具包安装的一部分安装的 Windows 驱动程序工具包 (WDK) 中的 x64 版本的 WinDbg.exe。这是 WinDbg.exe 的默认路径:

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

使用以下命令启动 WinDbg 以连接到目标计算机上的内核调试会话。端口和密钥的值应与您用于配置目标计算机的值相同。我们将使用50000作为端口,使用1.2.3.4作为密钥,这是我们在部署步骤中使用的值。k标志表明这是一个内核调试会话。

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

在Debug菜单上,选择Break。主机上的调试器将闯入目标计算机。在Debugger Command窗口中,您可以看到内核调试命令提示符:kd>。

此时,您可以通过在kd>提示符下输入命令来试验调试器。例如,您可以尝试以下命令:

流明
.sympath
.reload
x KmdfHelloWorld!*
要让目标计算机再次运行,请从“调试”菜单中选择“Go ”或按“g”,然后按“Enter”。

要停止调试会话,请从Debug菜单中选择Detach Debuggee 。

 重要的

确保在退出调试器之前使用“go”命令让目标计算机再次运行,否则目标计算机将保持对您的鼠标和键盘输入无响应,因为它仍在与调试器对话。

有关驱动程序调试过程的详细分步演练,请参阅调试通用驱动程序 - 分步实验室(Echo 内核模式)。

有关远程调试的详细信息,请参阅使用 WinDbg 进行远程调试。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Meta.Qing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值