首页 > 其他分享 >win驱动中使用IRP注意项及原因分析(后续补充)

win驱动中使用IRP注意项及原因分析(后续补充)

时间:2022-11-07 19:35:39浏览次数:46  
标签:项及 管理器 IRP win StackPtr Irp 调用 Fdo


MS上有很多关于驱动中使用IRP的守则(跟黄历中的宜忌差不多了),比如:

Q1.“Drivers must not attempt to reuse IRPs issued by the I/O manager. In particular, drivers should not attempt to reuse IRPs created by the IoMakeAssociatedIrp, IoBuildSynchronousFsdRequest, IoBuildAsynchronousFsdRequest, or IoBuildDeviceIoControlRequest routines.”--摘自:https://msdn.microsoft.com/zh-cn/library/ff561107

A1.大意为,在驱动程序中不应该重用IO管理器发来的IRP。这里概述一下IRP在设备栈中传递的背景:应用层调用ReadFile(NtReadFile)时,IO管理器会创建(调用IoAllocateIRP)并初始化(调用IoInitializeIRP)IRP,其中IoAllocateIrp会带一个StackSize的参数,用于创建和这个IRP关联的设备堆栈。一切完成后,IO管理器将IRP发送给下层设备对象,也就是驱动程序中。毋庸置疑,IRP在IO管理器的当前堆栈深度比IRP在驱动的当前堆栈深度至少深一个StackLocation。

    回到IoReuseIrp上,查看他的调用流程可以发现:IoReuseIrp将调用IoInitializeIrp和RtlZeroMemory(Irp,Irp->Size)清空当前IRP。我们来考虑这样一个情景:IO管理器发送IRP前,在自己的StackLocation中设置了一个CompleteRoution,CompleteRoution中调用KeSetEvent唤醒线程(NtReadFile等待在某个eventObj上)。可是设备驱动中调用RtlZeroMemory(Irp,Irp->Size)后,除了清空了自己及设备栈中位于底层设备的StackLocation(此时这些StackLocation中设置的CompleteRountion已经执行完),还顺带着把上层IO管理器的StackLocation一起清空了。不用说,IO管理器设置的CompleteRountion得不到执行,NtReadFile也将因此陷于无限等待中。

    但是,如果是设备驱动自己创建的IRP,调用IoReuseIrp时,并不会过度清除StackLocation(只清除自己及以下的StackLocation),这应该很好的解释了MS的注释项。


Q2.调用IoCompleteRequest后IRP沿设备堆栈返回。如果遇到某层StackLocation的CompleteRountine返回STATUS_MORE_PROCESSING_REQUIRED,停止返回,本层堆栈重新获得IRP的控制。并且该IRP从完成状态变为未完成状态,需要再次调用IoCompleteRequest.--摘自windows驱动开发技术详解P334

A2.这段话信息点有2个:1.返回STATUS_MORE_PROCESSING_REQUIRED时,本层堆栈重新获得IRP的控制权;2.需要再次调用IoCompleteRequest;下面一一分析

1).需要结合IoCompleteRequest的代码说明:

VOID
FASTCALL
IofCompleteRequest(IN PIRP Irp,
IN CCHAR PriorityBoost)
{
....
LastStackPtr = (PIO_STACK_LOCATION)(Irp + 1);
...
do
{
/* Set Pending Returned */
//准备异步返回时,会掉用IoMarkIrpPending使StackPtr->Control在该位上置位
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
...
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
{
...
}

/* Call the completion routine */
Status = StackPtr->CompletionRoutine(DeviceObject,
Irp,
StackPtr->Context); <=1 调用CompleteRoutine

/* Don't touch the Packet in this case, since it might be gone! */
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return; <=2 退出IoCompleteRequest
}

假设当前设备栈中由上而下以此堆着filterDo->Fdo->Pdo。当Fdo以同步方式IoCallDriver(Pdo)调用Pdo的某个派遣函数,那么直到Pdo显式调用IoCompleteRequest返回,Fdo才会从IoCallDriver返回。退出IoCompleteRequest!while循环有两大条件:1).遍历完所有设备堆栈,从而自然退出;2):遇到break;/return;语句强行退出while,二者条件满足一个就行。CompleteRoutine返回STATUS_MORE_PROCESSING_REQUIRED正好满足条件2),因此上层Fdo从IoCallDriver中全身而退,继续执行Fdo驱动中IoCallDriver之后的代码。这时StackPtr也被拨回指向Fdo所对应的设备栈,因此Fdo可以再次访问该Irp。

2).虽然Fdo获得了Irp,又随意的再Fdo中直接释放了Irp,而没有执行filterDo对应的设备栈中的CompleteRoutine,那这个完成函数随着Irp的消失,一起得不到执行。如果filterDo正好是异步方式调用IoCallDriver/KeWaitForSingleObject,等待CompleteRoutine中执行KeSetEvent,那很不幸,这个线程就失去了唤醒的机会。因此,某一层重新获得Irp后,还需要再次调用IoCompleteRequest,让它上层的设备栈中设置的CompleteRoutine得到运行的机会。(不能假设上层设备也是通过同步调用进入当前堆栈吧?)


Q3."每当低级驱动完成IRP后,将IRP的堆栈向上回卷时,底层IO堆栈中Control域的SL_PENDING_RETURNED位必须被传播到上一层。如果本层没有设置完成例程,那么这种传播是自动的,即不用程序员指定。否则,这种传播需要程序员自己实现,形如:

NTSTATUS CompletionRoutine(..)
{
if(Irp->PendingReturned)
{
IoMarkIrpPending(Irp);
}
}


"--摘自Windows驱动开发技术详解P332

A3.完成例程中遇到了Irp->PendingReturned,为什么一定要传播SL_PENDING_RETURNED到上层?这个问题在刚学驱动时困扰我很久,现在结合源码来回答。

切入点是驱动完成Irp后,和完成Irp相关的十有八九是函数IofCompleteRequest,这里择要罗列相关代码:

VOID
FASTCALL
IofCompleteRequest(IN PIRP Irp,
IN CCHAR PriorityBoost)
{
...
PIO_STACK_LOCATION StackPtr = IoGetCurrentIrpStackLocation(Irp); <---A
IoSkipCurrentIrpStackLocation(Irp); <---B
...
do
{
/* Set Pending Returned */
//准备异步返回时,会掉用IoMarkIrpPending使StackPtr->Control在该位上置位
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED; <---B1
if() <---E
{
Status = StackPtr->CompletionRoutine();
}
else
{
/* Otherwise, check if this is a completed IRP */
if ((Irp->CurrentLocation <= Irp->StackCount) && <---B2
(Irp->PendingReturned))
{
/* Mark it as pending */
IoMarkIrpPending(Irp);
}

/* Clear the stack location */
IopClearStackLocation(StackPtr);
}
IoSkipCurrentIrpStackLocation(Irp); <---C
StackPtr++; <---D
}

假设现在设备栈由高往低依次为filterDO->Fdo->Pdo。A处,StackPtr指向Pdo的设备栈;B处,Irp->CurrentLocation已经指向Fdo的设备栈,使得StackPtr和Irp->CurrentLocation形成譬如链表一样的前后关系;如果把Irp->PendingReturned看做临时变量,那么到了B1处,临时变量会装载来自下层Pdo设备StackPtr[Pdo]->Control的值,而到了B2处会以临时变量的值更新到当前Fdo设备StackPtr[Pdo]->Control,说白了就是取链表后一节点的值更新前一节点的值;CD两处依次改变Irp->CurrentLocation和StackPtr指针的值,使得他两维持链表前后关系。如此往复,就像波浪一样将Irp->Currentlocation从堆栈底部传递到顶部。重点来了,如果遇到了完成函数,IofCompleteRequest进入E处的if块,当从if块退出时,错失了"取链表后一节点的值更新前一节点的值"的机会,将波浪的连续性给截流了。因此需要在当前断流处(更新前一节点)StackPtr[Fdo]重新设置Control |= SL_PENDING_RETURNED,填补这个空缺。

Q4.IRP标志为完成后,不能再使用该Irp,即下面的代码是错误的:

DispatchWrite()
{
pIrp->IoStatus.Status = STATUS_SUCCESS;
pIrp->IoStatus.information = 0;
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
return pIrp->IoStatus.Status; <---error
}

A4.这个规则的原因有2个

首先因为IoCompleteRequest会调用IoFreeIrp释放pIrp的内存。

VOID
NTAPI
IoFreeIrp(IN PIRP Irp)
{
ExFreePool(Irp);
}


其次,需要知道IRP的内存来自非分页内存池。用户模式下为了减少App与内存管理器的交互,进程自身维护一个堆管理器进行小额内存分配;堆管理器则大规模的向内存管理器申请释放内存。到了内核模式下,堆管理器变身成为内存池。(张银奎 软件调试 23章)。由于非分页内存的特性---不会立即释放,而是在池管理器中缓存着。因此,就会发生调用IoCompleteRequest释放Irp后在使用Irp并没有立即报错(访问无效内存),而是等到以后某个点上(向内存管理器归还内存)莫名其妙的报错。

标签:项及,管理器,IRP,win,StackPtr,Irp,调用,Fdo
From: https://blog.51cto.com/u_13927568/5831060

相关文章

  • 注册IRP_MJ_SHUTDOWN事件 基于ReactOS0303
      系统关闭时,会向注册SHUTDOWN事件的设备驱动发送IRP_MJ_SHUTDOWN事件。NTSTATUSSTDCALLNtShutdownSystem(INSHUTDOWN_ACTIONAction){if(Action>ShutdownPowe......
  • windows防火墙支持FTP服务的设置方法
    2003server用于提供web和ftp服务,通过互联网用flashfxp实现远程上传网页。如果关闭防火墙,ftp上传下载正常,但启用windows防火墙后就不行,即使把web、ftp等服务列为例外也不行......
  • win11 首次跳过账号登陆
    跳过登录首次进入,进入界面后,同时按下ctrl+shift+F3等待重启进入之后关闭系统准备工具会自启千万不要点击确定按钮需要自己创建本地账户并赋予admin分组权限,Administr......
  • windows设备停用启动杂记
      公司(OEM厂商)的电脑升级到win10RS2RTM后,发现有部分MSinbox驱动在电源事件后会出现黄标的现象(ErrorCode=43--QueryRemove失败)甚至driverlost。MS虽然承认是他们......
  • 编写windbg调试器扩展 入门篇1
      我博客的左侧专栏曾经转过windows下编写调试器的一系列文章,这类文章是从零打造调试器,而这篇文章是介绍如何为windbg编写调试器扩展命令。0.前言  windbg的命令......
  • 在Windows 2000 Server系统光盘中集成SP4更新程序
    Windows2000Server操作系统以其稳定的性能依然服役于很多作为服务器的计算机中,目前其最新版本已经集成了SP4累积更新程序。本教程将介绍在未集成任何ServicePack的Wind......
  • OpenStack的Windows镜像制作
    基础环境安装yumgroupinstallVirtualization"VirtualizationClient"yuminstalllibvirt启动服务systemctlenablelibvirtdsystemctlstartlibvirtdsystemctl......
  • 使用 vscode 编译+运行 typescropt Mac win同理
    一、.d.ts文件最好在src/typings目录下,可在tsconfig.json文件配置二、vs监听文件变化,自动编译ts文件tsconfig.json{"compilerOptions":{"target":"es5"......
  • ACWING 第 76 场周赛 ABC
    https://www.acwing.com/activity/content/2589/这场周赛也很简单,除了C我在赛场上写的时候有点小bug,赛时没改出来,哎,真废啊4713.反转字符串#include<bits/stdc++.h>u......
  • Windows常用快捷键
    Windows常用快捷键01-Ctrl系列快捷键Ctrl+C:复制Ctrl+V:粘贴Ctrl+A:全选Ctrl+X:剪切Ctrl+Z:撤销Ctrl+S:保存Ctrl+N:快速创建同级界面Ctrl+W:关闭当前界面......