Windows驱动之IoRegisterPlugPlayNotification

文章目录

  • Windows驱动之IoRegisterPlugPlayNotification
    • 1. IoRegisterPlugPlayNotification
      • 1.1 函数声明
      • 1.2 使用
    • 2. 内核PNP事件原理
      • 2.1 事件队列
      • 2.2 IoRegisterPlugPlayNotification
      • 2.3 PNP通知事件的到来
      • 2.4 IoUnregisterPlugPlayNotification

Windows驱动之IoRegisterPlugPlayNotification

Windows提供了方法来通知用户层和内核层部件PNP事件发生,其中在用户层,有两种方式,分别是窗口和服务程序的,如下:

  1. RegisterDeviceNotification : 注册窗口的PNP事件,当事件到达的时候,就会接收到WM_DEVICECHANGE.
  2. RegisterServiceCtrlHandlerEx : 注册驱动的服务接口,接收PNP事件。

此外,内核中也提供了IoRegisterPlugPlayNotification 函数来注册PNP事件的回调函数,本文重点来讨论一下内核中的PNP事件。

1. IoRegisterPlugPlayNotification

1.1 函数声明

NTSTATUS IoRegisterPlugPlayNotification(
  _In_     IO_NOTIFICATION_EVENT_CATEGORY        EventCategory,
  _In_     ULONG                                 EventCategoryFlags,
  _In_opt_ PVOID                                 EventCategoryData,
  _In_     PDRIVER_OBJECT                        DriverObject,
  _In_     PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
  _In_opt_ PVOID                                 Context,
  _Out_    PVOID                                 *NotificationEntry
);

typedef NTSTATUS 
  DRIVER_NOTIFICATION_CALLBACK_ROUTINE(
    _In_ PVOID NotificationStructure,
    _Inout_opt_ PVOID Context
    );
  1. EventCategory 这个特指PNP事件的类别:
    • EventCategoryDeviceInterfaceChange : 设备实例到来和移除的消息。
    • EventCategoryHardwareProfileChange : 硬件Profile消息。
    • EventCategoryTargetDeviceChange : 设备改变消息,例如设备插入和设备的移除。
  2. EventCategoryFlags : 这个标记针对EventCategoryDeviceInterfaceChange这种事件有效,当标记PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, 这个函数就会枚举当前已经存在的设备接口。
  3. EventCategoryData : 各种不同PNP事件对应的而外信息:
    • EventCategoryDeviceInterfaceChange 这个类别的话,这个程序将提供一个设备的GUID。
    • EventCategoryHardwareProfileChange : 必须为NULL。
    • EventCategoryTargetDeviceChange : 必须指定文件对象。
  4. NotificationEntry : 返回值,用来反注册(IoUnregisterPlugPlayNotificationEx)通知事件。

1.2 使用

NTSTATUS OnPnpNotify(PPLUGPLAY_NOTIFICATION_HEADER p, PREG_RECORD reg)
{
	PAGED_CODE();
	char *msg;

	if (p->Event == GUID_DEVICE_INTERFACE_ARRIVAL)
		msg = "arrival";
	else if (p->Event == GUID_DEVICE_INTERFACE_REMOVAL)
		msg = "removal";
	else if (p->Event == GUID_TARGET_DEVICE_QUERY_REMOVE)
		msg = "target query remove";
	else if (p->Event == GUID_TARGET_DEVICE_REMOVE_CANCELLED)
		msg = "target remove cancelled";
	else if (p->Event == GUID_TARGET_DEVICE_REMOVE_COMPLETE)
		msg = "target remove complete";
	else 
		msg = "custom notification";
	return STATUS_SUCCESS;

}
NTSTATUS Register(PDEVICE_EXTENSION pdx, PREGISTER_PARAMS p)
{
	//...
	return IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange,
		PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,
		&p->guid, 
		pdx->DriverObject,
		(PDRIVER_NOTIFICATION_CALLBACK_ROUTINE) OnPnpNotify, 
		reg,
		&reg->InterfaceNotificationEntry);
}  

2. 内核PNP事件原理

2.1 事件队列

在内核中,存在一个链表,记录着其他模块向系统注册过的PNP事件回调函数,这个链表如下:

LIST_ENTRY PnpNotifyListHead;
BOOLEAN
INIT_FUNCTION
NTAPI
IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
    //...
    InitializeListHead(&PnpNotifyListHead);
    //...
}

其实这个队列中,保存的是PNP_NOTIFY_ENTRY结构体信息,这个结构体记录着IoRegisterPlugPlayNotification注册的具体结构信息,这个结构体如下:

typedef enum _IO_NOTIFICATION_EVENT_CATEGORY {
  EventCategoryReserved,
  EventCategoryHardwareProfileChange,
  EventCategoryDeviceInterfaceChange,
  EventCategoryTargetDeviceChange
} IO_NOTIFICATION_EVENT_CATEGORY;

typedef NTSTATUS
(NTAPI DRIVER_NOTIFICATION_CALLBACK_ROUTINE)(
  _In_ PVOID NotificationStructure,
  _Inout_opt_ PVOID Context);

typedef DRIVER_NOTIFICATION_CALLBACK_ROUTINE *PDRIVER_NOTIFICATION_CALLBACK_ROUTINE;

typedef struct _PNP_NOTIFY_ENTRY
{
    LIST_ENTRY PnpNotifyList;
    IO_NOTIFICATION_EVENT_CATEGORY EventCategory;
    PVOID Context;
    UNICODE_STRING Guid;
    PFILE_OBJECT FileObject;
    PDRIVER_OBJECT DriverObject;
    PDRIVER_NOTIFICATION_CALLBACK_ROUTINE PnpNotificationProc;
} PNP_NOTIFY_ENTRY, *PPNP_NOTIFY_ENTRY;

所有的PNP_NOTIFY_ENTRY都是通过PnpNotifyList串联起来。

2.2 IoRegisterPlugPlayNotification

这个函数就是向PnpNotifyListHead这个列表中注册一个PNP_NOTIFY_ENTRY,当PNP事件到来的时候,就会遍历这个链表调用回调函数,这个函数的实现如下:

NTSTATUS
NTAPI
IoRegisterPlugPlayNotification(IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
                               IN ULONG EventCategoryFlags,
                               IN PVOID EventCategoryData OPTIONAL,
                               IN PDRIVER_OBJECT DriverObject,
                               IN PDRIVER_NOTIFICATION_CALLBACK_ROUTINE CallbackRoutine,
                               IN PVOID Context,
                               OUT PVOID *NotificationEntry)
{
    PPNP_NOTIFY_ENTRY Entry;
    //...
    if (EventCategory == EventCategoryDeviceInterfaceChange &&
        EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES)
    {
        DEVICE_INTERFACE_CHANGE_NOTIFICATION NotificationInfos;
        UNICODE_STRING SymbolicLinkU;
        PWSTR SymbolicLink;
        Status = IoGetDeviceInterfaces((LPGUID)EventCategoryData,
                                       NULL, /* PhysicalDeviceObject OPTIONAL */
                                       0, /* Flags */
                                       &SymbolicLinkList);
        if (NT_SUCCESS(Status))
        {
            /* Enumerate SymbolicLinkList */
            NotificationInfos.Version = 1;
            NotificationInfos.Size = sizeof(DEVICE_INTERFACE_CHANGE_NOTIFICATION);
            RtlCopyMemory(&NotificationInfos.Event,
                          &GUID_DEVICE_INTERFACE_ARRIVAL,
                          sizeof(GUID));
            RtlCopyMemory(&NotificationInfos.InterfaceClassGuid,
                          EventCategoryData,
                          sizeof(GUID));
            NotificationInfos.SymbolicLinkName = &SymbolicLinkU;

            for (SymbolicLink = SymbolicLinkList;
                 *SymbolicLink;
                 SymbolicLink += wcslen(SymbolicLink) + 1)
            {
                RtlInitUnicodeString(&SymbolicLinkU, SymbolicLink);
                DPRINT("Calling callback routine for %S\n", SymbolicLink);
                (*CallbackRoutine)(&NotificationInfos, Context);  //已经存在的符号链接调用一下回调函数
            }

            ExFreePool(SymbolicLinkList);
        }
    }

    Entry->PnpNotificationProc = CallbackRoutine;
    Entry->EventCategory = EventCategory;
    Entry->Context = Context;
    Entry->DriverObject = DriverObject;
    switch (EventCategory)
    {
        case EventCategoryDeviceInterfaceChange:
        {
            Status = RtlStringFromGUID(EventCategoryData, &Entry->Guid);
            if (!NT_SUCCESS(Status))
            {
                ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY);
                ObDereferenceObject(DriverObject);
                return Status;
            }
            break;
        }
        case EventCategoryHardwareProfileChange:
        {
           break;
        }
        case EventCategoryTargetDeviceChange:
        {
            Entry->FileObject = (PFILE_OBJECT)EventCategoryData;
            break;
        }
        default:
        {
            break;
        }
    }

    KeAcquireGuardedMutex(&PnpNotifyListLock);
    InsertHeadList(&PnpNotifyListHead,
                   &Entry->PnpNotifyList);
    KeReleaseGuardedMutex(&PnpNotifyListLock);

    *NotificationEntry = Entry;

    return STATUS_SUCCESS;
}

其实这个函数流程很简单,主要三个:

  1. EventCategoryFlags & PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES判断是否枚举已经存在的接口信息,如果是,那么从注册表中枚举当前已经存在的接口并调用回调函数(*CallbackRoutine)(&NotificationInfos, Context).
  2. 分配一个PPNP_NOTIFY_ENTRY节点。
  3. 节点插入到队列中InsertHeadList(&PnpNotifyListHead, &Entry->PnpNotifyList);.

PPNP_NOTIFY_ENTRY插入队列之后,就等待PNP事件到来并遍历链表调用了。

2.3 PNP通知事件的到来

在内核中,PNP事件的通知是调用IopNotifyPlugPlayNotification这个函数来通知的,这个函数调用的地方非常多,各种PNP事件都会导致这个函数调用,如下:
Windows驱动之IoRegisterPlugPlayNotification_第1张图片
这里有一个函数IoSetDeviceInterfaceState, 使能设备接口的时候,就会调用这个函数,如下:

NTSTATUS
NTAPI
IoSetDeviceInterfaceState(IN PUNICODE_STRING SymbolicLinkName,
                          IN BOOLEAN Enable)
{
    //...
    EventGuid = Enable ? &GUID_DEVICE_INTERFACE_ARRIVAL : &GUID_DEVICE_INTERFACE_REMOVAL;
    IopNotifyPlugPlayNotification(
        PhysicalDeviceObject,
        EventCategoryDeviceInterfaceChange,
        EventGuid,
        &DeviceGuid,
        (PVOID)SymbolicLinkName);

    ObDereferenceObject(PhysicalDeviceObject);
    return STATUS_SUCCESS;
}

这里就会有个GUID_DEVICE_INTERFACE_ARRIVAL或者GUID_DEVICE_INTERFACE_REMOVALPNP消息通知。

其中IopNotifyPlugPlayNotification通知函数的实现很简单,如下:

VOID
IopNotifyPlugPlayNotification(
    IN PDEVICE_OBJECT DeviceObject,
    IN IO_NOTIFICATION_EVENT_CATEGORY EventCategory,
    IN LPCGUID Event,
    IN PVOID EventCategoryData1,
    IN PVOID EventCategoryData2)
{
    //...
    ListEntry = PnpNotifyListHead.Flink;
    while (ListEntry != &PnpNotifyListHead)
    {
        ChangeEntry = CONTAINING_RECORD(ListEntry, PNP_NOTIFY_ENTRY, PnpNotifyList);
        CallCurrentEntry = FALSE;
        //...
        if (CallCurrentEntry)
        {
            KeReleaseGuardedMutex(&PnpNotifyListLock);
            (ChangeEntry->PnpNotificationProc)(NotificationStructure,
                                               ChangeEntry->Context);
            KeAcquireGuardedMutex(&PnpNotifyListLock);
        }
        //...
    }
}

总结过来就两点:

  1. 构造具体PNP类型对应的结构体NotificationStructure.
  2. 遍历PnpNotifyListHead 并调用合适的回调函数。

2.4 IoUnregisterPlugPlayNotification

当然注册了之外就可以移除,移除的实现如下(就是从PnpNotifyList链表中摘除PPNP_NOTIFY_ENTRY):

NTSTATUS
NTAPI
IoUnregisterPlugPlayNotification(IN PVOID NotificationEntry)
{
    PPNP_NOTIFY_ENTRY Entry;
    PAGED_CODE();

    Entry = (PPNP_NOTIFY_ENTRY)NotificationEntry;

    KeAcquireGuardedMutex(&PnpNotifyListLock);
    RemoveEntryList(&Entry->PnpNotifyList);
    KeReleaseGuardedMutex(&PnpNotifyListLock);

    RtlFreeUnicodeString(&Entry->Guid);

    ObDereferenceObject(Entry->DriverObject);

    ExFreePoolWithTag(Entry, TAG_PNP_NOTIFY);

    return STATUS_SUCCESS;
}

你可能感兴趣的:(Windows驱动开发)