本主题介绍了如何使用内核模式驱动程序框架 (KMDF) 编写很是小的通用 Windows 驱动程序。node
若要开始操做,请确保你已安装 Microsoft Visual Studio 2015 和 Windows 驱动程序工具包 (WDK) 10。windows
当安装 WDK 时,须要包括 Windows 调试工具。网络
在名称字段中,输入“KmdfHelloWorld”做为项目名称。框架
备注函数
在建立新的 KMDF 或 UMDF 驱动程序时,必须选择一个很少于 32 个字符的驱动程序名称。 此长度限制在 wdfglobals.h 中定义。工具
在位置字段中,输入要在其中建立新项目的目录。测试
选中建立解决方案的目录。 单击肯定。spa
Visual Studio 建立了一个项目和一个解决方案。 你能够在解决方案资源管理器窗口中看到它们,如此处所示。 (若是“解决方案资源管理器”窗口不可见,则从视图菜单中选择解决方案资源管理器。)该解决方案包含名为 KmdfHelloWorld 的驱动程序项目。debug
在解决方案资源管理器窗口中,右键单击 KmdfHelloWorld,而后选择属性。 导航到配置属性 > 驱动程序设置 > 常规,请注意,目标平台默认为通用。3d
在解决方案资源管理器窗口中,右键单击 KmdfHelloWorld,而后选择添加 > 新建项目。
在添加新项目对话框中,选择 C++ 文件。 对于名称,输入“Driver.c”。
备注
文件扩展名为 .c,不是 .cpp。
单击添加。 Driver.c 文件添加在源文件下,以下所示。
如今,你已经建立了空的 Hello World 项目并添加了 Driver.c 源文件,你将经过实现两个基本事件回调函数来编写驱动程序运行所需的最基本的代码。
在 Driver.c 中,首先包括如下标头:
C++
#include <ntddk.h> #include <wdf.h>
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:
C++
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 是彻底应该这样命名的惟一一项。
如今,你的整个 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 个项目),而后选择配置管理器。 为驱动程序项目和程序包项目选择配置和平台。 在本练习中,咱们选择调试和 x64。
在解决方案资源管理器窗口中,右键单击 KmdfHelloWorld,而后选择属性。 在 Wpp 跟踪 > 全部选项中,将运行 Wpp 跟踪设置为否。 单击应用,而后单击肯定。
若要查看生成的驱动程序,则在“文件资源管理器”中,依次转到你的 KmdfHelloWorld 文件夹和 C:\KmdfHelloWorld\x64\Debug。 该文件夹包括:
一般,当你测试和调试驱动程序时,调试器和驱动程序会在不一样的计算机上运行。 运行调试器的计算机称为主计算机,运行驱动程序的计算机称为目标计算机。 目标计算机也称为测试计算机。
到目前为止,你已在主计算机上使用 Visual Studio 生成了驱动程序。 如今,你须要配置目标计算机。
按照预配计算机以便进行驱动程序部署和测试 (WDK 10) 中的说明进行操做。
提示
按照步骤使用网络电缆自动预配目标计算机时,请记下端口和密钥。 之后,你将在调试步骤中使用它们。 在此示例中,咱们将使用 50000 做为端口,使用 1.2.3.4 做为密钥。
在实际驱动程序调试方案中,咱们建议使用 KDNET 生成的密钥。 有关如何使用 KDNET 生成一个随机密钥的详细信息,请参阅调试驱动程序 - 分步实验室(Sysvad 内核模式)主题。
在主计算机上,在 Visual Studio 中打开你的解决方案。 你能够在 KmdfHelloWorld 文件夹中双击解决方案文件 KmdfHelloWorld.sln。
选择硬件 ID 驱动程序更新,而后输入驱动程序的硬件 ID。 在本练习中,硬件 ID 为 Root\KmdfHelloWorld。 单击肯定。
备注
在本练习中,硬件 ID 未标识硬件的真实部分。 它标识了虚构设备,该设备位于设备树中,做为根节点的子节点。 对于真实的硬件,不选择硬件 ID 驱动程序更新,选择安装和验证。 你将在驱动程序的信息 (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 file> <hardware ID>
安装此驱动程序所需的 INF 文件是 KmdfHelloWorld.inf。 INF 文件包含用于安装驱动程序二进制文件 KmdfHelloWorld.sys 的硬件 ID。 回想一下,位于 INF 文件中的硬件 ID 是 Root\KmdfHelloWorld。
以管理员身份打开命令提示符窗口。 导航到你的驱动程序包文件夹,而后输入如下命令:
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
在调试菜单上,选择中断。 主计算机上的调试器将中断目标计算机。 在调试器命令窗口中,你能够看到内核调试命令提示符:kd>。
此时,能够试验调试器,方法是在 kd> 提示符处输入命令。 例如,能够尝试使用如下命令:
若要让目标计算机再次运行,请从调试菜单中选择执行,或者按“g”,而后按“Enter”。
若要中止调试会话,请从调试菜单中选择分离调试器。
重要
请确保在退出调试器以前使用“执行”命令让目标计算机再次运行,不然目标计算机将仍然对你的鼠标和键盘输入无响应,由于它仍在与调试器通话。
有关驱动程序调试过程的详细分步演练,请参阅调试通用驱动程序 - 分步实验室(回显内核模式)。
有关远程调试的详细信息,请参阅使用 WinDbg 远程调试。