Windows 驱动开发 新手入门(一)

您所在的位置:网站首页 驱动开发难不难做 Windows 驱动开发 新手入门(一)

Windows 驱动开发 新手入门(一)

2024-07-01 21:10| 来源: 网络整理| 查看: 265

Windows 驱动开发 新手入门(一) 引言驱动介绍Win10 WDK 建立一个驱动项目建立MyDriver.cpp理论知识驱动入口派遣函数 MajorFunctionDevice和SymbolicLinkDeviceExtensionIRP

引言

首先祝朋友们新年快乐,然后呢,因为无聊,写2篇文章打发时间,而且太久没弄过windows的东西了,算是回顾了,本文是对Windows下的驱动开发有一个简单的介绍,我尽可能写的小白文一些,因为大多数的驱动开发书籍对新手来说还是过于难理解。

本篇文章通过WDM进行介绍,具体NT式驱动例子看这: Windows 驱动开发 新手入门(二)

本系列所有文章 Windows 驱动开发 新手入门(三) Windows 驱动开发 新手入门(四)

驱动介绍

在介绍驱动开发之前,先了解一下基础知识,驱动是什么的?

驱动这个词是由Driver直译的,这和平常开发中的测试驱动开发(TDD)中的驱动并不是一个意思。 驱动是在内核下工作的,如果你了解过Windows的一些内核对象(如Event Mutex等),你也许会认为在Windows中用户层也可以随意获取到内核对象。实际上虽然我们在用户层可以获取到,但这只是通过微软公开的API获取的。

驱动(Driver)我们可以理解为系统和硬件交互用的,我们不需要知道底层硬件是什么样子的,我们不需要为每个硬件单独写一份代码,因为我们可以通过系统去和硬件交互,使用系统提供的API来和硬件交互,这些操作都是在系统内核中完成的。它是一套在Windows中的标准,我们不用关心硬件底层是如何实现的,这就像是DLL是用户层模块,驱动就是内核层模块。

设备(Device),可以是关联的物理设备,也可以是我们在驱动中创建的虚拟设备,应用层(通过Symbolic Link找到Device)向Device发送IRP(I/O Request Packet) IO请求包,此时我们的驱动就可以处理这些请求。我们为了拥有更高的权限,一般会创建一个虚拟设备,仅仅只是为了让代码在内核中工作。

符号链接(Symbolic Link),Windows中可以通过mklink直接创建符号链接,它的作用你可以理解为Linux中的软连接,同样我们也可以通过代码创建一个符号链接,只是它指向的是一个Device,应用层通过符号链接找到设备,此时通过IRP就可以和驱动进行交互了。

Win10 WDK

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式驱动。 1 所以我们选择WDM驱动,我想先从WDM驱动介绍开始 在这里插入图片描述

可以清楚的看见只有一个inf文件,这个文件是之后驱动安装的文件,我们先跳过。 1

建立MyDriver.cpp

在Source Files目录建立MyDriver.cpp,并且将下面的代码拷贝进去,不要急,代码我会挑出关键部分单讲,并且我尽可能写了每行代码的注释。

//由于我们建立的是CPP文件,所以引入头文件和入口函数需要extern "C" extern "C" { #include } #define DEVICE_NAME L"\\Device\\MyWdmDevice" //定义设备名称 #define LINK_NAME L"\\??\\MyWdmLink" //定义符号链接 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 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是驱动在注册表中的键值。

DriverUnload是驱动卸载的回调,如果我们不设置DriverUnload,那么此时我们将无法正常的卸载驱动,系统这么做的原因是为了保证系统的稳定性。 比如我们在DriverEntry中添加了某些系统回调,此时我们没有DriverUnload,因此系统不知道什么时候该移除这些回调,如果暴力移除驱动,此时系统回调会出问题,系统回调表中存在了一个被移除掉的驱动的回调,当调用时系统蓝屏 。

派遣函数 MajorFunction

我们在驱动入口函数中看到了pDriverObj->MajorFunction数组,其作用是针对每一个的事件都有与之对应的回调函数。

因为WDM驱动不在DriverEntry入口中去创建设备和对应的符号链接,而是移到了DriverAddDevice函数中,所以pDriverObj->MajorFunction中我们并没有单独指定pDriverObj->MajorFunction[IRP_MJ_CREATE]和pDriverObj->MajorFunction[IRP_MJ_CLOSE],而是将所有的派遣函数指向一个通用的回调函数DispatchRoutine。

Device和SymbolicLink

其中设备名称在代码中被我们宏定义为L"\\Device\\MyWdmDevice",符号链接被我们定义为L"\\??\\MyWdmLink" 。

Device是用来和应用层通信的,应用层程序可以通过SymbolicLink找到对应的设备。

IoCreateDevice就是创建设备的函数

第一个参数为驱动对象指针第二个参数为设备扩展结构(DeviceExtension)的大小第三个参数为设备名称,具体可以查看微软文档。

IoCreateSymbolicLink就是创建符号链接的函数

第一个参数为符号链接名称第二个参数为设备名称 DeviceExtension

设备扩展结构,你可以将某些信息保存在扩展结构中,这样可以避免使用全局变量,应用层通信时,可以读取或写入的信息的结构,之后NT式驱动的例子中会用到。

IRP

在通用的派遣函数DispatchRoutine中,我们看到了一个参数pIrp,IRP的含义是 I/O Request Packet缩写,是在驱动中IO请求的结构体,其具体作用,我决定放在NT式驱动中细说。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3