首先祝朋友们新年快乐,然后呢,因为无聊,写2篇文章打发时间,而且太久没弄过windows的东西了,算是回顾了,本文是对Windows
下的驱动开发
有一个简单的介绍,我尽可能写的小白文
一些,因为大多数的驱动开发书籍对新手来说还是过于难理解。
本篇文章通过WDM进行介绍,具体NT式驱动例子看这:
Windows 驱动开发 新手入门(二)
在介绍驱动开发
之前,先了解一下基础知识
,驱动
是什么的?
驱动
这个词是由Driver
直译的,这和平常开发中的测试驱动开发(TDD)
中的驱动
并不是个意思。
驱动
是在内核
下工作的,如果你了解过Windows
的一些内核对象
(如Event Mutex等),你也许会认为在Windows
中用户层
也可以随意获取到内核对象。实际上虽然我们在用户层
可以获取到,但这只是通过微软公开的API
获取的。
Windows Driver Kit 用于开发、测试和部署 Windows 驱动程序(官网原话)
由于我的电脑在去年中旬做过一次系统,所以索性装了VS2019,所以请挑选对应版本的WDK进行安装。
Windows 10 版本 2004 VS 2019 https://docs.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk
其他版本 https://docs.microsoft.com/zh-cn/windows-hardware/drivers/other-wdk-downloads
在安装完WDK后,新建项目选择Driver你会看见如下这些类型,VS2019中无法快速建立一个NT式驱动。
所以我们选择WDM驱动,我想先从WDM驱动介绍开始
可以清楚的看见只有一个inf文件,这个文件是之后驱动安装的文件,我们先跳过。
在Source Files
目录建立MyDriver.cpp
,并且将下面的代码拷贝进去,不要急,代码我会挑出关键部分单讲,并且我尽可能写了每行代码的注释。
//由于我们建立的是CPP文件,所以引入头文件和入口函数需要extern "C"
extern "C" {
#include
}
#define DEVICE_NAME L"\\Device\\MyWdmDevice" //定义设备名称
#define LINK_NAME L"\\??\\MyWdmLink" //定义符号链接,??代表以前的DosDevices
NTSTATUS DriverAddDevice(PDRIVER_OBJECT pDriverObj, PDEVICE_OBJECT pPhysicalDeviceObject);
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp);
NTSTATUS WDMPNP(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp);
void DriverUnload(PDRIVER_OBJECT pDriverObj);
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING pRegistryString)
{
UNREFERENCED_PARAMETER(pRegistryString); //不用将编译警告等级设为3,不使用 就Unreferenced即可
DbgPrint("驱动被加载\n");
//这里和nt驱动的区别是,我们不要去直接给MajorFunction 派遣 IRP_MJ_CREATE之类的
//这里我们需要用到扩展
//在WDM驱动程序中DriverEntry不再负责创建设备,而是交由AddDevice例程去创建设备。
pDriverObj->DriverExtension->AddDevice = DriverAddDevice;
pDriverObj->DriverUnload = DriverUnload; //这里不是真实的卸载
//IRP全部派遣到DisPatchRoutine
for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObj->MajorFunction[i] = DispatchRoutine;
}
//重写PNP派遣
pDriverObj->MajorFunction[IRP_MJ_PNP] = WDMPNP; //即插即用服务
return STATUS_SUCCESS;
}
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT PDeviceObject; ///< 设备对象
PDEVICE_OBJECT PNextStackDevice; ///< 下层设备对象指针
UNICODE_STRING DeviceName; ///< 设备名称
UNICODE_STRING SymLinkName; ///< 符号链接名
}DEVICE_EXTENSION, * PDEVICE_EXTENSION;
NTSTATUS DriverAddDevice(PDRIVER_OBJECT pDriverObj, PDEVICE_OBJECT pPhysicalDeviceObject)
{
DbgPrint("AddDevice\n");
NTSTATUS status = STATUS_SUCCESS;
UNICODE_STRING deviceName;
UNICODE_STRING linkName;
PDEVICE_OBJECT pDeviceObj = NULL; //创建设备对象
PDEVICE_EXTENSION pDeviceExt = NULL; //设备扩展对象
RtlInitUnicodeString(&deviceName, DEVICE_NAME); //初始化Unicode字符串 设备名称
RtlInitUnicodeString(&linkName, LINK_NAME);//初始化Unicode字符串 符号链接名称
//创建设备
status = IoCreateDevice(pDriverObj, sizeof(PDEVICE_EXTENSION), &deviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObj);
if (!NT_SUCCESS(status))
{
return status;
}
//让扩展对象指向刚刚创建的设备的扩展
pDeviceExt = (PDEVICE_EXTENSION)(pDeviceObj->DeviceExtension);
pDeviceExt->PDeviceObject = pDeviceObj;
pDeviceExt->DeviceName = deviceName;
pDeviceExt->SymLinkName = linkName;
// 将设备对象挂接在设备堆栈上
pDeviceExt->PNextStackDevice = IoAttachDeviceToDeviceStack(pDeviceObj, pPhysicalDeviceObject);
//创建符号链接
status = IoCreateSymbolicLink(&linkName, &deviceName);
if (!NT_SUCCESS(status))
{
IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);
status = IoCreateSymbolicLink(&linkName, &deviceName);
if (!NT_SUCCESS(status))
{
return status;
}
}
pDeviceObj->Flags |= DO_BUFFERED_IO | DO_POWER_PAGABLE;
pDeviceObj->Flags &= ~DO_DEVICE_INITIALIZING;
DbgPrint("Leave AddDevice\n");
return status;
}
/// @brief 对默认IPR进行处理
NTSTATUS DispatchRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
UNREFERENCED_PARAMETER(pDeviceObject);
DbgPrint("Enter WDMDispatchRoutine\n");
NTSTATUS status = STATUS_SUCCESS;
pIrp->IoStatus.Status = status;
pIrp->IoStatus.Information = 0;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
DbgPrint("Leave WDMDriverDispatchRoutine\n");
return status;
}
/// @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;
// 略过当前堆栈
IoSkipCurrentIrpStackLocation(pIrp);
// 用下层堆栈的驱动设备处理此IRP
NTSTATUS status = IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);
// 删除符号链接
IoDeleteSymbolicLink(&pDeviceExt->SymLinkName);
//调用IoDetachDevice()把设备对象从设备栈中脱开:
if (pDeviceExt->PNextStackDevice != NULL)
IoDetachDevice(pDeviceExt->PNextStackDevice);
//删除设备对象:
IoDeleteDevice(pDeviceExt->PDeviceObject);
DbgPrint("Leave HandleRemoveDevice\n");
return status;
}
NTSTATUS WDMPNP(IN PDEVICE_OBJECT pDeviceObj, IN PIRP pIrp)
{
PDEVICE_EXTENSION pDeviceExt = (PDEVICE_EXTENSION)pDeviceObj->DeviceExtension;
PIO_STACK_LOCATION pStackLoc = IoGetCurrentIrpStackLocation(pIrp);
unsigned long func = pStackLoc->MinorFunction;
DbgPrint("PNP Request (%u)\n", func);
NTSTATUS status = STATUS_SUCCESS;
switch (func)
{
case IRP_MN_REMOVE_DEVICE:
status = PnpRemoveDevice(pDeviceExt, pIrp);
break;
default:
// 略过当前堆栈
IoSkipCurrentIrpStackLocation(pIrp);
// 用下层堆栈的驱动设备处理此IRP
status = IoCallDriver(pDeviceExt->PNextStackDevice, pIrp);
break;
}
DbgPrint("Leave HelloWDMPnp\n");
return status;
}
/// @brief 驱动程序卸载操作
void DriverUnload(PDRIVER_OBJECT pDriverObj)
{
UNREFERENCED_PARAMETER(pDriverObj);
DbgPrint("Enter WDMUnload\n");
DbgPrint("Leave WDMUnload\n");
}
在之前写程序时,程序入口
函数为main
,参数有argc和argv代表着命令行的参数个数及对应的字符串指针,驱动
也有入口
函数为DriverEntry
,其返回类型为NTSTATUS
。
函数的第一个参数
pDriverObj
是刚被初始化的驱动对象指针
,
函数的第二个参数pRegistryString
是驱动在注册表
中的键值。
我们在驱动入口函数中看到了pDriverObj->MajorFunction
数组,其作用是针对每一个的事件
都有与之对应
的回调函数
。
因为WDM
驱动不在DriverEntry
入口中去创建设备
和对应的符号链接
,而是移到了DriverAddDevice
函数中,所以pDriverObj->MajorFunction
中我们并没有单独指定pDriverObj->MajorFunction[IRP_MJ_CREATE]
和pDriverObj->MajorFunction[IRP_MJ_CLOSE]
,而是将所有的派遣函数指向一个通用的回调函数DispatchRoutine
。
其中设备名称
在代码中被我们宏定义
为L"\\Device\\MyWdmDevice"
,符号链接
被我们定义为L"\\??\\MyWdmLink"
,??
代表着默认的路径,也就是DosDevices
。
Device
是用来和应用层通信的,应用层程序可以通过SymbolicLink
找到对应的设备。
IoCreateDevice
就是创建设备的函数
- 第一个参数为
驱动对象指针
- 第二个参数为
设备扩展结构(DeviceExtension)
的大小
- 第三个参数为
设备名称
,具体可以查看微软文档。
IoCreateSymbolicLink
就是创建符号链接的函数
- 第一个参数为
符号链接名称
- 第二个参数为
设备名称
设备扩展结构是和应用层通信时,可以读取或写入的信息的结构,之后NT式驱动的例子中会用到。
在通用的派遣函数DispatchRoutine
中,我们看到了一个参数pIrp
,IRP
的含义是I/O Request Packet
缩写,是在驱动中IO请求的结构体,其具体作用,我决定放在NT式驱动中细说。