IRP的完成IoCompleteRequest
每当一个IRP在下层设备层完成时,是需要调用IoCompleteRequest来实现IRP的完成,这个完成其实是实现对执行的IRP的善后操作,这个操作其实是一个宏,真实函数数是IofCompleteRequest。
#define IoCompleteRequest IofCompleteRequest
Windows内核对设备的IO操作在Windows驱动层中表现为IRP,Windows IO的操作分为同步操作和异步操作,所以表现在IRP上为IRP同步等待完成和挂起返回,具体何时完成由根据实际情况选择时机完成。这个时机的载体一般就是DPC,异步线程等。
我们知道,当设备在创建一个IRP时,其中一个参数就是设备的stackSize,使用这个参数,系统创建的IRP将为其设备对应的各栈分配一个IO_STACK_LOCATION,下层设备可用也可不用这个STACK_LOCATION。如果要用,是需要调用函数IoCopyCurrentIrpStackLocationToNext,如果不需要可使用IoSkipCurrentIrpStackLocation函数跳当前设备栈。当IRP通过一层层的IoCallDriver到达底层设备时,这个底层设备是需要最终实现对该IRP的完成。
当IRP完成时,是需要调用IoCompleteRequest函数执行善后操作的,同时我们也知道,当执行了该操作后,中间的各层设备由于设置了相应的完成例程,有一次机会再次获取当前IRP的所有权进行再次操作。
Windows并未提供该函数的代码,不过ReactOS提供了相应的代码:
VOID FASTCALL IofCompleteRequest ( IN PIRP Irp,
IN CCHAR PriorityBoost
)
{
PIO_STACK_LOCATION StackPtr, LastStackPtr;
PDEVICE_OBJECT DeviceObject;
PFILE_OBJECT FileObject;
PETHREAD Thread;
NTSTATUS Status;
PMDL Mdl, NextMdl;
ULONG MasterCount;
PIRP MasterIrp;
ULONG Flags;
NTSTATUS ErrorCode = STATUS_SUCCESS;
PREPARSE_DATA_BUFFER DataBuffer = NULL;
IOTRACE(IO_IRP_DEBUG,
"%s - Completing IRP %p\n",
__FUNCTION__,
Irp);
/* Make sure this IRP isn't getting completed twice or is invalid */
if ((Irp->CurrentLocation) > (Irp->StackCount + 1))
{
/* Bugcheck */
KeBugCheckEx(MULTIPLE_IRP_COMPLETE_REQUESTS, (ULONG_PTR)Irp, 0, 0, 0);
}
/* Some sanity checks */
ASSERT(Irp->Type == IO_TYPE_IRP);
ASSERT(!Irp->CancelRoutine);
ASSERT(Irp->IoStatus.Status != STATUS_PENDING);
ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);
/* Get the last stack */
LastStackPtr = (PIO_STACK_LOCATION)(Irp + 1);
if (LastStackPtr->Control & SL_ERROR_RETURNED)
{
/* Get the error code */
ErrorCode = PtrToUlong(LastStackPtr->Parameters.Others.Argument4);
}
/*
* Start the loop with the current stack and point the IRP to the next stack
* and then keep incrementing the stack as we loop through. The IRP should
* always point to the next stack location w.r.t the one currently being
* analyzed, so completion routine code will see the appropriate value.
* Because of this, we must loop until the current stack location is +1 of
* the stack count, because when StackPtr is at the end, CurrentLocation is +1.
*/
for (StackPtr = IoGetCurrentIrpStackLocation(Irp),
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++;
Irp->CurrentLocation <= (Irp->StackCount + 1);
StackPtr++,
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++)
{
/* Set Pending Returned */
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
/* Check if we failed */
if (!NT_SUCCESS(Irp->IoStatus.Status))
{
/* Check if it was changed by a completion routine */
if (Irp->IoStatus.Status != ErrorCode)
{
/* Update the error for the current stack */
ErrorCode = Irp->IoStatus.Status;
StackPtr->Control |= SL_ERROR_RETURNED;
LastStackPtr->Parameters.Others.Argument4 = UlongToPtr(ErrorCode);
LastStackPtr->Control |= SL_ERROR_RETURNED;
}
}
/* Check if there is a Completion Routine to Call */
if ((NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_SUCCESS)) ||
(!NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_ERROR)) ||
(Irp->Cancel &&
(StackPtr->Control & SL_INVOKE_ON_CANCEL)))
{
/* Clear the stack location */
IopClearStackLocation(StackPtr);
/* Check for highest-level device completion routines */
if (Irp->CurrentLocation == (Irp->StackCount + 1))
{
/* Clear the DO, since the current stack location is invalid */
DeviceObject = NULL;
}
else
{
/* Otherwise, return the real one */
DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
}
/* Call the completion routine */
Status = StackPtr->CompletionRoutine(DeviceObject,
Irp,
StackPtr->Context);
/* Don't touch the Packet in this case, since it might be gone! */
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
}
else
{
/* Otherwise, check if this is a completed IRP */
if ((Irp->CurrentLocation <= Irp->StackCount) &&
(Irp->PendingReturned))
{
/* Mark it as pending */
IoMarkIrpPending(Irp);
}
/* Clear the stack location */
IopClearStackLocation(StackPtr);
}
}
/* Check if the IRP is an associated IRP */
if (Irp->Flags & IRP_ASSOCIATED_IRP)
{
/* Get the master IRP and count */
MasterIrp = Irp->AssociatedIrp.MasterIrp;
MasterCount = InterlockedDecrement(&MasterIrp->AssociatedIrp.IrpCount);
/* Free the MDLs */
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
/* Go to the next one */
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
/* Free the IRP itself */
IoFreeIrp(Irp);
/* Complete the Master IRP */
if (!MasterCount) IofCompleteRequest(MasterIrp, PriorityBoost);
return;
}
/* Check whether we have to reparse */
if (Irp->IoStatus.Status == STATUS_REPARSE)
{
if (Irp->IoStatus.Information > IO_REMOUNT)
{
/* If that's a reparse tag we understand, save the buffer from deletion */
if (Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)
{
ASSERT(Irp->Tail.Overlay.AuxiliaryBuffer != NULL);
DataBuffer = (PREPARSE_DATA_BUFFER)Irp->Tail.Overlay.AuxiliaryBuffer;
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
}
else
{
Irp->IoStatus.Status = STATUS_IO_REPARSE_TAG_NOT_HANDLED;
}
}
}
/* Check if we have an auxiliary buffer */
if (Irp->Tail.Overlay.AuxiliaryBuffer)
{
/* Free it */
ExFreePool(Irp->Tail.Overlay.AuxiliaryBuffer);
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
}
/* Check if this is a Paging I/O or Close Operation */
if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))
{
/* Handle a Close Operation or Sync Paging I/O */
if (Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_CLOSE_OPERATION))
{
/* Set the I/O Status and Signal the Event */
Flags = Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_PAGING_IO);
*Irp->UserIosb = Irp->IoStatus;
KeSetEvent(Irp->UserEvent, PriorityBoost, FALSE);
/* Free the IRP for a Paging I/O Only, Close is handled by us */
if (Flags)
{
/* If we were using the reserve IRP, then call the appropriate
* free function (to make the IRP available again)
*/
if (Irp == IopReserveIrpAllocator.ReserveIrp)
{
IopFreeReserveIrp(PriorityBoost);
}
/* Otherwise, free for real! */
else
{
IoFreeIrp(Irp);
}
}
}
else
{
#if 0
/* Page 166 */
KeInitializeApc(&Irp->Tail.Apc
&Irp->Tail.Overlay.Thread->Tcb,
Irp->ApcEnvironment,
IopCompletePageWrite,
NULL,
NULL,
KernelMode,
NULL);
KeInsertQueueApc(&Irp->Tail.Apc,
NULL,
NULL,
PriorityBoost);
#else
/* Not implemented yet. */
UNIMPLEMENTED_DBGBREAK("Not supported!\n");
#endif
}
/* Get out of here */
return;
}
/* Unlock MDL Pages, page 167. */
Mdl = Irp->MdlAddress;
while (Mdl)
{
MmUnlockPages(Mdl);
Mdl = Mdl->Next;
}
/* Check if we should exit because of a Deferred I/O (page 168) */
if ((Irp->Flags & IRP_DEFER_IO_COMPLETION) && !(Irp->PendingReturned))
{
/* Restore the saved reparse buffer for the caller */
if (Irp->IoStatus.Status == STATUS_REPARSE &&
Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT)
{
Irp->Tail.Overlay.AuxiliaryBuffer = (PCHAR)DataBuffer;
}
/*
* Return without queuing the completion APC, since the caller will
* take care of doing its own optimized completion at PASSIVE_LEVEL.
*/
return;
}
/* Get the thread and file object */
Thread = Irp->Tail.Overlay.Thread;
FileObject = Irp->Tail.Overlay.OriginalFileObject;
/* Make sure the IRP isn't canceled */
if (!Irp->Cancel)
{
/* Initialize the APC */
KeInitializeApc(&Irp->Tail.Apc,
&Thread->Tcb,
Irp->ApcEnvironment,
IopCompleteRequest,
NULL,
NULL,
KernelMode,
NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
FileObject,
DataBuffer,
PriorityBoost);
}
else
{
/* The IRP just got canceled... does a thread still own it? */
if (Thread)
{
/* Yes! There is still hope! Initialize the APC */
KeInitializeApc(&Irp->Tail.Apc,
&Thread->Tcb,
Irp->ApcEnvironment,
IopCompleteRequest,
NULL,
NULL,
KernelMode,
NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
FileObject,
DataBuffer,
PriorityBoost);
}
else
{
/* Nothing left for us to do, kill it */
ASSERT(Irp->Cancel);
IopCleanupIrp(Irp, FileObject);
}
}
}
IofCompleteRequest该函数在最前面首先进行必有的参数校验,注意,这里有一次系统中断蓝屏的错误报告:MULTIPLE_IRP_COMPLETE_REQUESTS,从这里来看,该蓝屏操作只有一个有效的参数即该IRP,其余参数都为0。
从这个错误的条件来看,多次完成只是其中之一,也有可能IRP数据被破坏,只是这个检测确实有点简单。
具体关于MULTIPLE_IRP_COMPLETE_REQUESTS蓝屏有分析可参见:
- http://www.pnpon.com/article/detail-164.html
- https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/bug-check-0x44--multiple-irp-complete-requests
参数校验完成后,然后从当前IO_STACK_LOCATION开始,向上回溯。如果该IRP因为错误或失败执行IoCompleteRequest则是分别标识各IO_STACK_LOCATION栈信息。同时根据各栈的IRP完成例程函数执行其对应的IRP完成例程。
在执行格栈的IRP完成例程之彰,得先获取其对应栈的设备对象,当是最顶层也即是最底层时,将设备对象指针设为NULL.
当然如果没有设备完成例程,直接清除当前栈的数据信息,随时准备返回。这种情况对应于IoCopyCurrentIrpStackLocationToNext但并不设置其完成例程即并不执行 IoSetCompletionRoutine函数。
对于IRP上层IRP挂起返回的操作,在完成时需要判断是否已经PendingReturned,如果是则继续IoMarkIrpPending,表示继续续等待返回。
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
Pending返回的IRP都需要执行IoMarkIrpPending函数,表示当前IRP挂起。表示系统不用等待IRP执行返回,这是一个异步操作。
VOID
IoMarkIrpPending(
_Inout_ PIRP Irp)
{
IoGetCurrentIrpStackLocation((Irp))->Control |= SL_PENDING_RETURNED;
}
我们接下来看,后面的是根据IRP是否一个关联IRP。如果是关联IRP,还需要对与之相关联的主IRP进行完成操作。
关联IRP一种内核操作,表示当执行IRP执行在设备驱动某层时,该IRP转化为了对多个下层的操作请求。所以这个IRP变成了多个IRP,这时其中的一个IRP变成了主IRP,其余IRP变成了关联IRP,所以在此需对此IRP关联的主IRP进行完成操作。
后续判断返回状态是否执行REPARSE操作,这种操作应该发生在文件路径重解析之内。
该IRP是否有与之对应的辅助内存,如果存在,这里应该进行清理。
后续判断是否内存页倒换的操作。这里我们忽略,在文件系统中我们再分析。
是否分配MDL,进行MDL内存解锁。
最近根据IRP是否是取消操作取完,执行不同的APC完成函数调用,此操作需要该IRP对应的线程之中。所以这里创建对应的APC操作,并将对应的APC函数挂入其对应的线程APC队列中,等待调度执行。
IRP的APC完成例程,见IopCompleteRequest函数解析。