引言
本系列所有文章
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 参数从其堆栈位置复制到下一个较低驱动程序的堆栈位置。文章来源:https://www.toymoban.com/news/detail-400604.html
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
是目标设备栈的最顶层设备。文章来源地址https://www.toymoban.com/news/detail-400604.html
到了这里,关于Windows 驱动开发 新手入门(四)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!