我已经开发了一个键盘过滤驱动程序,它将键盘按钮'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调用堆栈。
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函数似乎没有得到正确的释放。
我该如何解决这个问题?
发布于 2017-03-17 12:36:13
如果您将指针传递给自己的驱动程序主体(在您的情况下是ReadCompletion
)--在使用此指针之前,不能卸载驱动程序(ReadCompletion
调用并返回您的情况)。
正如所通知的,哈里约翰斯顿需要使用IoSetCompletionRoutineEx
-但这方面的文档是不好的,并没有解释所有的细节。绝对强制学习windows src文件(例如WRK-v1.2
)和二进制windows代码。如果您寻找IoSetCompletionRoutineEx
的实现--您可以查看这个例程,nothing,,以防止您的驱动程序卸载。它只需分配小内存块,将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
time时是有效的。您可以在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);
in KbdDispatch
,防止卸载驱动程序,直到ObfDereferenceObject(DeviceObject);
在OnComplete
中调用为止。
如果我们自己调用IoSetCompletionRoutineEx
/ ObfDereferenceObject
,那么在这种情况下,您可以要求什么呢?因为如果DriverUnload
已经调用--您的所有代码只保存在DeviceObject
上的单个引用--那么当您从OnComplete
调用ObfDereferenceObject(DeviceObject);
时,您的设备将被删除,驱动程序将在ObfDereferenceObject
中卸载,最后这个例程返回给您的卸载的代码。因此,IoSetCompletionRoutineEx
的意义是保护您的完成例程。
但需要理解的是,这毕竟不是百分之百正确的解决方案。如果附加的设备与IoDetachDevice/IoDeleteDevice
不正确,请调用DriverUnload
。(这必须从IRP_MN_REMOVE_DEVICE
或FAST_IO_DETACH_DEVICE
回调中调用)
假设下一种情况--有人为您的NtReadFile
设备所附加的设备A
调用B
。NtReadFile
通过IoGetRelatedDeviceObject
获取指向您的B
设备的指针。在内部,这个例行调用是IoGetAttachedDevice
。请阅读以下内容:
IoGetAttachedDevice不会增加设备对象的引用计数。(因此不需要对ObDereferenceObject进行匹配的调用。)IoGetAttachedDevice的调用方必须确保在执行IoGetAttachedDevice时,没有向堆栈添加或从堆栈中移除设备对象。不能这样做的调用者必须使用IoGetAttachedDeviceReference。
假设当NtReadFile
使用指向B
设备的指针时,另一个名为DriverUnload
的线程删除B
设备并卸载驱动程序。存在于设备A
上的句柄/文件对象--保持它并防止卸载。但是您所附的B
设备不支持任何。因此,如果NtReadFile
或使用您的设备的任何其他I/O子系统例程与DriverUnload
并行执行,则调用detach/delete设备-系统可能会在NtReadFile
代码中崩溃。你对此无能为力。只有一种方式后调用IoDetachDevice
的一些(多少?!)在打电话给IoDeleteDevice
之前等待时间。幸运的是,这种情况的可能性很低。
所以,尝试理解-系统可能已经崩溃在NtReadFile
中了。即使您的调度被调用-您的DeviceObject
可以被删除/无效,或者驱动程序在调度过程中卸载。只有在你打电话给ObfReferenceObject(DeviceObject)
之后,一切才会好起来。所有这些问题都是因为您尝试在DriverUnload中分离附加设备(windows不是为此设计的)。
还可以注意到代码中的许多其他错误。假设完成例程不能返回Irp->IoStatus.Status
,它必须返回或StopCompletion
(即STATUS_MORE_PROCESSING_REQUIRED
)或任何其他值-通常的ContinueCompletion
(即STATUS_CONTINUE_COMPLETION
或0)也不需要硬编码"\\Device\\KeyboardClass0"
,但如果不使用STATUS_CONTINUE_COMPLETION
驱动程序,则使用IoRegisterPlugPlayNotification
与GUID_CLASS_KEYBOARD
。对于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;
}
发布于 2017-03-17 03:48:41
这听起来像是解释了你的问题:
Note只有能够保证在完成例程完成之前不会卸载的驱动程序才能使用IoSetCompletionRoutine。否则,驱动程序必须使用IoSetCompletionRoutineEx,这将阻止驱动程序在其完成例程执行之前卸载。
(来自用于IoSetCompletionRoutine的MSDN文档
PS:功能中的一击延迟是预期的,因为您的驱动程序没有连接到加载时已经在进行的读取操作。我不确定是否有什么合理的方法。
https://stackoverflow.com/questions/42848364
复制相似问题