键盘filter驱动程序卸载蓝屏

我开发了一个键盘filter驱动程序,将键盘button“1”(Qbutton上方)更改为“2”。

这个驱动工作正常。

但是,执行卸载后,按下键盘button会导致蓝屏死机。

如果驱动程序在没有按下键盘button的情况下被加载和卸载,它将被正常地卸载。

当我用Windbg检查它时,我的驱动程序的ReadCompletion()函数即使在卸载后也被调用。

即使我调用了IoDetachDevice()和IoDeleteDevice(),我也不知道为什么会发生这种情况。

另外,加载驱动程序后,如果您在开始时按下键盘button“1”,则不会更改为“2”。

然后它变化很好。

我不知道这是什么关系。

我希望你能find解决这个问题的办法。

请回答关于我的问题。

以下是源代码。

#include <wdm.h> typedef struct { PDEVICE_OBJECT NextLayerDeviceObject; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; const WCHAR next_device_name[] = L"\\Device\\KeyboardClass0"; const char dbg_name[] = "[Test]"; NTSTATUS IrpSkip(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS ret = STATUS_SUCCESS; PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp); DbgPrint("%s IrpSkip() Start\n", dbg_name); DbgPrint("%s IrpSkip() - MajorFunction %d\n", dbg_name, Stack->MajorFunction); IoSkipCurrentIrpStackLocation(Irp); ret = IoCallDriver(((PDEVICE_EXTENSION)(DeviceObject->DeviceExtension))->NextLayerDeviceObject, Irp); DbgPrint("IoCallDriver return %x\n", ret); DbgPrint("%s IrpSkip() End\n", dbg_name); return ret; } NTSTATUS ReadCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context) { NTSTATUS ret = STATUS_SUCCESS; PIO_STACK_LOCATION Stack; unsigned char key[32]; DbgPrint("%s ReadCompletion() Start\n", dbg_name); if (Irp->IoStatus.Status == STATUS_SUCCESS) { DbgPrint("%s ReadCompletion() - Success\n", dbg_name); RtlCopyMemory(key, Irp->AssociatedIrp.SystemBuffer, 32); DbgPrint("%s Data : %d %d %d %d %d %d %d %d\n", dbg_name, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7]); if (key[2] == 2) { key[2] = 3; RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, key, 32); DbgPrint("%s Key '1' changed '2'\n", dbg_name); } } //else if (Irp->IoStatus.Status == STATUS_PENDING) else { DbgPrint("%s ReadCompletion() - Fail... %x\n", Irp->IoStatus.Status); } if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } DbgPrint("%s ReadCompletion() End\n", dbg_name); return Irp->IoStatus.Status; } NTSTATUS Read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { NTSTATUS ret = STATUS_SUCCESS; PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(Irp); DbgPrint("%s Read() Start\n", dbg_name); PDEVICE_EXTENSION device_extension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; //IoCopyCurrentIrpStackLocationToNext(Irp); PIO_STACK_LOCATION current_irp = IoGetCurrentIrpStackLocation(Irp); PIO_STACK_LOCATION next_irp = IoGetNextIrpStackLocation(Irp); *next_irp = *current_irp; IoSetCompletionRoutine(Irp, ReadCompletion, DeviceObject, TRUE, TRUE, TRUE); ret=IoCallDriver(((PDEVICE_EXTENSION)device_extension)->NextLayerDeviceObject, Irp); DbgPrint("%s Read() End\n", dbg_name); return ret; } NTSTATUS Unload(IN PDRIVER_OBJECT DriverObject) { NTSTATUS ret = STATUS_SUCCESS; IoDetachDevice(((PDEVICE_EXTENSION)(DriverObject->DeviceObject->DeviceExtension))->NextLayerDeviceObject); IoDeleteDevice(DriverObject->DeviceObject); DbgPrint("%s Unload()...\n", dbg_name); return ret; } NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS ret=STATUS_SUCCESS; UNICODE_STRING _next_device_name; DbgSetDebugFilterState(DPFLTR_DEFAULT_ID, DPFLTR_INFO_LEVEL, TRUE); DbgPrint("%s DriverEntry() Start\n", dbg_name); RtlInitUnicodeString(&_next_device_name, next_device_name); for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION ; i++) { DriverObject->MajorFunction[i] = IrpSkip; } DriverObject->DriverUnload = Unload; DriverObject->MajorFunction[IRP_MJ_READ] = Read; PDEVICE_OBJECT DeviceObject = 0; PDEVICE_EXTENSION DeviceExtension; ret = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), 0, FILE_DEVICE_KEYBOARD, 0, TRUE, &DeviceObject); if (ret == STATUS_SUCCESS) { DbgPrint("%s DriverEntry() - IoCreateDevice() Success\n", dbg_name); } else { DbgPrint("%s DriverEntry() - IoCreateDevice() Fail\n", dbg_name); return ret; } DeviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; DeviceObject->Flags |= (DO_BUFFERED_IO | DO_POWER_PAGABLE); ret = IoAttachDevice(DeviceObject, &_next_device_name, &DeviceExtension->NextLayerDeviceObject); if (ret == STATUS_SUCCESS) { DbgPrint("%s DriverEntry() - IoAttachDevice() Success\n", dbg_name); } else { DbgPrint("%s DriverEntry() - IoAttachDevice() Fail\n", dbg_name); IoDeleteDevice(DriverObject->DeviceObject); return ret; } DbgPrint("%s DriverEntry() End\n", dbg_name); return ret; } 

以下是Windbg调用堆栈。

 0: kd> k # ChildEBP RetAddr 00 82f33604 82eea083 nt!RtlpBreakWithStatusInstruction 01 82f33654 82eeab81 nt!KiBugCheckDebugBreak+0x1c 02 82f33a1c 82e4c5cb nt!KeBugCheck2+0x68b 03 82f33a1c 975e36e0 nt!KiTrap0E+0x2cf WARNING: Frame IP not in any known module. Following frames may be wrong. 04 82f33aac 82e83933 <Unloaded_Test.sys>+0x16e0 05 82f33af0 8efed7a2 nt!IopfCompleteRequest+0x128 06 82f33b14 8eea7b74 kbdclass!KeyboardClassServiceCallback+0x2fa 07 82f33b78 82e831b5 i8042prt!I8042KeyboardIsrDpc+0x18c 08 82f33bd4 82e83018 nt!KiExecuteAllDpcs+0xf9 09 82f33c20 82e82e38 nt!KiRetireDpcList+0xd5 0a 82f33c24 00000000 nt!KiIdleLoop+0x38 

callback函数似乎没有正确释放。

我如何解决这个问题?

Solutions Collecting From Web of "键盘filter驱动程序卸载蓝屏"

如果你传递指针到自己的驱动程序体( ReadCompletion在你的情况) – 驱动程序不能被卸载,直到使用这个指针( ReadCompletion调用并返回你的情况)

正如通知Harry Johnston需要使用IoSetCompletionRoutineEx – 但是这个文档很糟糕,不能解释所有的细节。 绝对强制学习windows src文件(例如WRK-v1.2 )和二进制窗口代码。 如果你寻找IoSetCompletionRoutineEx实现 – 你可以查看这个例程没有做什么来防止你的驱动程序卸载。 它只需分配小内存块,在这里保存DeviceObjectContextCompletionRoutine ,并将IopUnloadSafeCompletion设置为完成,并将指针指向分配的内存块作为上下文。

什么是IopUnloadSafeCompletion在做什么?

 NTSTATUS IopUnloadSafeCompletion( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) { PIO_UNLOAD_SAFE_COMPLETION_CONTEXT Usc = Context; NTSTATUS Status; ObReferenceObject (Usc->DeviceObject); Status = Usc->CompletionRoutine (DeviceObject, Irp, Usc->Context); ObDereferenceObject (Usc->DeviceObject); ExFreePool (Usc); return Status; } 

但是这个假定Usc->DeviceObject在调用IopUnloadSafeCompletion是有效的 。 您可以在CompletionRoutine删除/取消引用DeviceObject ,执行一些导致驱动程序卸载的任务 – 并且不会崩溃,因为您的CompletionRoutine通过添加对设备的引用来保护。 但是如果IopUnloadSafeCompletion将在您的设备已经被销毁并且驱动程序被卸载时被调用 – 任何方式都将会崩溃。

部分解决方案将在调度例程中调用ObfReferenceObject(DeviceObject) ,在完成例程中调用ObfReferenceObject(DeviceObject) 。 这对实践解决问题。 所以代码必须是下一个

 NTSTATUS OnComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/) { ObfDereferenceObject(DeviceObject);// !!! PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); if (Irp->PendingReturned) { IrpSp->Control |= SL_PENDING_RETURNED; } if (IrpSp->MajorFunction == IRP_MJ_READ && Irp->IoStatus.Status == STATUS_SUCCESS && (Irp->Flags & IRP_BUFFERED_IO)) { if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA)) { PKEYBOARD_INPUT_DATA pkid = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer; do { DbgPrint("Port%x> %x %x\n", pkid->UnitId, pkid->MakeCode, pkid->Flags); } while (pkid++, --n); } } return ContinueCompletion; } NTSTATUS KbdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) { IoCopyCurrentIrpStackLocationToNext(Irp); if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE)) { IoSkipCurrentIrpStackLocation(Irp); } else { ObfReferenceObject(DeviceObject);// !!! } return IofCallDriver( reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp); } 

调用ObfReferenceObject(DeviceObject);KbdDispatch防止卸载你的驱动程序,直到ObfDereferenceObject(DeviceObject);OnComplete里面调用。

如果我们自己调用ObfReferenceObject / ObfDereferenceObject ,你可以在本例中IoSetCompletionRoutineEx ObfDereferenceObject什么? 因为如果DriverUnload已经调用 – 所有的代码只保存在DeviceObject上的单引用上 – 所以当你调用ObfDereferenceObject(DeviceObject);OnComplete – 你的设备将被删除,驱动程序卸载ObfDereferenceObject ,最后这个例程返回到你的卸载代码。 所以IoSetCompletionRoutineEx感觉就是保护你的完成例程。

但需要明白,这是反正不是100%正确的解决方案。 从DriverUnload调用IoDetachDevice/IoDeleteDevice来连接设备不正确。 ( 这个必须从IRP_MN_REMOVE_DEVICEFAST_IO_DETACH_DEVICE回调中调用

假设下一个场景 – 有人为您的B设备所连接的设备A调用NtReadFileNtReadFile通过IoGetRelatedDeviceObject获取指向你的B设备的指针。 内部这个例程调用IoGetAttachedDevice 。 读这个:

IoGetAttachedDevice不增加设备对象的引用计数。 (因此,不需要对ObDereferenceObject进行匹配调用。) IoGetAttachedDevice的调用者必须确保在IoGetAttachedDevice执行时没有设备对象被添加到堆栈或从堆栈中移除。 不能这样做的调用者必须使用IoGetAttachedDeviceReference

假设当NtReadFile使用指针指向你的B设备时,另一个线程调用你的DriverUnload ,它删除B设备并卸载驱动程序。 设备A上存在句柄/文件对象 – 这可以防止卸载。 但是你连接的B设备不能容纳任何东西 。 因为如果NtReadFile或使用您的设备的任何其他I / O子系统例程与DriverUnload同时执行,而您调用分离/删除设备,系统可能已经在NtReadFile代码内崩溃。 你什么都做不了 调用IoDetachDevice之后只有一种方法(多少次?!)在调用IoDeleteDevice之前等待一段时间。 所幸这种情况的可能性非常低。

所以试试看 – 系统已经可以在NtReadFile崩溃了。 即使您的Dispatch调用了 – 您的DeviceObject可以被删除/无效或在调度例程期间卸载了驱动程序。 只有在你调用ObfReferenceObject(DeviceObject)之后ObfReferenceObject(DeviceObject) 。 和所有这个问题,因为你尝试分离连接的设备DriverUnload(窗口不是为此设计的)。

在你的代码中还可以注意到许多其他的错误。 说完成例程不能返回Irp->IoStatus.Status它必须返回或STATUS_MORE_PROCESSING_REQUIRED (即STATUS_MORE_PROCESSING_REQUIRED )或任何其他值 – 通常ContinueCompletion (即STATUS_CONTINUE_COMPLETION或0)也不需要硬编码"\\Device\\KeyboardClass0"但使用IoRegisterPlugPlayNotificationGUID_CLASS_KEYBOARD如果你不是wdm的驱动程序。 也为XP需要特殊处理程序的IRP_MJ_POWER ( 传递电源IRPs ),但可能是这已经不是实际的,如果XP支持不实际。

代码示例可以如下所示:

 struct DEVICE_EXTENSION { PDEVICE_OBJECT _NextDeviceObject; }; NTSTATUS KbdPower(PDEVICE_OBJECT DeviceObject, PIRP Irp) { PoStartNextPowerIrp(Irp); IoSkipCurrentIrpStackLocation(Irp); return PoCallDriver( reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp); } NTSTATUS OnComplete(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID /*Context*/) { ObfDereferenceObject(DeviceObject); PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); if (Irp->PendingReturned) { IrpSp->Control |= SL_PENDING_RETURNED; } if (IrpSp->MajorFunction == IRP_MJ_READ && Irp->IoStatus.Status == STATUS_SUCCESS && (Irp->Flags & IRP_BUFFERED_IO)) { if (ULONG n = (ULONG)Irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA)) { PKEYBOARD_INPUT_DATA pkid = (PKEYBOARD_INPUT_DATA)Irp->AssociatedIrp.SystemBuffer; do { DbgPrint("Port%x> %x %x\n", pkid->UnitId, pkid->MakeCode, pkid->Flags); } while (pkid++, --n); } } return ContinueCompletion; } NTSTATUS KbdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) { IoCopyCurrentIrpStackLocationToNext(Irp); if (0 > IoSetCompletionRoutineEx(DeviceObject, Irp, OnComplete, NULL, TRUE, TRUE, TRUE)) { IoSkipCurrentIrpStackLocation(Irp); } else { ObfReferenceObject(DeviceObject); } return IofCallDriver( reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject, Irp); } NTSTATUS KbdNotifyCallback(PDEVICE_INTERFACE_CHANGE_NOTIFICATION Notification, PDRIVER_OBJECT DriverObject) { if (::RtlCompareMemory(&Notification->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID)) { DbgPrint("++%wZ\n", Notification->SymbolicLinkName); HANDLE hFile; OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, Notification->SymbolicLinkName, OBJ_CASE_INSENSITIVE }; IO_STATUS_BLOCK iosb; if (0 <= IoCreateFile(&hFile, SYNCHRONIZE, &oa, &iosb, 0, 0, FILE_SHARE_VALID_FLAGS, FILE_OPEN, 0, 0, 0, CreateFileTypeNone, 0, IO_ATTACH_DEVICE)) { PFILE_OBJECT FileObject; NTSTATUS status = ObReferenceObjectByHandle(hFile, 0, 0, 0, (void**)&FileObject, 0); NtClose(hFile); if (0 <= status) { PDEVICE_OBJECT DeviceObject, TargetDevice = IoGetAttachedDeviceReference(FileObject->DeviceObject); ObfDereferenceObject(FileObject); if (0 <= IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), 0, TargetDevice->DeviceType, TargetDevice->Characteristics & (FILE_REMOVABLE_MEDIA|FILE_DEVICE_SECURE_OPEN), FALSE, &DeviceObject)) { DeviceObject->Flags |= TargetDevice->Flags & (DO_BUFFERED_IO|DO_DIRECT_IO|DO_SUPPORTS_TRANSACTIONS|DO_POWER_PAGABLE|DO_POWER_INRUSH); DEVICE_EXTENSION* pExt = (DEVICE_EXTENSION*)DeviceObject->DeviceExtension; if (0 > IoAttachDeviceToDeviceStackSafe(DeviceObject, TargetDevice, &pExt->_NextDeviceObject)) { IoDeleteDevice(DeviceObject); } else { DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; DbgPrint("++DeviceObject<%p> %x\n", DeviceObject, DeviceObject->Flags); } } ObfDereferenceObject(TargetDevice); } } } return STATUS_SUCCESS; } PVOID NotificationEntry; void KbdUnload(PDRIVER_OBJECT DriverObject) { DbgPrint("KbdUnload(%p)\n", DriverObject); if (NotificationEntry) IoUnregisterPlugPlayNotification(NotificationEntry); PDEVICE_OBJECT NextDevice = DriverObject->DeviceObject, DeviceObject; while (DeviceObject = NextDevice) { NextDevice = DeviceObject->NextDevice; DbgPrint("--DeviceObject<%p>\n", DeviceObject); IoDetachDevice(reinterpret_cast<DEVICE_EXTENSION*>(DeviceObject->DeviceExtension)->_NextDeviceObject); IoDeleteDevice(DeviceObject); } } NTSTATUS KbdInit(PDRIVER_OBJECT DriverObject, PUNICODE_STRING /*RegistryPath*/) { DbgPrint("KbdInit(%p)\n", DriverObject); DriverObject->DriverUnload = KbdUnload; #ifdef _WIN64 __stosq #else __stosd #endif ((PULONG_PTR)DriverObject->MajorFunction, (ULONG_PTR)KbdDispatch, RTL_NUMBER_OF(DriverObject->MajorFunction)); ULONG MajorVersion; PsGetVersion(&MajorVersion, 0, 0, 0); if (MajorVersion < 6) DriverObject->MajorFunction[IRP_MJ_POWER] = KbdPower; IoRegisterPlugPlayNotification( EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES, (void*)&GUID_CLASS_KEYBOARD, DriverObject, (PDRIVER_NOTIFICATION_CALLBACK_ROUTINE)KbdNotifyCallback, DriverObject, &NotificationEntry); return STATUS_SUCCESS; } 

这听起来像解释了你的问题:

注意只有一个可以保证它的驱动程序在其完成例程完成之前不会被卸载的操作可以使用IoSetCompletionRoutine。 否则,驱动程序必须使用IoSetCompletionRoutineEx,它可以防止驱动程序卸载,直到执行完成例程。

(从IoSetCompletionRoutine的MSDN文档 。)


PS:在功能生效的一个按键延迟是可以预料的,因为你的驱动程序没有被挂载到读取操作时,它已经在加载时正在进行。 我不确定是否有任何合理的方法来做到这一点。