在RemoveEntryList中使用BUG检查0x139的BSOD

问题描述:

我们开发了基于(WinDDK 6)原生串行COM端口驱动程序的WDM串口驱动程序。在RemoveEntryList中使用BUG检查0x139的BSOD

但是我们的客户在使用我们的驱动程序时有一个触发BSOD的应用程序。

此应用程序在打开程序的按钮时连续调用IRP_MJ_READ,并且在关闭程序而不关闭按钮时发生蓝屏死机。

我们使用WinDBG进行了调试,发现根本原因是RemoveEntryList,并且Bug检查代码告诉我们我们已经拨打了RemoveEntryList两次。见Bug check 0x139

经过分析,我们无法看到驱动程序和WinDDK之间的代码差异,但本机COM1在运行此应用程序时不会触发BSOD。

,相关代码为如下:

当正在关闭程序,系统调用SerialKillAllReadsOrWrites杀死在ReadQueue未决的IRP。

VOID 
SerialKillAllReadsOrWrites(
    IN PDEVICE_OBJECT DeviceObject, 
    IN PLIST_ENTRY QueueToClean, 
    IN PIRP *CurrentOpIrp 
    ) 
{ 

    KIRQL cancelIrql; 
    PDRIVER_CANCEL cancelRoutine; 

    IoAcquireCancelSpinLock(&cancelIrql); 

    // 
    // Clean the list from back to front. 
    // 
    while (!IsListEmpty(QueueToClean)) { 

     PIRP currentLastIrp = CONTAINING_RECORD(
            QueueToClean->Blink, 
            IRP, 
            Tail.Overlay.ListEntry 
           ); 

     RemoveEntryList(QueueToClean->Blink); 

     cancelRoutine = currentLastIrp->CancelRoutine; 
     currentLastIrp->CancelIrql = cancelIrql; 
     currentLastIrp->CancelRoutine = NULL; 
     currentLastIrp->Cancel = TRUE; 

     cancelRoutine(
      DeviceObject, 
      currentLastIrp 
      );    // <- call SerialCancelQueued() 

     IoAcquireCancelSpinLock(&cancelIrql); 

    } 

    . 
    . 
    . 
} 
VOID 
SerialCancelQueued(
    PDEVICE_OBJECT DeviceObject, 
    PIRP Irp 
    ) 
{ 

    PSERIAL_DEVICE_EXTENSION extension = DeviceObject->DeviceExtension; 
    PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp); 

    SERIAL_LOCKED_PAGED_CODE(); 

    Irp->IoStatus.Status = STATUS_CANCELLED; 
    Irp->IoStatus.Information = 0; 
    RemoveEntryList(&Irp->Tail.Overlay.ListEntry); // <- BSOD happened here! 
    . 
    . 
    . 
} 

我们发现RemoveEntryList第一呼叫SerialKillAllReadsOrWritesSerialCancelQueued第二个电话会删除原来的项目。

而且我们测试过,如果我们标记第一个RemoveEntryList,它就通过了,不再BSOD。

但为什么本机COM不会触发BSOD即使拨打RemoveEntryList两次以删除相同的条目?

有人可以帮我理解为什么吗?谢谢。

+0

你不需要调用'RemoveEntryList'从'SerialKillAllReadsOrWrites'因为你从'CancelRoutine'调用它。当然你可以在'RemoveEntryList'后调用'InitializeListHead' - 在这种情况下它可以安全地调用它两次,但更正确的时候不会从'SerialKillAllReadsOrWrites'调用它(当系统本身取消你的irp时 - 它也不会)从你的'QueueToClean'中删除你的irp,你真的在​​循环中调用'IoAcquireCancelSpinLock(&cancelIrql)'? – RbMm

+0

是的,我真的叫'IoAcquireCancelSpinLock(&cancelIrql)'。我认为这是可以的,因为'SerialCancelQueued'末尾有'IoReleaseCancelSpinLock'。 – CCT

我发现WDK8.1中的RemoveEntryList与WDK6中的不同。 如果我通过WDK6构建驱动程序,当我们拨打RemoveEntryList两次时,Windows不会触发蓝屏。 但是,如果驱动程序是由WDK8.1构建的,则当我们拨打RemoveEntryList两次时,Windows将触发蓝屏。 因此,如果我们想通过WDK8.1构建驱动程序,可能应该修改SerialKillAllReadsOrWrites中的原始代码以避免调用RemoveEntryList两次。

// WDK6: 
FORCEINLINE 
BOOLEAN 
RemoveEntryList(
    _In_ PLIST_ENTRY Entry 
    ) 

{ 

    PLIST_ENTRY Blink; 
    PLIST_ENTRY Flink; 

    Flink = Entry->Flink; 
    Blink = Entry->Blink; 
    Blink->Flink = Flink; 
    Flink->Blink = Blink; 
    return (BOOLEAN)(Flink == Blink); 
} 

// WDK 8.1 
FORCEINLINE 
BOOLEAN 
RemoveEntryList(
    _In_ PLIST_ENTRY Entry 
    ) 

{ 

    PLIST_ENTRY PrevEntry; 
    PLIST_ENTRY NextEntry; 

    NextEntry = Entry->Flink; 
    PrevEntry = Entry->Blink; 
    if ((NextEntry->Blink != Entry) || (PrevEntry->Flink != Entry)) { 
     FatalListEntryError((PVOID)PrevEntry, 
          (PVOID)Entry, 
          (PVOID)NextEntry); 
    } 

    PrevEntry->Flink = NextEntry; 
    NextEntry->Blink = PrevEntry; 
    return (BOOLEAN)(PrevEntry == NextEntry); 
}