IoCopyCurrentIrpStackLocationToNext和IoSkipCurrentIrpStackLocation操作的IO_STACK_LOCATION有什么区别
在Windows驱动中,传递IPR一般有两种操作:
一种是调用IoSkipCurrentIrpStackLocation,表示跳过本层驱动的操作,直接转发至下层:
IoSkipCurrentIrpStackLocation(Irp);
return IoCallDriver(FDODeviceExtension->NextDeviceObject, Irp);
另一种是我们对IPR完成时,对完成情况感兴趣,所以需要本层的驱动栈保留,使用IoSetCompletionRoutine设置完成例程(可选择)。这个保留的驱动栈一般用于当IRP完成时回调函数的调用。
如我们需要知道下层驱动已经正确的完成了IPR。
KEVENT event;
KeInitializeEvent(&event, NotificationEvent, FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,
(PIO_COMPLETION_ROUTINE)IrpCompletionRoutine,
(PVOID) & event,
TRUE,
TRUE,
TRUE);
status = IoCallDriver(DeviceObject, Irp);
if (status == STATUS_PENDING)
{
KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
NULL);
status = Irp->IoStatus.Status;
}
上面的说法都是比较笼统,对于明白的人是真的明白,但对于不明白的人是看了很多编都不清楚。这里们进行详细的解释.
我们知道在创建一个设备时,特别是PDO时,是需要设置一个叫做栈大小的参数。
PDODeviceObject->StackSize = DeviceObject->StackSize + 1;
这个参数一般应用于需要IPR调用时申请IRP的IoAllocateIrp函数的使用
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
if (!Irp)
{
/* No memory */
return STATUS_INSUFFICIENT_RESOURCES;
}
IoAllocateIr会调用IoInitializeIrp进行IRP的分初始化。初始化的方法为:
VOID NTAPI IoInitializeIrp ( IN PIRP Irp,
IN USHORT PacketSize,
IN CCHAR StackSize
)
{
/* Clear it */
IOTRACE(IO_IRP_DEBUG,
"%s - Initializing IRP %p\n",
__FUNCTION__,
Irp);
RtlZeroMemory(Irp, PacketSize);
/* Set the Header and other data */
Irp->Type = IO_TYPE_IRP;
Irp->Size = PacketSize;
Irp->StackCount = StackSize;
Irp->CurrentLocation = StackSize + 1;
Irp->ApcEnvironment = KeGetCurrentThread()->ApcStateIndex;
Irp->Tail.Overlay.CurrentStackLocation = (PIO_STACK_LOCATION)(Irp + 1) + StackSize;
/* Initialize the Thread List */
InitializeListHead(&Irp->ThreadListEntry);
}
这里看到最顶层的设备对应的栈顶,和栈的概念类似。
这样,创建的IRP会有StackSize个IO_STACK_LOCATION,该设备(DEVICE_OBJECT)和一层的设备都有一个私有的IO_STACK_LOCATION。但这种对应关系不是惟一确定的,是可以通过相关的IRP STATCK_LOCATION函数操作跳过该设备对应的STACK_LOCATION的,其中一个典型的函数就是IoSkipCurrentIrpStackLocation
FORCEINLINE
VOID
IoSkipCurrentIrpStackLocation (
_Inout_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount);
Irp->CurrentLocation++;
Irp->Tail.Overlay.CurrentStackLocation++;
}
使用此函数将放弃本层STATCK_LOCATION,这样先将STATCK_LOCATION+1,然后立即调用IoCallDriver让其减1。这样一加一减实现利当前驱动栈层给下层设备应用。
以下为ReactOS提供的IoCallDriver源代码
#define IoCallDriver(a,b) \
IofCallDriver(a,b)
NTSTATUS FASTCALL IofCallDriver (
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
PDRIVER_OBJECT DriverObject;
PIO_STACK_LOCATION StackPtr;
/* Make sure this is a valid IRP */
ASSERT(Irp->Type == IO_TYPE_IRP);
/* Get the Driver Object */
DriverObject = DeviceObject->DriverObject;
/* Decrease the current location and check if */
Irp->CurrentLocation--;
if (Irp->CurrentLocation <= 0)
{
/* This IRP ran out of stack, bugcheck */
KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0);
}
/* Now update the stack location */
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->Tail.Overlay.CurrentStackLocation = StackPtr;
/* Get the Device Object */
StackPtr->DeviceObject = DeviceObject;
/* Call it */
return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,
Irp);
}
而我们一般在FDO中,是需要调用当前的STACK_LOCATION的,如以下代码
PFDO_DEVICE_EXTENSION FDODeviceExtension;
IoStack = IoGetCurrentIrpStackLocation(Irp);
其中IoGetCurrentIrpStackLocation的源代码如下:
FORCEINLINE
__drv_aliasesMem
PIO_STACK_LOCATION
IoGetCurrentIrpStackLocation(
_In_ PIRP Irp
)
{
NT_ASSERT(Irp->CurrentLocation <= Irp->StackCount + 1);
return Irp->Tail.Overlay.CurrentStackLocation;
}
回过头来我们再说IoCopyCurrentIrpStackLocationToNext函数。
FORCEINLINE
VOID
IoCopyCurrentIrpStackLocationToNext(
_Inout_ PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PIO_STACK_LOCATION nextIrpSp;
irpSp = IoGetCurrentIrpStackLocation(Irp);
nextIrpSp = IoGetNextIrpStackLocation(Irp);
RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
nextIrpSp->Control = 0;
}
这个函数是将当前STACK_LOCATION中除完成例程CompletionRoutine以上的所有参数复制到下层STACK_LOCATION中,这样就实现了STACK_LOCATION的值传递。
由此可见,要进行IPR的下发调用IoCallDriver,是需要IoSkipCurrentIrpStackLocation和IoSkipCurrentIrpStackLocation操作的,只是一个不再保留该设备层的驱动栈,一个保留该层的设备栈(主要是用于完成例程)
最后,我们再看一下IoCompleteRequest的实现。
#define IoCompleteRequest(a,b) \
IofCompleteRequest(a,b)
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);
}
}
}
该函数IoCompleteRequest是实现的的是IRP完成,所以这里需要对所有的设备栈进行全部回溯,并调用其完成例程。这里 的代码是从函数地第50行开始的。
这里我们注意99行的代码:
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;
当完成返回STATUS_MORE_PROCESSING_REQUIRED时,将不再调用后续的完成例程,并直接返回。有兴趣的同学可详见STATUS_MORE_PROCESSING_REQUIRED和STATUS_CONTINUE_COMPLETION的区别。