在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
第一呼叫SerialKillAllReadsOrWrites
和SerialCancelQueued
第二个电话会删除原来的项目。
而且我们测试过,如果我们标记第一个RemoveEntryList
,它就通过了,不再BSOD。
但为什么本机COM不会触发BSOD即使拨打RemoveEntryList
两次以删除相同的条目?
有人可以帮我理解为什么吗?谢谢。
我发现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);
}
你不需要调用'RemoveEntryList'从'SerialKillAllReadsOrWrites'因为你从'CancelRoutine'调用它。当然你可以在'RemoveEntryList'后调用'InitializeListHead' - 在这种情况下它可以安全地调用它两次,但更正确的时候不会从'SerialKillAllReadsOrWrites'调用它(当系统本身取消你的irp时 - 它也不会)从你的'QueueToClean'中删除你的irp,你真的在循环中调用'IoAcquireCancelSpinLock(&cancelIrql)'? – RbMm
是的,我真的叫'IoAcquireCancelSpinLock(&cancelIrql)'。我认为这是可以的,因为'SerialCancelQueued'末尾有'IoReleaseCancelSpinLock'。 – CCT