首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >键盘过滤驱动程序卸载BSOD

键盘过滤驱动程序卸载BSOD
EN

Stack Overflow用户
提问于 2017-03-17 02:49:22
回答 2查看 992关注 0票数 1

我已经开发了一个键盘过滤驱动程序,它将键盘按钮'1‘(在Q按钮上方)更改为'2’。

这个司机工作得很好。

但是,在执行卸载后,按下键盘按钮会导致BSOD。

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

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

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

此外,在加载驱动程序之后,如果在开始时按下键盘按钮'1‘,它不会更改为'2’。

然后一切都变的很好。

我不知道这与什么有关。

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

请回答我的问题。

下面是源代码。

代码语言:javascript
运行
复制
#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调用堆栈。

代码语言:javascript
运行
复制
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函数似乎没有得到正确的释放。

我该如何解决这个问题?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-03-17 12:36:13

如果您将指针传递给自己的驱动程序主体(在您的情况下是ReadCompletion)--在使用此指针之前,不能卸载驱动程序(ReadCompletion调用并返回您的情况)。

正如所通知的,哈里约翰斯顿需要使用IoSetCompletionRoutineEx -但这方面的文档是不好的,并没有解释所有的细节。绝对强制学习windows src文件(例如WRK-v1.2)和二进制windows代码。如果您寻找IoSetCompletionRoutineEx的实现--您可以查看这个例程,nothing,,以防止您的驱动程序卸载。它只需分配小内存块,将DeviceObjectContextCompletionRoutine保存在这里,并将IopUnloadSafeCompletion设置为完成,并将指向已分配内存块的指针设置为上下文。

IopUnloadSafeCompletion在做什么?

代码语言:javascript
运行
复制
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)。这在实践上解决了问题。所以代码必须是下一个

代码语言:javascript
运行
复制
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_DEVICEFAST_IO_DETACH_DEVICE回调中调用)

假设下一种情况--有人为您的NtReadFile设备所附加的设备A调用BNtReadFile通过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驱动程序,则使用IoRegisterPlugPlayNotificationGUID_CLASS_KEYBOARD。对于xp,IRP_MJ_POWER也需要特殊的处理程序( 通过功率IRPs ),但如果xp支持不是实际的话,这可能已经不是实际的了。

代码示例可以如下所示:

代码语言:javascript
运行
复制
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;
}
票数 2
EN

Stack Overflow用户

发布于 2017-03-17 03:48:41

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

Note只有能够保证在完成例程完成之前不会卸载的驱动程序才能使用IoSetCompletionRoutine。否则,驱动程序必须使用IoSetCompletionRoutineEx,这将阻止驱动程序在其完成例程执行之前卸载。

(来自用于IoSetCompletionRoutine的MSDN文档

PS:功能中的一击延迟是预期的,因为您的驱动程序没有连接到加载时已经在进行的读取操作。我不确定是否有什么合理的方法。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/42848364

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档