我开发了一个键盘过滤器驱动程序,可以将键盘按钮“1”(在Q按钮上方)更改为“2”。
此驱动程序正常。
但是,执行卸载后,按键盘按钮会导致BSOD。
如果在未按下键盘按钮的情况下装载和卸载驱动程序,则会正常卸载。
当我使用Windbg检查它时,我的驱动程序的ReadCompletion()函数即使在卸载后也会被调用。
我不知道为什么会发生这种情况,即使我已经调用了IoDetachDevice()和IoDeleteDevice()。
此外,在加载驱动程序后,如果您在开头按下键盘按钮“1”,则不会更改为“2”。
然后它变化很好。
我不知道这与什么有关。
我希望你能找到解决这个问题的方法。
请回答我的问题。
以下是源代码。
#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 Call Stack。
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功能似乎没有正确发布。
如何解决这个问题?
答案 0 :(得分:2)
这听起来像是在解释你的问题:
注意只有在完成例程完成之前不能卸载的驱动程序才能使用IoSetCompletionRoutine。否则,驱动程序必须使用IoSetCompletionRoutineEx,这会阻止驱动程序在完成例程执行之前卸载。
(来自MSDN documentation for IoSetCompletionRoutine。)
PS:功能生效中的一键击延迟是预期的,因为驱动程序没有挂钩到加载时已经在进行的读取操作。我不确定是否有任何合理的方法可以做到这一点。
答案 1 :(得分:2)
如果您将指针传递给自己的驱动程序主体(在您的情况下为ReadCompletion
) - 在使用此指针(ReadCompletion
调用并返回您的情况)之前,不得卸载驱动程序
通知 Harry Johnston 需要使用IoSetCompletionRoutineEx
- 但是这方面的文档很糟糕,并没有解释所有细节。绝对必要的研究windows src文件(例如WRK-v1.2
)和二进制windows代码。如果您查找IoSetCompletionRoutineEx
的实现 - 您可以查看此例程没有做以防止驱动程序卸载。它只是分配小内存块,在此保存DeviceObject
,Context
和CompletionRoutine
并将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
时有效。您可以在DeviceObject
内删除/取消引用CompletionRoutine
,执行导致您的驱动程序卸载的任务 - 并且不会崩溃,因为您的CompletionRoutine
通过添加对您的设备的引用来保护。但是如果你的设备已经被销毁并且驱动程序被卸载,将会调用IopUnloadSafeCompletion
- 任何方式都会崩溃。
部分解决方案将在您的调度例程中调用ObfReferenceObject(DeviceObject)
,在完成例程中调用ObfDereferenceObject(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
询问什么?因为如果DriverUnload
已经被调用 - 您的所有代码都只保留在DeviceObject
上的单个引用 - 那么当您从ObfDereferenceObject(DeviceObject);
调用OnComplete
时 - 您的设备将被删除并且驱动程序在内部卸载ObfDereferenceObject
最后,此例程返回到已卸载的代码。所以IoSetCompletionRoutineEx
的感觉就是保护你的完成程序。
但需要明白,这无论如何都不是100%正确的解决方案。从IoDetachDevice/IoDeleteDevice
致电DriverUnload
附加设备不正确。 (必须从IRP_MN_REMOVE_DEVICE
或FAST_IO_DETACH_DEVICE
回调调用)
假设下一个场景 - 有人为NtReadFile
设备所附加的设备A
致电B
。 NtReadFile
通过B
获取指向IoGetRelatedDeviceObject
设备的指针。在这个例程中调用IoGetAttachedDevice
。读这个:
IoGetAttachedDevice 不会增加引用计数 设备对象。 (因此没有与 ObDereferenceObject 的匹配调用 IoGetAttachedDevice 的来电者必须确保没有设备 对象被添加到堆栈或从堆栈中删除 IoGetAttachedDevice 正在执行。无法执行此操作的呼叫者必须使用 IoGetAttachedDeviceReference 代替。
假设NtReadFile
使用指向B
设备的指针,另一个称为DriverUnload
的线程删除B
设备并卸载驱动程序。句柄/文件对象存在于设备A
上 - 这样可以保留它并防止卸载。但是您附加的B
设备没有任何内容。因此,如果NtReadFile
或使用您的设备的任何其他I / O子系统例程与调用分离/删除设备的DriverUnload
同时执行 - 系统可能已在NtReadFile
代码内崩溃。你无能为力。调用IoDetachDevice
后,只有一种方式可以在调用IoDeleteDevice
之前等待一些(多少?!)时间。幸运的是,这种情况的可能性很低。
因此请尝试理解 - 系统可能已在NtReadFile
崩溃。即使您的Dispatch调用了 - 您的DeviceObject
可以在调度例程中被删除/无效或驱动程序已卸载。只有在你致电ObfReferenceObject(DeviceObject)
之后,一切都变好了。以及所有这个问题,因为您尝试在DriverUnload中分离连接的设备(不是为此设计的窗口)。
还可以注意到代码中的许多其他错误。说完成例程不能返回Irp->IoStatus.Status
它必须返回或StopCompletion
(即STATUS_MORE_PROCESSING_REQUIRED
)或任何其他值 - 通常ContinueCompletion
(即STATUS_CONTINUE_COMPLETION
或0)也如果您不是驱动程序,则无需硬编码"\\Device\\KeyboardClass0"
,而是将IoRegisterPlugPlayNotification
与GUID_CLASS_KEYBOARD
一起使用。同样对于xp需要IRP_MJ_POWER
(Passing 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;
}