• Windows 驱动开发 新手入门(四)


    引言

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

    本篇文章介绍一下设备对象,这是写驱动过滤的基础,比如键盘,串口等等的过滤。

    PDO

    PDOPhsical Device Object的缩写,直译就是物理设备对象,一般来说,PDO就是在就是DeviceStack中最下层的设备对象。

    获取设备对象

    IoGetDeviceObjectPointer

    IoGetDeviceObjectPointer 只能返回设备对象地址。

    NTSTATUS IoGetDeviceObjectPointer(
      [in]  PUNICODE_STRING ObjectName,
      [in]  ACCESS_MASK     DesiredAccess,
      [out] PFILE_OBJECT    *FileObject,
      [out] PDEVICE_OBJECT  *DeviceObject
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • ObjectName 指向包含 Unicode 字符串的缓冲区的指针,该字符串是设备对象的名称。

    • DesiredAccess 指定表示所需访问的权限掩码值。 通常 ,DesiredAccess指定FILE_READ_DATA。 不经常指定FILE_WRITE_DATA或FILE_ALL_ACCESS访问权限。

    • FileObject 指向表示相应设备对象到用户模式代码的文件对象的指针(如果调用成功)。

    • DeviceObject 指向表示命名逻辑、虚拟或物理设备(如果调用成功)的设备对象的指针。

    IoGetDeviceObjectPointer 小栗子

    下面的栗子是打开串口3的设备。

    PDEVICE_OBJECT OpenSeria3()
    {
    	PFILE_OBJECT pFileObject = NULL;
    	PDEVICE_OBJECT pDeviceObject = NULL;
    	UNICODE_STRING deviceNameStr;
    	RtlInitUnicodeString(&deviceNameStr, L"\\Device\\Serial3");
    	NTSTATUS status = IoGetDeviceObjectPointer(&deviceNameStr, FILE_ALL_ACCESS, &pFileObject, &pDeviceObject);
    	if (status == STATUS_SUCCESS)
    		ObDereferenceObject(pFileObject);//用不到file_object,所以先解除引用
    	return pDeviceObject;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ObReferenceObjectByName

    需要自己手动导入,ObReferenceObjectByName 能够返回任意对象地址,所以它同样可以返回设备对象。

    //使用NTKERNELAPI宏
    NTKERNELAPI NTSTATUS ObReferenceObjectByName(__in PUNICODE_STRING  ObjectName,
    	__in ULONG  Attributes,
    	__in_opt PACCESS_STATE  AccessState,
    	__in_opt ACCESS_MASK  DesiredAccess,
    	__in POBJECT_TYPE  ObjectType,
    	__in KPROCESSOR_MODE  AccessMode,
    	__inout_opt PVOID  ParseContext,
    	__out PVOID* Object
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ObReferenceObjectByName小栗子

    这次我们获取键盘驱动对象

    //直接声明即可,其实是存在的
    extern POBJECT_TYPE IoDriverObjectType;
    PDEVICE_OBJECT OpenKeyboard()
    {
    	
    	PDRIVER_OBJECT pKbdDriverObject = NULL;
    	UNICODE_STRING driverNameStr;
    	RtlInitUnicodeString(&driverNameStr, L"\\Driver\\Kbdclass");// Kbdclass驱动名
    
    	NTSTATUS status = ObReferenceObjectByName(&driverNameStr, OBJ_CASE_INSENSITIVE, NULL, 0, IoDriverObjectType, KernelMode, NULL, &pKbdDriverObject);
    	if (status == STATUS_SUCCESS) {
    		//调用ObReferenceObjectByName后,对象引用计数会+1,我们需要解引
    		///不是说我们用不到它,是因为它本身就存在,是我们导致的引用计数增加了
    		ObDereferenceObject(pKbdDriverObject);
    	}
    	
    	return pKbdDriverObject;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    设备绑定

    Windows系统为了方便开发者,无论在应用层还是内核层都实现了分层的设计,比如应用层的LSP,驱动开发中也同样,我们想实现过滤器,就要在设备的上层和下层之间,插入我们自己的虚拟设备,让我们在不影响正常数据的情况下,实现过滤。

    IoAttachDevice

    NTSTATUS IoAttachDevice(
      [in]  PDEVICE_OBJECT  SourceDevice,
      [in]  PUNICODE_STRING TargetDevice,
      [out] PDEVICE_OBJECT  *AttachedDevice
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • SourceDevice源设备(也就是我们生成的虚拟设备,用来过滤)
    • TargetDevice目标设备,也就是我们要绑定的设备,你也可以理解为附加(注意这是一个UNICODE_STRING指针,是设备名,而非设备对象)
    • AttachedDevice是用来返回的2级指针,绑定成功之后,被绑定设备将会返回到传入的1级指针地址中。

    IoAttachDeviceToDeviceStackSafe

    这个和IoAttachDevice的区别是参数TargetDevice也是设备对象了。

    NTSTATUS IoAttachDeviceToDeviceStackSafe(
      [in]  PDEVICE_OBJECT SourceDevice,
      [in]  PDEVICE_OBJECT TargetDevice,
      [out] PDEVICE_OBJECT *AttachedToDeviceObject
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这个函数最低受支持的客户端 Windows 2000 Service Pack 4 (SP4) 和 Windows XP 及更高版本。

    IoAttachDeviceToDeviceStackSafeIoAttachDevice的区别在于:在获取到 I/O 系统数据库锁时更新此字段
    由于它获取到了锁, 因此,如果SourceDevice对象在其AttachedToDeviceObject字段更新之前收到一个IRP,IoAttachDeviceToDeviceStackSafe可避免可能发生的竞争状况。

    IoAttachDeviceToDeviceStack

    IoAttachDeviceToDeviceStackSafe一样,只是这个我并不推荐使用,他并没有获取锁。

    PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
      [in] PDEVICE_OBJECT SourceDevice,
      [in] PDEVICE_OBJECT TargetDevice
    );
    
    • 1
    • 2
    • 3
    • 4

    封装一个绑定设备的函数

    创建设备在之前的文章就写过,但是这次我们需要创建的不是FILE_DEVICE_UNKNOWN,而是和目标设备相同类型的设备。

    //封装一个绑定设备函数
    NTSTATUS status;
    
    	//创建一个和目标设备相同类型的设备
    	status = IoCreateDevice(pDriver, 0, NULL, pTargetDevice->DeviceType, 0, FALSE, pFilterDevice);
    	if (status != STATUS_SUCCESS)
    		return status;
    
    	//设置相同的标志
    	if (pTargetDevice->Flags & DO_BUFFERED_IO)
    		(*pFilterDevice)->Flags |= DO_BUFFERED_IO;
    	if (pTargetDevice->Flags & DO_DIRECT_IO)
    		(*pFilterDevice)->Flags |= DO_DIRECT_IO;
    
    	//如果你需要IRP_MJ_POWER,此时派遣函数是在PASSIVE_LEVEL的
    	//如果此时没有这个标志,电源管理器则可以在DISPATCH_LEVEL发送电源管理请求
    	//此时如果访问到分页内存,就会蓝屏
    	(*pFilterDevice)->Flags |= DO_POWER_PAGABLE;
    		
    
    	//相同特性
    	if (pTargetDevice->Characteristics & FILE_DEVICE_SECURE_OPEN)
    		(*pFilterDevice)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
    
    
    	//绑定设备
    	status = IoAttachDeviceToDeviceStackSafe(*pFilterDevice, pTargetDevice, pAttachedDevice);
    	if (status != STATUS_SUCCESS)//绑定失败删除
    		IoDeleteDevice(*pFilterDevice);
    	else
    		(*pFilterDevice)->Flags & ~DO_DEVICE_INITIALIZING;//设置这个设备已经初始化过了
    
    	return status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    获取绑定的设备

    绑定之后的设备就是在设备栈中的最顶层,所以下面这个API就是获取最顶层的设备

    IoGetAttachedDevice

    PDEVICE_OBJECT IoGetAttachedDevice(
      [in] PDEVICE_OBJECT DeviceObject
    );
    
    • 1
    • 2
    • 3
    • DeviceObject 指向要返回其最顶层附加设备的设备对象的指针。

    设备栈处理

    IoSkipCurrentIrpStackLocation

    void IoSkipCurrentIrpStackLocation(
      [in, out] PIRP Irp
    );
    
    • 1
    • 2
    • 3

    驱动程序向下一个较低的驱动程序发送 IRP 时,如果我们不打算提供IoCompletion例程(也就是我们不处理,直接交给我们绑定之前的设备处理),驱动程序可以调用IoSkipCurrentIrpStackLocation。如果在调用IoCallDriver之前调用IoSkipCurrentIrpStackLocation,则下一个较低的驱动程序会收到与我们驱动程序相同的IO_STACK_LOCATION

    如果打算为IRP提供IoCompletion例程,应调用IoCopyCurrentIrpStackLocationToNext而不是IoSkipCurrentIrpStackLocation
    如果驱动程序已挂起 IRP,则驱动程序不应将 IRP 传递给下一个较低驱动程序之前调用IoSkipCurrentIrpStackLocation。如果驱动程序在将挂起的 IRP 传递给下一个较低的驱动程序之前调用IoSkipCurrentIrpStackLocation ,则仍会在下一个驱动程序的 I/O 堆栈位置的控制成员中设置 SL_PENDING_RETURNED标志。因为下一个驱动程序拥有该堆栈位置并可能修改它,它可能会清除挂起标志。这种情况可能会导致操作系统发出错误检查或 IRP 的处理永远不会完成。
    相反,已挂起 IRP 的驱动程序应在调用IoCallDriver之前调用IoCopyCurrentIrpStackLocationToNext为下一个较低的驱动程序设置新的堆栈位置。

    IoCopyCurrentIrpStackLocationToNext

    void IoCopyCurrentIrpStackLocationToNext(
      [in, out] PIRP Irp
    );
    
    • 1
    • 2
    • 3

    IoCopyCurrentIrpStackLocationToNext 将 IRP 参数从其堆栈位置复制到下一个较低驱动程序的堆栈位置。

    IoCallDriver

    #define IoCallDriver(a,b)   \
            IofCallDriver(a,b)
    );
    
    
    NTSTATUS IofCallDriver(
      PDEVICE_OBJECT        DeviceObject,
      __drv_aliasesMem PIRP Irp
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    IoCallDriver让传入的DeviceObject处理IRP,一般我们绑定设备后,派遣函数中首先使用IoSkipCurrentIrpStackLocationIoCopyCurrentIrpStackLocationToNext,然后使用IoCallDriver(pAttachedDeviceObject,pIrp)

  • 相关阅读:
    C语言--每日五道选择题--Day21
    (Matalb分类预测)GWO-BP灰狼算法优化BP神经网络的多维分类预测
    GPIO子系统(三)
    Kube-OVN v1.10.0:新增Windows节点支持,用户自定义子网ACL等10+硬核功能
    springboot自定义注解防止表单重复提交
    ptp4l测试-LinuxPTP\ptp4l配置与问题排查
    效率出图!9款最好用的矢量图软件推荐
    CDH大数据平台 Error: Package: 1:mariadb-devel-5.5.68-1.el7.x86_64 (base)
    分布式架构在云计算平台中的应用及优缺点
    2.7 Python-运算符
  • 原文地址:https://blog.csdn.net/slslslyxz/article/details/125230137