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驱动开发1(最简单的NT驱动)

VS2013 WDK8.1驱动开发2(最简单的WDM驱动)

VS2013 WDK8.1驱动开发3(手动加载NT驱动程序)

VS2013 WDK8.1驱动开发4(NT式驱动基本结构)

VS2013 WDK8.1驱动开发5(WDM驱动基本结构)

VS2013 WDK8.1驱动开发6(内存管理)

VS2013 WDK8.1驱动开发7(派遣函数)

VS2013 WDK8.1驱动开发8(设备读写操作)

VS2013 WDK8.1驱动开发9(IO设备控制操作)


赞助作者写出更好文章


分享给朋友阅读吧


您还未登录,登录微博账号发表精彩评论

 微博登录


最新评论

    还没有人评论...

 

 

刘杰

28岁, 现居苏州

微博:

微信:

BurnellLIU

邮箱:

burnell_liu@outlook.com

Github:

https://github.com/BurnellLiu

简介:

努力做一个快乐的程序员, good good study, day day up!

友情链接: Will Mao

苏ICP备16059872号. Copyright © 2017. http://www.coderjie.com. All rights reserved.