STATUS_MORE_PROCESSING_REQUIRED 和 IoMarkIrpPending

主要常识描述完成例程是如何导致蓝屏的问题,发现关键在于错误的设置导致 IopCompleteRequest 被多次调用

1.先看NtReadFile这个函数

NtReadFile(){
 ...
 IopSynchronousServiceTail(); // 内部调用此函数
}
=
IopSynchronousServiceTail()
{
      status = IoCallDriver( DeviceObject, Irp );
      if (DeferredIoCompletion) {
        if (status != STATUS_PENDING) {             // 注意这里, 如果返回的不是STATUS_PENDING,会调用IopCompleteRequest
            IopCompleteRequest( xxxx );
        }
      }

      if (SynchronousIo) {
        if (status == STATUS_PENDING) {
            status = KeWaitForSingleObject( &FileObject->Event,
        ...
      }
}

2. 简单看看 IoCompleteRequest

IoCompleteRequest()
{
      for (stackPointer = IoGetCurrentIrpStackLocation( Irp ),
            Irp->CurrentLocation++,
            Irp->Tail.Overlay.CurrentStackLocation++;                  // 提前++了
            Irp->CurrentLocation <= (CCHAR) (Irp->StackCount + 1);
            stackPointer++,
            Irp->CurrentLocation++,
            Irp->Tail.Overlay.CurrentStackLocation++) 
      {
            // 这个for循环的意思是从底层往高层遍历irpstack, 检查stackPointer->Control的值,,,【 IoMarkIrpPending()函数就是设置这个值 】
            // Irp->Tail.Overlay.CurrentStackLocation 要比 stackPointer 高一层,因为他提前++了
            // 
            Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;      // 取得当前层的Control值,赋值给PendingReturned

            if ( 有完成例程 ){
                  ZeroIrpStackLocation( stackPointer );
                  status = 完成例程();                                                // 关键就看完成例程怎么处理这个 Irp->PendingReturned
                                                                                     // 主要注意是否会调用IoMarkIrpPending()传递

                  if (status == STATUS_MORE_PROCESSING_REQUIRED) {                   // 返回STATUS_MORE_PROCESSING_REQUIRED,return 收工了
                        return;       
                  }
            } else {
                  if (Irp->PendingReturned && Irp->CurrentLocation <= Irp->StackCount) { // 如果PendingReturned被当前层赋值SL_PENDING_RETURNED
                      IoMarkIrpPending( Irp );                                           // 把这个值传递给高层
                  }
                  ZeroIrpStackLocation( stackPointer );
              }
      }

      ...
      // 上面一套for循环完成, 下面就要判断这个Irp->PendingReturned 了,以决定是return还是KeInitializeApc( IopCompleteRequest ); 
      // 
      if (Irp->Flags & IRP_DEFER_IO_COMPLETION && !Irp->PendingReturned) {  // Irp->PendingReturned 没被设置,那就返回
            return
       }

      KeInitializeApc( IopCompleteRequest );                                // Irp->PendingReturned 被设置了,就要调用IopCompleteRequest
}

*** 由此看到,完成例程里面怎么写,会决定 Irp->PendingReturned,进而决定调不调用 IopCompleteRequest;

3. 简单看看 IopCompleteRequest

IopCompleteRequest()
{
      // 1. 拷贝数据等所有需要完成的事项
      RtlCopyMemory( irp->UserBuffer,irp->AssociatedIrp.SystemBuffer,irp->IoStatus.Information );
      ...
      
      // 2. 是否有APC
      if (irp->Overlay.AsynchronousParameters.UserApcRoutine) {
            KeInitializeApc( &irp->Tail.Apc,
                             &thread->Tcb,
                             CurrentApcEnvironment,
                             IopUserCompletion,                        // 重点 1
                             (PKRUNDOWN_ROUTINE) IopUserRundown,       // 3
                             (PKNORMAL_ROUTINE) irp->Overlay.AsynchronousParameters.UserApcRoutine,
                             irp->RequestorMode,
                             irp->Overlay.AsynchronousParameters.UserApcContext );
      }
      else{ 
            IoFreeIrp( irp );                                          // 重点 2
      }
}
// 重点 1 和 3 里面都是调用IoFreeIrp, 估计一个是apc正常完成,一个是不能正常完成,反正都会FreeIrp
IopUserCompletion() / IopUserRundown()
{ 
      IoFreeIrp( CONTAINING_RECORD( Apc, IRP, Tail.Apc ) );       
}
// 重点 2
IoFreeIrp( irp )
{
      if (Irp->Type != IO_TYPE_IRP) {
        KeBugCheckEx( MULTIPLE_IRP_COMPLETE_REQUESTS, (ULONG_PTR) Irp, __LINE__, 0, 0 );
      }
      ...
}

*** 由此看到 IopCompleteRequest 只能被调用一次,否则会多次IoFreeIrp,导致蓝屏

4. 找个例子——1

代码从这里copy的 https://blog.csdn.net/wsgxiaomianao/article/details/35279727 
截取其中的 DispatchRead 和 IoReadCompletion
NTSTATUS
IoReadCompletion()
{
 
/*      // 代码里说了,这里要注释,不然会蓝屏,下面就看看如果不注释为啥会蓝屏,是否导致了IopCompleteRequest多次调用
        // 下面我用 AAA BBB CCC标注了重要地方
        //
	if (Irp->PendingReturned)
	{
		//传播pending位
		IoMarkIrpPending(Irp);//完成例程返回STATUS_MORE_PROCESSING_REQUIRED时,不可以设置IoMarkIrpPending
		//PKEVENT PKevent = (PKEVENT)Context;
		//KeSetEvent(PKevent,IO_NO_INCREMENT,FALSE);
	}
*/
	PKEVENT PKevent = (PKEVENT)Context;
	KeSetEvent(PKevent,IO_NO_INCREMENT,FALSE);
	
	return STATUS_MORE_PROCESSING_REQUIRED;//STATUS_SUCCESS;          // AAA , 底层的IoCompleteRequest会return收工,不会调用IopCompleteRequest
}
 
NTSTATUS DispatchRead(PDEVICE_OBJECT pDevObj,PIRP pIrp)
{
      ...
	//设置IRP的完成例程
	IoSetCompletionRoutine(pIrp,IoReadCompletion,&kEvent,TRUE,TRUE,TRUE); // 完成例程 IoReadCompletion
       
	status = IoCallDriver(pDevExt->pTargetedDevObj,pIrp);//转发IRP给下层驱动
 
	if (status == STATUS_PENDING)
	{
		KdPrint(("DrvierB:DispatchRead::IoCallDriver STATUS_PENDING...."));
		KeWaitForSingleObject(&kEvent,Executive,KernelMode,FALSE,NULL);            // 
		KdPrint(("DrvierB:IoReadCompletion::finish STATUS_PENDING...."));
 
		status = pIrp->IoStatus.Status;		            // 
	}
 
	//此时需要再次调用来完成IRP,但是本程序再此处会有发送多个IRP完成的蓝屏?????为什么?
	//原因://完成例程返回STATUS_MORE_PROCESSING_REQUIRED时,不可以设置IoMarkIrpPending

	IoCompleteRequest(pIrp,IO_NO_INCREMENT);                    //  CCC ,如果完成例程里使用IoMarkIrpPending的话,那这里的IoCompleteRequest会
                                                                    // 在 Irp->PendingReturned = stackPointer->Control & SL_PENDING_RETURNED;  时
                                                                    // 发现赋值了,然后KeInitializeApc( IopCompleteRequest ); 又调用了IopCompleteRequest
	
	return status;                     //  BBB, 返回STATUS_SUCCESS,要么是status = IoCallDriver,要么是status = pIrp->IoStatus.Status;
                                           // 那上层NtReadFile会调用IopCompleteRequest
}

*** 由于DispatchRead()返回的必然是STATUS_SUCCESS, 导致上面NtReadFile必然会IopCompleteRequest,所以下面必然不能让 Irp->PendingReturned 被赋值

5. 找个例子——2

这也是一个正确的例子
IoReadCompletion()
{
      if(Irp->PendingReturned){
          IoMarkIrpPending(Irp);
      }
      return Irp->IoStatus.Status; 
}
DispatchRead()
{
        IoSetCompletionRoutine( IoReadCompletion );
        return IoCallDriver(devExt->pLowerDeviceObject,Irp);     // 直接把下层的返回值,传递到上层,(上个例子是必然返回SUCCESS)
}
1. 首先本层没使用IoCompleteRequest,自然也不会调用IopCompleteRequest
2. 如果下层返回SUCCESS, 那下层没有IoMarkIrpPending, 完成例程里if不成立,则本层也不会IoMarkIrpPending,所以下层IoCompleteRequest不会调用IopCompleteRequest
    而返回的SUCCESS导致上层调用IopCompleteRequest, 收尾工作由上层完成
3. 如果下层返回PENDING,  那下层IoMarkIrpPending了,完成例程里面if成立,导致Irp->PendingReturned传递,所以下层会调用到IopCompleteRequest
    返回PENDING,那上层不会调用IopCompleteRequest, 收尾工作由底层完成 

***  也可以不管下层返回啥,你都直接返回PENDING, 然后修改成本层或者下层去调用IopCompleteRequest,那又是另外一种写法了

总之只能调用一次 IopCompleteRequest

posted @ 2020-12-04 12:34  一条小鳄鱼  阅读(725)  评论(0)    收藏  举报