IRP完成APC执行函数IopCompleteRequest
2021-07-16
258
1
IRP在完成时调用IoCompleteRequest,其最终会执行一个APC调用,该调用的函数名为IopCompleteRequest。
其调用APC调用时的代码如下:
KeInitializeApc(&Irp->Tail.Apc,
&Thread->Tcb,
Irp->ApcEnvironment,
IopCompleteRequest,
NULL,
NULL,
KernelMode,
NULL);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc,
FileObject,
DataBuffer,
PriorityBoost);
IopCompleteRequest对应的函数原型为
VOID
NTAPI
IopCompleteRequest(IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext,
IN PVOID* SystemArgument1,
IN PVOID* SystemArgument2)
{
PFILE_OBJECT FileObject;
PIRP Irp;
PMDL Mdl, NextMdl;
PVOID Port = NULL, Key = NULL;
BOOLEAN SignaledCreateRequest = FALSE;
/* Get data from the APC */
FileObject = (PFILE_OBJECT)*SystemArgument1;
Irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
IOTRACE(IO_IRP_DEBUG,
"%s - Completing IRP %p for %p\n",
__FUNCTION__,
Irp,
FileObject);
/* Sanity check */
ASSERT(Irp->IoStatus.Status != (NTSTATUS)0xFFFFFFFF);
/* Check if we have a file object */
if (*SystemArgument2)
{
/* Check if we're reparsing */
if ((Irp->IoStatus.Status == STATUS_REPARSE) &&
(Irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT))
{
PREPARSE_DATA_BUFFER ReparseData;
ReparseData = (PREPARSE_DATA_BUFFER)*SystemArgument2;
ASSERT(ReparseData->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT);
ASSERT(ReparseData->ReparseDataLength < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
ASSERT(ReparseData->Reserved < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
IopDoNameTransmogrify(Irp, FileObject, ReparseData);
}
}
/* Handle Buffered case first */
if (Irp->Flags & IRP_BUFFERED_IO)
{
/* Check if we have an input buffer and if we succeeded */
if ((Irp->Flags & IRP_INPUT_OPERATION) &&
(Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) &&
!(NT_ERROR(Irp->IoStatus.Status)))
{
_SEH2_TRY
{
/* Copy the buffer back to the user */
RtlCopyMemory(Irp->UserBuffer,
Irp->AssociatedIrp.SystemBuffer,
Irp->IoStatus.Information);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Fail the IRP */
Irp->IoStatus.Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
/* Also check if we should de-allocate it */
if (Irp->Flags & IRP_DEALLOCATE_BUFFER)
{
/* Deallocate it */
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
}
/* Now we got rid of these two... */
Irp->Flags &= ~(IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER);
/* Check if there's an MDL */
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
/* Free it */
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
/* No MDLs left */
Irp->MdlAddress = NULL;
/*
* Check if either the request was completed without any errors
* (but warnings are OK!), or if it was completed with an error, but
* did return from a pending I/O Operation and is not synchronous.
*/
if (!NT_ERROR(Irp->IoStatus.Status) ||
(Irp->PendingReturned &&
!IsIrpSynchronous(Irp, FileObject)))
{
/* Get any information we need from the FO before we kill it */
if ((FileObject) && (FileObject->CompletionContext))
{
/* Save Completion Data */
Port = FileObject->CompletionContext->Port;
Key = FileObject->CompletionContext->Key;
}
/* Check for UserIos */
if (Irp->UserIosb != NULL)
{
/* Use SEH to make sure we don't write somewhere invalid */
_SEH2_TRY
{
/* Save the IOSB Information */
*Irp->UserIosb = Irp->IoStatus;
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
/* Ignore any error */
}
_SEH2_END;
}
/* Check if we have an event or a file object */
if (Irp->UserEvent)
{
/* At the very least, this is a PKEVENT, so signal it always */
KeSetEvent(Irp->UserEvent, 0, FALSE);
/* Check if we also have a File Object */
if (FileObject)
{
/* Check if this is an Asynch API */
if (!(Irp->Flags & IRP_SYNCHRONOUS_API))
{
/* Dereference the event */
ObDereferenceObject(Irp->UserEvent);
}
/*
* Now, if this is a Synch I/O File Object, then this event is
* NOT an actual Executive Event, so we won't dereference it,
* and instead, we will signal the File Object
*/
if ((FileObject->Flags & FO_SYNCHRONOUS_IO) &&
!(Irp->Flags & IRP_OB_QUERY_NAME))
{
/* Signal the file object and set the status */
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
}
/*
* This could also be a create operation, in which case we want
* to make sure there's no APC fired.
*/
if (Irp->Flags & IRP_CREATE_OPERATION)
{
/* Clear the APC Routine and remember this */
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
}
else if (FileObject)
{
/* Signal the file object and set the status */
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
/*
* This could also be a create operation, in which case we want
* to make sure there's no APC fired.
*/
if (Irp->Flags & IRP_CREATE_OPERATION)
{
/* Clear the APC Routine and remember this */
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
/* Update transfer count for everything but create operation */
if (!(Irp->Flags & IRP_CREATE_OPERATION))
{
if (Irp->Flags & IRP_WRITE_OPERATION)
{
/* Update write transfer count */
IopUpdateTransferCount(IopWriteTransfer,
(ULONG)Irp->IoStatus.Information);
}
else if (Irp->Flags & IRP_READ_OPERATION)
{
/* Update read transfer count */
IopUpdateTransferCount(IopReadTransfer,
(ULONG)Irp->IoStatus.Information);
}
else
{
/* Update other transfer count */
IopUpdateTransferCount(IopOtherTransfer,
(ULONG)Irp->IoStatus.Information);
}
}
/* Now that we've signaled the events, de-associate the IRP */
IopUnQueueIrpFromThread(Irp);
/* Now check if a User APC Routine was requested */
if (Irp->Overlay.AsynchronousParameters.UserApcRoutine)
{
/* Initialize it */
KeInitializeApc(&Irp->Tail.Apc,
KeGetCurrentThread(),
CurrentApcEnvironment,
IopFreeIrpKernelApc,
IopAbortIrpKernelApc,
(PKNORMAL_ROUTINE)Irp->
Overlay.AsynchronousParameters.UserApcRoutine,
Irp->RequestorMode,
Irp->
Overlay.AsynchronousParameters.UserApcContext);
/* Queue it */
KeInsertQueueApc(&Irp->Tail.Apc, Irp->UserIosb, NULL, 2);
}
else if ((Port) &&
(Irp->Overlay.AsynchronousParameters.UserApcContext))
{
/* We have an I/O Completion setup... create the special Overlay */
Irp->Tail.CompletionKey = Key;
Irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;
KeInsertQueue(Port, &Irp->Tail.Overlay.ListEntry);
}
else
{
/* Free the IRP since we don't need it anymore */
IoFreeIrp(Irp);
}
/* Check if we have a file object that wasn't part of a create */
if ((FileObject) && !(SignaledCreateRequest))
{
/* Dereference it, since it's not needed anymore either */
ObDereferenceObjectDeferDelete(FileObject);
}
}
else
{
/*
* Either we didn't return from the request, or we did return but this
* request was synchronous.
*/
if ((Irp->PendingReturned) && (FileObject))
{
/* So we did return with a synch operation, was it the IRP? */
if (Irp->Flags & IRP_SYNCHRONOUS_API)
{
/* Yes, this IRP was synchronous, so return the I/O Status */
*Irp->UserIosb = Irp->IoStatus;
/* Now check if the user gave an event */
if (Irp->UserEvent)
{
/* Signal it */
KeSetEvent(Irp->UserEvent, 0, FALSE);
}
else
{
/* No event was given, so signal the FO instead */
KeSetEvent(&FileObject->Event, 0, FALSE);
}
}
else
{
/*
* It's not the IRP that was synchronous, it was the FO
* that was opened this way. Signal its event.
*/
FileObject->FinalStatus = Irp->IoStatus.Status;
KeSetEvent(&FileObject->Event, 0, FALSE);
}
}
/* Now that we got here, we do this for incomplete I/Os as well */
if ((FileObject) && !(Irp->Flags & IRP_CREATE_OPERATION))
{
/* Dereference the File Object unless this was a create */
ObDereferenceObjectDeferDelete(FileObject);
}
/*
* Check if this was an Executive Event (remember that we know this
* by checking if the IRP is synchronous)
*/
if ((Irp->UserEvent) &&
(FileObject) &&
!(Irp->Flags & IRP_SYNCHRONOUS_API))
{
/* This isn't a PKEVENT, so dereference it */
ObDereferenceObject(Irp->UserEvent);
}
/* Now that we've signaled the events, de-associate the IRP */
IopUnQueueIrpFromThread(Irp);
/* Free the IRP as well */
IoFreeIrp(Irp);
}
}
该函数IopCompleteRequest首先判断是否含有文件对象,如果有则执行相应的操作。
后续判断该IRP是否是IRP_BUFFERED_IO操作,即缓存操作,这里由于已经在当前进程中,可以直接进行内存拷贝,将内核中的数据拷贝到应用层缓冲区中。对应的代码为:
RtlCopyMemory(Irp->UserBuffer,
Irp->AssociatedIrp.SystemBuffer,
Irp->IoStatus.Information);
所以我们在IRP操作时可以根据Irp->UserBuffer知道期用户层的内存地址。
后续紧跟着的是判断该IRP的IO操作是否为异步操作,如果是,获取用户层绑定的完成端口操作。
获取用户层的等待事件,并置事件有效。
if (Irp->UserEvent)
{
/* Signal it */
KeSetEvent(Irp->UserEvent, 0, FALSE);
}
else
{
/* No event was given, so signal the FO instead */
KeSetEvent(&FileObject->Event, 0, FALSE);
}
后面有一个关于文件读写操作的操作,见代码:
if (!(Irp->Flags & IRP_CREATE_OPERATION))
{
if (Irp->Flags & IRP_WRITE_OPERATION)
{
/* Update write transfer count */
IopUpdateTransferCount(IopWriteTransfer,
(ULONG)Irp->IoStatus.Information);
}
else if (Irp->Flags & IRP_READ_OPERATION)
{
/* Update read transfer count */
IopUpdateTransferCount(IopReadTransfer,
(ULONG)Irp->IoStatus.Information);
}
else
{
/* Update other transfer count */
IopUpdateTransferCount(IopOtherTransfer,
(ULONG)Irp->IoStatus.Information);
}
}
我们可以分析一下IopUpdateTransferCount的代码,这里统计了当前进程和整个系统数据的读写数量。
VOID
IopUpdateTransferCount(IN IOP_TRANSFER_TYPE Type, IN ULONG TransferCount)
{
PLARGE_INTEGER CountToChange;
PLARGE_INTEGER TransferToChange;
/* Make sure I/O operations are being counted */
if (IoCountOperations)
{
if (Type == IopReadTransfer)
{
/* Increase read count */
CountToChange = &PsGetCurrentProcess()->ReadTransferCount;
TransferToChange = &IoReadTransferCount;
}
else if (Type == IopWriteTransfer)
{
/* Increase write count */
CountToChange = &PsGetCurrentProcess()->WriteTransferCount;
TransferToChange = &IoWriteTransferCount;
}
else
{
/* Increase other count */
CountToChange = &PsGetCurrentProcess()->OtherTransferCount;
TransferToChange = &IoOtherTransferCount;
}
/* Increase the process-wide count */
ExInterlockedAddLargeStatistic(CountToChange, TransferCount);
/* Increase global count */
ExInterlockedAddLargeStatistic(TransferToChange, TransferCount);
}
}
后续又是根据该操作是否绑有相应的用户层APC操作而执行。
当然,以上都是异步的,对于同步的和异步的类似,只是代码相对比较简单而已。
回过头来看,对于IRP的完成,如果仅是设备层,相对比较简单,但如果是文件或内存分页倒换的操作,就会变地比较复杂很多。该IRP完成中,后续大部分都是处理关于文件操作的处理。