Windows 驱动开发 新手入门(四)
引言
本系列所有文章
Windows 驱动开发 新手入门(一)
Windows 驱动开发 新手入门(二)
Windows 驱动开发 新手入门(三)
Windows 驱动开发 新手入门(四)
本篇文章介绍一下设备对象,这是写驱动过滤的基础,比如键盘,串口等等的过滤。
PDO
PDO
是Phsical 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 及更高版本。
IoAttachDeviceToDeviceStackSafe
和IoAttachDevice
的区别在于:在获取到 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
,一般我们绑定设备后,派遣函数中首先使用IoSkipCurrentIrpStackLocation
或IoCopyCurrentIrpStackLocationToNext
,然后使用IoCallDriver(pAttachedDeviceObject,pIrp)
,为了传给到目标设备栈,pAttachedDeviceObject
是目标设备栈的最顶层设备。