在 RemoveEntryList 中出现错误检查 0x139 的 BSOD
BSOD with Bug Check 0x139 in RemoveEntryList
我们开发了基于(WinDDK 6)原生串口驱动的WDM串口驱动
但是我们的客户有一个应用程序在使用我们的驱动程序时触发了 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!
.
.
.
}
我们发现 SerialKillAllReadsOrWrites
中 RemoveEntryList
的第一次调用和 SerialCancelQueued
中的第二次调用将删除相同的条目。
而且我们测试过,如果我们标记第一个RemoveEntryList
,它就通过了,不再是BSOD。
但为什么原生 COM 即使调用 RemoveEntryList
两次以删除相同的条目也不会触发 BSOD?
有人可以帮我理解为什么吗?谢谢。
我发现 WDK8.1 中的 RemoveEntryList
与 WDK6 中的不同。
如果我用 WDK6 构建驱动程序,当我们调用 RemoveEntryList
两次时 Windows 不会触发 BSOD。
但是,如果驱动程序是由 WDK8.1 构建的,那么当我们调用 RemoveEntryList
两次时,Windows 将触发 BSOD。
所以,如果我们想通过 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);
}
我们开发了基于(WinDDK 6)原生串口驱动的WDM串口驱动
但是我们的客户有一个应用程序在使用我们的驱动程序时触发了 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!
.
.
.
}
我们发现 SerialKillAllReadsOrWrites
中 RemoveEntryList
的第一次调用和 SerialCancelQueued
中的第二次调用将删除相同的条目。
而且我们测试过,如果我们标记第一个RemoveEntryList
,它就通过了,不再是BSOD。
但为什么原生 COM 即使调用 RemoveEntryList
两次以删除相同的条目也不会触发 BSOD?
有人可以帮我理解为什么吗?谢谢。
我发现 WDK8.1 中的 RemoveEntryList
与 WDK6 中的不同。
如果我用 WDK6 构建驱动程序,当我们调用 RemoveEntryList
两次时 Windows 不会触发 BSOD。
但是,如果驱动程序是由 WDK8.1 构建的,那么当我们调用 RemoveEntryList
两次时,Windows 将触发 BSOD。
所以,如果我们想通过 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);
}