主要常识描述完成例程是如何导致蓝屏的问题,发现关键在于错误的设置导致 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