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
);
  • 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;
}

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
);

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->DeviceObject;
}

设备绑定

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

IoAttachDevice

NTSTATUS IoAttachDevice(
  [in]  PDEVICE_OBJECT  SourceDevice,
  [in]  PUNICODE_STRING TargetDevice,
  [out] PDEVICE_OBJECT  *AttachedDevice
);
  • SourceDevice源设备(也就是我们生成的虚拟设备,用来过滤)
  • TargetDevice目标设备,也就是我们要绑定的设备,你也可以理解为附加(注意这是一个UNICODE_STRING指针,是设备名,而非设备对象)
  • AttachedDevice是用来返回的2级指针,绑定成功之后,返回目标设备栈最顶层设备

IoAttachDeviceToDeviceStackSafe

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

NTSTATUS IoAttachDeviceToDeviceStackSafe(
  [in]  PDEVICE_OBJECT SourceDevice,
  [in]  PDEVICE_OBJECT TargetDevice,
  [out] PDEVICE_OBJECT *AttachedToDeviceObject
);

这个函数最低受支持的客户端 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
);

获取绑定的设备

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

IoGetAttachedDevice

文件头ntifs.h,如果仅引入了ntddk.h,可以使用IoGetAttachedDeviceReference代替,它会多加一次引用计数。

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

看一下下面的代码回有直观的理解

void test(){
		ccpAttachDevice(driver, com_ob, &s_fltobj[i], &s_nextobj[i]);
		PDEVICE_OBJECT top = IoGetAttachedDeviceReference(s_nextobj[i]);
		
		DbgPrint("pTarget %p pFilter %p   pAttached %p top %p\n", com_ob, s_fltobj[i], s_nextobj[i], top);
		ObDereferenceObject(top);
}
pTarget FFFFE28A63151A60 pFilter FFFFE28A652AFE10   pAttached FFFFE28A63151A60 top FFFFE28A652AFE10

设备栈处理

IoSkipCurrentIrpStackLocation

void IoSkipCurrentIrpStackLocation(
  [in, out] PIRP Irp
);

驱动程序向下一个较低的驱动程序发送 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
);

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

IoCallDriver

#define IoCallDriver(a,b)   \
        IofCallDriver(a,b)
);


NTSTATUS IofCallDriver(
  PDEVICE_OBJECT        DeviceObject,
  __drv_aliasesMem PIRP Irp
);

IoCallDriver让传入的DeviceObject处理IRP,一般我们绑定设备后,派遣函数中首先使用IoSkipCurrentIrpStackLocationIoCopyCurrentIrpStackLocationToNext,然后使用IoCallDriver(pAttachedDeviceObject,pIrp),为了传给到目标设备栈,pAttachedDeviceObject是目标设备栈的最顶层设备。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没事干写博客玩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值