VS2013 WDK8.1驱动开发2(最简单的WDM驱动)
本系列博客为学习《Windows驱动开发技术详解》一书的学习笔记。
前言
上次创建了一个最简单的NT驱动程序,这次我们来创建一个最简单的WDM驱动程序。WDM驱动相比于NT驱动程序多了对即插即用功能的支持。
搭建Windows驱动开发环境
- 开发主机安装Microsoft Visual Studio 2013,下载地址点我
- 开发主机安装Windows Driver Kit (WDK) 8.1,下载地址点我
- 测试虚拟机环境为Win7 64位
VS2013必须配合WDK8.1才可以进行驱动程序的开发,只有安装了WDK8.1后,VS2013中才会出现驱动开发工程的模板,如下图:
创建一个WDM驱动工程
新建一个空的WDM驱动工程,删除附带的Package工程,新建一个WDMDriver.c文件,我们的代码都写在该文件中。
编写WDM驱动程序代码
1. WDM驱动程序的入口函数
#include <wdm.h>
/// @brief 初始化驱动程序
/// @param[in] pDriverObject 驱动对象
/// @param[in] pRegPath 驱动程序在注册表中的路径
/// @return 初始化驱动状态
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath)
{
UNREFERENCED_PARAMETER(pRegPath);
KdPrint(("Enter DriverEntry\n"));
pDriverObject->DriverExtension->AddDevice = HelloWDMAddDevice;
pDriverObject->DriverUnload = HelloWDMUnload;
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = HelloWDMDispatchRoutine;
}
pDriverObject->MajorFunction[IRP_MJ_PNP] = HelloWDMPnp;
KdPrint(("Leave DriverEntry\n"));
return STATUS_SUCCESS;
}
- WDM驱动程序需要包含wdm.h头文件。
- 代码中我们给AddDevice设置了一个回调函数,NT驱动程序没有此回调函数。该函数的作用是创建设备,该函数由PNP(即插即用)管理器调用,PNP管理器会在指定设备插入系统时调用该函数。
- 相比于NT驱动程序,WDM驱动程序还多了一个对IRP_MJ_PNP的处理函数,该函数的作用是处理PNP的请求包,例如启动设备、停止设备等请求。
2. AddDevice例程
在WDM驱动程序中DriverEntry不再负责创建设备,而是交由AddDevice例程去创建设备。
/// @brief 设备扩展结构
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT PDeviceObject; ///< 设备对象
PDEVICE_OBJECT PNextStackDevice; ///< 下层设备对象指针
UNICODE_STRING DeviceName; ///< 设备名称
UNICODE_STRING SymLinkName; ///< 符号链接名
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
/// @brief 添加新设备
/// @param[in] pDriverObject 从I/O管理器传进来的驱动对象
/// @param[in] pPhysicalDeviceObject 从I/O管理器传进来的物理设备对象
/// @return 添加新设备状态
NTSTATUS HelloWDMAddDevice(IN PDRIVER_OBJECT pDriverObject, IN PDEVICE_OBJECT pPhysicalDeviceObject)
{
KdPrint(("Enter HelloWDMAddDevice\n"));
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING devName = { 0 }; // 设备名称
UNICODE_STRING symName = { 0 }; // 链接符号名
PDEVICE_OBJECT pDeviceObject = NULL; // 创建的设备对象
PDEVICE_EXTENSION pDeviceExt = NULL; // 设备扩展对象
// 初始化字符串
RtlInitUnicodeString(&devName, L"\\Device\\HelloWDMDevice");
RtlInitUnicodeString(&symName, L"\\??\\HelloWDM");
// 创建设备
status = IoCreateDevice(
pDriverObject,
sizeof(DEVICE_EXTENSION),
&devName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDeviceObject);
if (!NT_SUCCESS(status))
{
return status;
}
pDeviceExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
pDeviceExt->PDeviceObject = pDeviceObject;
pDeviceExt->DeviceName = devName;
pDeviceExt->SymLinkName = symName;
// 讲设备对象挂接在设备堆栈上
pDeviceExt->PNextStackDevice = IoAttachDeviceToDeviceStack(pDeviceObject, pPhysicalDeviceObject);
status = IoCreateSymbolicLink(&symName, &devName);
if (!NT_SUCCESS(status))
{
IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);
status = IoCreateSymbolicLink(&symName, &devName);
if (!NT_SUCCESS(status))
{
return status;
}
}
pDeviceObject->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
KdPrint(("Leave HelloWDMAddDevice\n"));
return status;
}
- 和NT驱动一样我们需要声明一个设备扩展结构。
- AddDevice例程的参数有两个,第一个是驱动对象,第二个是物理设备对象,该对象由I/O管理器创建,物理设备对象的概以后讲。
- 创建设备对象代码和NT驱动程序类似,只是多了一个将设备对象附加在设备栈上的过程,设备栈的概念以后会讲到。
- 当系统中插入多个指定硬件时该函数被被多次调用,也就是说会多次创建设备对象,创建多个设备时需要每个设备的设备名称和链接符号都不同,示例代码中使用相同的设备名和链接符号是有问题的,实际编写程序时请注意。
3. PNP IRP处理例程
/// @brief 对即插即用IPR进行处理
/// @param[in] pDeviceObject 功能设备对象
/// @param[in] pIrp 请求包
/// @return 状态
NTSTATUS HelloWDMPnp(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
KdPrint(("Enter HelloWDMPnp\n"));
PDEVICE_EXTENSION pDeviceExt = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
PIO_STACK_LOCATION pStackLoc = IoGetCurrentIrpStackLocation(pIrp);
unsigned long func = pStackLoc->MinorFunction;
KdPrint(("PNP Request (%u)\n", func));
NTSTATUS status = STATUS_SUCCESS;
switch (func)
{
case IRP_MN_REMOVE_DEVICE:
status = PnpRemoveDevice(pDeviceExt, pIrp);
break;
default:
status = PnpDefaultHandler(pDeviceExt, pIrp);
break;
}
KdPrint(("Leave HelloWDMPnp\n"));
return status;
}
- PNP派遣函数的第一个参数是设备对象,系统在回调该方法时会使用我们在AddDevice例程中创建出来的设备对象填写该参数。
- 通过当前IRP栈位置我们可以知道请求的次功能代码,在上述代码中我们只对移除设备功能代码做了特殊处理,其他PNP功能代码进行默认处理,IRP栈以后会讲到,目前不用在意。
PNP功能代码的默认处理函数如下:
/// @brief 对PNP IRP进行默认处理
/// @param[in] pDeviceExt 设备对象扩展
/// @param[in] pIrp I/O请求包
/// @return 状态
NTSTATUS PnpDefaultHandler(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp)
{
KdPrint(("Enter DefaultPnpHandler\n"));
// 略过当前堆栈
IoSkipCurrentIrpStackLocation(pIrp);
KdPrint(("Leave DefaultPnpHandler\n"));
// 用下层堆栈的驱动设备处理此IRP
return IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);
}
- 代码中我们只是略过当前堆栈,并将IRP转发给下层设备处理。
PNP移除设备代码的处理函数如下:
/// @brief PNP移除设备处理函数
/// @param[in] pDeviceExt 设备扩展对象
/// @param[in] pIrp 请求包
/// @return 状态
NTSTATUS PnpRemoveDevice(PDEVICE_EXTENSION pDeviceExt, PIRP pIrp)
{
KdPrint(("Enter HandleRemoveDevice\n"));
pIrp->IoStatus.Status = STATUS_SUCCESS;
NTSTATUS status = PnpDefaultHandler(pDeviceExt, pIrp);
// 删除符号链接
IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);
//调用IoDetachDevice()把设备对象从设备栈中脱开:
if (pDeviceExt->PNextStackDevice != NULL)
IoDetachDevice(pDeviceExt->PNextStackDevice);
//删除设备对象:
IoDeleteDevice(pDeviceExt->PDeviceObject);
KdPrint(("Leave HandleRemoveDevice\n"));
return status;
}
- 这个函数负责了驱动程序的卸载工作。
- 卸载前我们需要让下层设备先完成这个请求,所以我们调用了DefaultPnpHandler处理方法。
- 除了删除符号链接删除设备对象外,我们还需要将设备对象从设备栈中脱开。
4. 默认IRP处理例程
/// @brief 对默认IPR进行处理
NTSTATUS HelloWDMDispatchRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
UNREFERENCED_PARAMETER(pDeviceObject);
KdPrint(("Enter HelloWDMDispatchRoutine\n"));
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
KdPrint(("Leave WDMDriverDispatchRoutine\n"));
return STATUS_SUCCESS;
}
- 代码中我们简单的将IRP设置为完成。
5. 卸载驱动例程
WDM驱动的卸载工作在PNP处理函数中的RemoveDevice函数中完成,所以卸载驱动例程不用做任何事。
/// @brief 驱动程序卸载操作
void HelloWDMUnload(IN PDRIVER_OBJECT pDriverObject)
{
UNREFERENCED_PARAMETER(pDriverObject);
KdPrint(("Enter HelloWDMUnload\n"));
KdPrint(("Leave HelloWDMUnload\n"));
}
3. 编写INF文件
WDM驱动的安装需要使用INF文件,VS2013已经给我们创建了一个默认的INF文件,我们只需修改它就可以。INF文件描述了WDM驱动程序的操作硬件设备的信息和驱动程序的一些信息,INF文件的配置项很多目前我们只需复制如下的内容即可:
;
; chapter01HelloWDM.inf
;
[Version]
Signature="$WINDOWS NT$"
;如果设备是一个标准类别,使用标准类的名称和GUID 否则创建一个自定义的类别名称,并自定义它的GUID
;自定义的类别在注册表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\ 有显示
Class=%DeviceClassName%
ClassGuid={24B968AA-9C43-41D4-BFD5-0DED43B29F16}
;INF文件的提供者
Provider=Tester
DriverVer=
;如果不是标准类别设备,这里的配置必须的
[ClassInstall32]
Addreg=ClassAddReg
;对应的注册表是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\
[ClassAddReg]
HKR,,,,%DeviceClassName%
HKR,,Icon,,"-5"
;驱动文件需要复制到的目的目录,12表示%windir%/system32/drivers
[DestinationDirs]
DriverFilesName=12
;驱动文件名段
[DriverFilesName]
chapter01-HelloWDM.sys
[SourceDisksNames.x86]
1 = "HelloWDM Installation Disk"...
[SourceDisksNames.amd64]
1 = "HelloWDM Installation Disk"...
[SourceDisksFiles]
;源驱动文件在标记为1的磁盘下
DriverFilesName=1
;这里是设置制作商相关的选项,注意这里VS默认生成的标准设备的配置 如:%ManufacturerName%=Standard,NT$ARCH$
;如果不是标准类别设备这里必须修改,要不然最后加载的时候会出现259错误
;并且指定模型段
[Manufacturer]
%ManufacturerName%=MyDeviceModel,ntx86,ntamd64
;设备模型段
[MyDeviceModel]
;指定设备描述和设备ID,以及安装段
%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888
[MyDeviceModel.ntx86]
;指定设备描述和设备ID,以及安装段
%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888
[MyDeviceModel.ntamd64]
;指定设备描述和设备ID,以及安装段
%DeviceDesc%=DriverInstall.nt, PCI\VEN_8888&DEV_8888
;这里需要注意WIN2000及其以上的系统这里有个.NT
[DriverInstall.nt]
;指定需要拷贝的文件
CopyFiles=DriverFilesName
;指定写注册表的段
AddReg=InstallAddReg
[InstallAddReg]
HKLM, "System\CurrentControlSet\Services\TestWDMSvc\Parameters", "BreakOnEntry", 0x00010001, 0
;注册表中的服务名具体地址是 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
[DriverInstall.nt.Services]
;TestWDMSvc为安装分服务名称
Addservice = TestWDMSvc, 0x00000002, SysAddService
;服务的具体选项
[SysAddService]
DisplayName = TestWDMSvc
ServiceType = 1 ; SERVICE_KERNEL_DRIVER
StartType = 3 ; SERVICE_DEMAND_START
ErrorControl = 1 ; SERVICE_ERROR_NORMAL
ServiceBinary = %12%\chapter01-HelloWDM.sys
[Strings]
ManufacturerName="MySoft"
DeviceDesc="MyVEN_8888&DEV_8888Device"
DeviceClassName="TestClass"
4. 配置工程属性、编译
配置选项选择Win7Debug x64并且设置测试签名:
编译成功后可以在工程目录x64\Win7Debug文件夹下找到如下文件:
- chapter01-HelloWDM.sys,目标驱动文件
- chapter01HelloWDM.inf,安装配置文件
- chapter01-HelloWDM.cer,证书文件
5. 虚拟机配置
- 开启测试签名模式
- 提前开启DebugView
- 将chapter01-HelloWDM.cer,chapter01-HelloWDM.sys和chapter01HelloWDM.inf拷贝到虚拟机上
- 安装测试证书chapter01-HelloWDM.cer
6. 安装WDM驱动
1. 打开虚拟机的设备管理器点击添加过时硬件
2. 选择手动安装,点击下一步
3. 选择显示所有设备,点击下一步
4. 点击从磁盘安装
5. 点击浏览,选择chapter01HelloWDM.inf文件,点击确定
6. 点击下一步
7. 成功安装驱动
8. DebugView中可以看到调试信息
9. 设备管理器中可以看到我们安装的驱动程序
9. 点击我们的驱动程序,选择卸载驱动,DebugView中可以看到卸载驱动的调试信息
后话
本文完整工程和代码托管在GitHub上点我查看。
其他章节链接
VS2013 WDK8.1驱动开发2(最简单的WDM驱动)
还没有人评论...