写博客整理记录一下IRP相关的知识点,加深一下印象。

  所有的I/O请求都是以IRP的形式提交的。当I/O管理器为了响应某个线程调用的的I/O API的时候,就会构造一个IRP,用于在I/O系统处理这个请求的过程中代表该请求。

0x01  IRP与IO_STACK_LOCATION的结构体概览

   IRP由两部分组成,一个固定大小的头(即IRP结构体的大小)以及一个或多个I/O栈单元(即IO_STACK_LOCATION数组)

先看头部的IRP结构体,主要信息有:请求的类型和大小,请求是同步还是异步,缓冲区指针,可被改变的状态值等等。

 typedef struct _IRP {
CSHORT Type;//结构类型
USHORT Size;//irp的实际分配长度(包含后面的栈空间数组)
struct _MDL *MdlAddress;//关联的MDL链表
ULONG Flags;//irp标志
union {
//(一个irp可以分成n个子irp发给下层的驱动)
struct _IRP *MasterIrp;//当该irp是子irp时表示所属的主irp
volatile LONG IrpCount;//当该irp是主irp时,表示子irp个数
PVOID SystemBuffer;//关联的系统缓冲地址
} AssociatedIrp;
LIST_ENTRY ThreadListEntry;//用来挂入线程的未决(指Pending)irp链表
IO_STATUS_BLOCK IoStatus;//该irp的完成结果(内置的)
KPROCESSOR_MODE RequestorMode;//表示是来自用户模式还是内核模式的irp请求
BOOLEAN PendingReturned;//表示下层设备当初处理该irp时是否是Pending异步方式处理的
CHAR StackCount;//本irp的栈层数,也即本结构体后面的数组元素个数
CHAR CurrentLocation;//从上往下数,当前的栈空间位置序号(每个栈层就是一个栈空间)
BOOLEAN Cancel;//表示用户是否发出了取消该irp的命令
KIRQL CancelIrql;//取消时的irql
CCHAR ApcEnvironment;//该irp当初分配时,线程的apc状态(挂靠态/常态)
UCHAR AllocationFlags;//用于保存当初分配该irp结构时的分配标志
PIO_STATUS_BLOCK UserIosb;//可为空。表示用户自己提供的一个结构,用来将完成结果返回给用户
PKEVENT UserEvent;//可为空。表示用户自己提供的irp完成事件
union {
struct {
_ANONYMOUS_UNION union {
PIO_APC_ROUTINE UserApcRoutine;//用户提供的APC例程
PVOID IssuingProcess;
} DUMMYUNIONNAME;
PVOID UserApcContext;//APC例程的参数
} AsynchronousParameters;
LARGE_INTEGER AllocationSize;//本结构当初分配时总的分配长度(包含后面的数组)
} Overlay;
volatile PDRIVER_CANCEL CancelRoutine;//本irp关联的取消例程(取消时将执行这个函数)
PVOID UserBuffer;//关联的用户空间缓冲区地址(直接使用可能不安全)
union {
struct {
_ANONYMOUS_UNION union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;//用来挂入设备对象内置的irp队列
_ANONYMOUS_STRUCT struct {
PVOID DriverContext[];
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
PETHREAD Thread;//该irp的发起者线程
PCHAR AuxiliaryBuffer;//关联的辅助缓冲
_ANONYMOUS_STRUCT struct {
LIST_ENTRY ListEntry;
_ANONYMOUS_UNION union {
//这个字段与CurrentLocation的作用一样,只是一个表示指针,一个表示序号
struct _IO_STACK_LOCATION *CurrentStackLocation;//当前的栈空间位置
ULONG PacketType;
} DUMMYUNIONNAME;
} DUMMYSTRUCTNAME;
//irp本来是发给设备的,但是我们也可以看做是发给文件对象(各栈层可能有变动)
struct _FILE_OBJECT *OriginalFileObject;//本irp最初发往的文件对象
} Overlay;
KAPC Apc;//与本次irp相关的APC例程
PVOID CompletionKey;
} Tail;
} IRP, *PIRP;

  每个IRP结构体后面紧跟一个数组,那就是IRP的I/O设备栈,数组中每个元素的类型为IO_SATCK_LOCATION(只列出部分字段),它包含的关键字段有主功能码次功能码,以及不同请求对应的功能参数等:

 typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;//主功能码
UCHAR MinorFunction;//次功能码
UCHAR Flags;
UCHAR Control;//DeviceControl的控制码 union { struct {
ULONG Length;//读请求的长度
ULONG POINTER_ALIGNMENT Key;
LARGE_INTEGER ByteOffset;//读请求的文件偏移位置
} Read; struct {
ULONG Length; //写请求的长度
ULONG POINTER_ALIGNMENT Key;
LARGE_INTEGER ByteOffset; //写请求的文件偏移位置
} Write; struct {
ULONG OutputBufferLength;
ULONG POINTER_ALIGNMENT InputBufferLength;
ULONG POINTER_ALIGNMENT IoControlCode;
PVOID Type3InputBuffer;
} DeviceIoControl;//NtDeviceIoControlFile struct {
PVOID Argument1;
PVOID Argument2;
PVOID Argument3;
PVOID Argument4;
} Others;//没有列举的结构可以用这几个字段 ...
} Parameters;//一个复杂的联合体,对应各种irp的参数 PDEVICE_OBJECT DeviceObject;//本栈层的设备对象
PFILE_OBJECT FileObject;//关联的文件对象
PIO_COMPLETION_ROUTINE CompletionRoutine;//记录着上层设置的完成例程
PVOID Context;//完成例程的参数 } IO_STACK_LOCATION, *PIO_STACK_LOCATION;

0x02  从WRK源码看IRP的构建与下发

    1.首先看IRP的构建IoAllocateIrp

 PIRP
IopAllocateIrpPrivate(
IN CCHAR StackSize,
IN BOOLEAN ChargeQuota
) /*++ Routine Description: This routine allocates an I/O Request Packet from the system nonpaged pool.
The packet will be allocated to contain StackSize stack locations. The IRP
will also be initialized. Arguments: StackSize - Specifies the maximum number of stack locations required. ChargeQuota - Specifies whether quota should be charged against thread. Return Value: The function value is the address of the allocated/initialized IRP,
or NULL if one could not be allocated. --*/ {
USHORT allocateSize;
UCHAR fixedSize;
PIRP irp;
UCHAR lookasideAllocation;
PNPAGED_LOOKASIDE_LIST lookasideList;
UCHAR mustSucceed;
PP_NPAGED_LOOKASIDE_NUMBER number;
USHORT packetSize;
PKPRCB prcb; //
// If the size of the packet required is less than or equal to those on
// the lookaside lists, then attempt to allocate the packet from the
// lookaside lists.
//IopLargeIrpStackLocations的值是8 irp = NULL;
fixedSize = ;
mustSucceed = ;
packetSize = IoSizeOfIrp(StackSize);//((USHORT) (sizeof( IRP ) + ((StackSize) * (sizeof( IO_STACK_LOCATION )))))注意这里是Irp大小+设备栈大小的总大小
allocateSize = packetSize;
//如果栈层数小于等于8又不计较配额浪费,那么就从预置的irp容器分配
if ((StackSize <= (CCHAR)IopLargeIrpStackLocations) &&
((ChargeQuota == FALSE) || (IopLookasideIrpFloat < IopLookasideIrpLimit))) {
fixedSize = IRP_ALLOCATED_FIXED_SIZE;
number = LookasideSmallIrpList;
if (StackSize != ) {
allocateSize = IoSizeOfIrp((CCHAR)IopLargeIrpStackLocations);//对齐8个栈层大小
number = LookasideLargeIrpList;
} prcb = KeGetCurrentPrcb();
lookasideList = prcb->PPLookasideList[number].P;//尝试从该容器的P链表中分配出一个irp
lookasideList->L.TotalAllocates += ;//该链表总的分配请求计数++
irp = (PIRP)ExInterlockedPopEntrySList(&lookasideList->L.ListHead,
&lookasideList->Lock);//分配内存
if (irp == NULL) {//如果分配失败
lookasideList->L.AllocateMisses += ;//该链表的分配失败计数++
//再尝试从该容器的L链表中分配出一个irp
lookasideList = prcb->PPLookasideList[number].L;
lookasideList->L.TotalAllocates += ;
irp = (PIRP)ExInterlockedPopEntrySList(&lookasideList->L.ListHead,
&lookasideList->Lock);
}
} //
// If an IRP was not allocated from the lookaside list, then allocate
// the packet from nonpaged pool and charge quota if requested.
// lookasideAllocation = ;
if (!irp) {//如果仍然分配失败或者尚未分配
if (fixedSize != ) {
lookasideList->L.AllocateMisses += ;
} //
// There are no free packets on the lookaside list, or the packet is
// too large to be allocated from one of the lists, so it must be
// allocated from nonpaged pool. If quota is to be charged, charge it
// against the current process. Otherwise, allocate the pool normally.
// if (ChargeQuota) {
try {
irp = ExAllocatePoolWithQuotaTag(NonPagedPool, allocateSize,' prI');//直接从非分页池中分配 } except(EXCEPTION_EXECUTE_HANDLER) {
NOTHING;
} } else { //
// Attempt to allocate the pool from non-paged pool. If this
// fails, and the caller's previous mode was kernel then allocate
// the pool as must succeed.
// irp = ExAllocatePoolWithTag(NonPagedPool, allocateSize, ' prI');
if (!irp) {
mustSucceed = IRP_ALLOCATED_MUST_SUCCEED;
if (KeGetPreviousMode() == KernelMode ) {
irp = ExAllocatePoolWithTag(NonPagedPoolMustSucceed,
allocateSize,
' prI');
}
}
} if (!irp) {
return NULL;
} } else {
if (ChargeQuota != FALSE) {
lookasideAllocation = IRP_LOOKASIDE_ALLOCATION;
InterlockedIncrement( &IopLookasideIrpFloat );
}
ChargeQuota = FALSE;
} //
// Initialize the packet.
//
//分配完irp后,做一些基本字段的初始化
IopInitializeIrp(irp, packetSize, StackSize);
irp->AllocationFlags = (fixedSize | lookasideAllocation | mustSucceed);
if (ChargeQuota) {
irp->AllocationFlags |= IRP_QUOTA_CHARGED;
} return irp;
} #define IoSizeOfIrp(_StackSize) sizeof(IRP) + _StackSize * sizeof(IO_STACK_LOCATION)

    提炼一下IopAllocateIrpPrivate函数中的关键信息:

1.IopAllocateIrpPrivate通过StackSize即I/O设备栈的层数来分配内存的大小,即一次性分配的大小为:IRP结构体大小+IO_STACK_LOCATION大小*设备栈的层数

    2.由于IRP的频繁分配,所以irp栈层数小于等于8时,按8对齐(类似于内存对齐),直接从容器中分配irp整个结构(包括设备栈)。

 #define IopInitializeIrp( Irp,
PacketSize, ,//实际分配的大小
StackSize ) {//栈层数 \
RtlZeroMemory( (Irp), (PacketSize) ); \
(Irp)->Type = (CSHORT) IO_TYPE_IRP; \
(Irp)->Size = (USHORT) ((PacketSize)); \
(Irp)->StackCount = (CCHAR) ((StackSize)); \
(Irp)->CurrentLocation = (CCHAR) ((StackSize) + ); //注意这里:初始栈空间位置在栈顶的上面 \
(Irp)->ApcEnvironment = KeGetCurrentApcEnvironment(); \
InitializeListHead (&(Irp)->ThreadListEntry); \
(Irp)->Tail.Overlay.CurrentStackLocation = \
((PIO_STACK_LOCATION) ((UCHAR *) (Irp) + \
sizeof( IRP ) + \
( (StackSize) * sizeof( IO_STACK_LOCATION )))); }

    这里字段的初始化需要注意的是:CurrentLocation (记录的是一个序号,表示当前所在的设备栈,最顶层的设备栈,即最先接收到此IRP的设备对象对应的设备栈,所属的CurrentLocation 的值应当是整个设备栈中最大的,这一点涉及到之后IRP的传递问题)的初始值是((StackSize) + 1),即为栈层数+1;

    CurrentLocation字段记录了该irp在各层驱动的处理进度。该数组中,第一个元素表示设备栈的栈底,最后一个元素表示栈顶。每当将irp转发到下层设备时,irp头部中的CurrentLocation字段递减,而不是递增;

    而CurrentStackLocation 指针,指向的则是设备栈的最顶层(高地址),即第一个接收到此IRP的设备对象对应的那层设备栈。

    

    2.再看IRP的下发IoCalllDriver

NTSTATUS
FASTCALL
IopfCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
) /*++ Routine Description: This routine is invoked to pass an I/O Request Packet (IRP) to another
driver at its dispatch routine. Arguments: DeviceObject - Pointer to device object to which the IRP should be passed. Irp - Pointer to IRP for request. Return Value: Return status from driver's dispatch routine. --*/ {
PIO_STACK_LOCATION irpSp;
PDRIVER_OBJECT driverObject;
NTSTATUS status; //
// Ensure that this is really an I/O Request Packet.
// ASSERT( Irp->Type == IO_TYPE_IRP ); //
// Update the IRP stack to point to the next location.
//
Irp->CurrentLocation--;//序号--,代表当前栈层位置向下滑动,指向下层栈空间 if (Irp->CurrentLocation <= ) {
KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, , , );
} irpSp = IoGetNextIrpStackLocation( Irp );//指针下移,代表当前栈层位置向下滑动,指向下层栈空间(注意这里CurrentLocation代表的是序号,CurrentStackLocation代表的是指针)
Irp->Tail.Overlay.CurrentStackLocation = irpSp;//当前栈空间已经指向了下一层设备栈了 //
// Save a pointer to the device object for this request so that it can
// be used later in completion.
// irpSp->DeviceObject = DeviceObject;//记录好下层的设备 //
// Invoke the driver at its dispatch routine entry point.
// driverObject = DeviceObject->DriverObject; PERFINFO_DRIVER_MAJORFUNCTION_CALL(Irp, irpSp, driverObject); status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
Irp );//调用下层驱动对应irp的派遣函数 PERFINFO_DRIVER_MAJORFUNCTION_RETURN(Irp, irpSp, driverObject); return status;
} #define IoGetNextIrpStackLocation( Irp ) (\
(Irp)->Tail.Overlay.CurrentStackLocation - )

    

    可以看到,上层驱动在调用这个函数,将irp发到下层设备时,会自动在内部将当前栈空间位置向下滑动一个位置,指向下层的栈空间(递减IRP的CurrentLocation,并获得下一层的IO_STACK_LOCATION,设置到IRP的CurrentStackLocation指针中)。

PS:

 ddk提供了一个宏,用来移动irp的栈空间位置

 #define IoSetNextIrpStackLocation(Irp) \

 { \

    Irp->CurrentLocation--;\    //序号向下滑动一项

    Irp->Tail.Overlay.CurrentStackLocation--;\     //数组元素指针也向下滑动一项

 }

 下面的宏实际上获取的就是当前栈空间的位置

 #define IoGetCurrentIrpStackLocation(irp)  irp->Tail.Overlay.CurrentStackLocation

 下面的宏实际上获取的就是下层栈空间的位置

 #define IoGetNextIrpStackLocation(irp)  irp->Tail.Overlay.CurrentStackLocation – 1

IRP小结 0x01 IRP & IO_STACK_LOCATION(结合WRK理解)的更多相关文章

  1. IRP IO_STACK_LOCATION 《寒江独钓》内核学习笔记(1)

    在学习内核过滤驱动的过程中,遇到了大量的涉及IRP操作的代码,这里有必要对IRP的数据结构和与之相关的API函数做一下笔记. 1. 相关阅读资料 <深入解析 windows 操作系统(第4版,中 ...

  2. [转&精]IO_STACK_LOCATION与IRP的一点笔记

    IO_STACK_LOCATION和IRP算是驱动中两个很基础的东西,为了理解这两个东西,找了一点资料. 1. IRP可以看成是Win32窗口程序中的消息(Message),DEVICE_OBJECT ...

  3. IRP 与 派遣函数

    什么是派遣函数: 派遣函数是 WIndows 驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的.也就是说,派遣函数是用来处理驱动程序提交过来的 I ...

  4. 漫谈IRP

    I/O Request Packet(IRP) IRP概述: IRP是由I/O管理器发出的,I/O管理器是用户态与内核态之间的桥梁,当用户态进程发出I/O请求时,I/O管理器就捕获这些请求,将其转换为 ...

  5. 从IRP说起(转)

    原文链接:http://www.cnblogs.com/zhuyp1015/archive/2012/03/14/2396595.html IRP(I/O request package)是操作系统内 ...

  6. CPU与IRP的一些相关函数

    VOID KiAdjustIrpCredits ( VOID )其中 Number = KeNumberProcessors;Prcb = KiProcessorBlock[Index]; 多核情况下 ...

  7. 派遣函数IRP

    派遣函数是Windows驱动程序中的重要概念.驱动程序的主要功能是负责处理I/O请求,其中大部分I/O请求是在派遣函数中处理的. 用户模式下所有对驱动程序的I/O请求,全部由操作系统转换为一个叫做IR ...

  8. Windows驱动开发-IRP结构体

    IRP的全名是I/O Request Package,即输入输出请求包,它是Windows内核中的一种非常重要的数据结构. 上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求,操作系统将相应的 ...

  9. IRP的同步

    应用层对设备的同步与异步操作 以WriteFile为例,一般的同步操作是调用WriteFile完成后,并不会返回,应用程序会在此处暂停,一直等到函数将数据写入文件中并正常返回,而异步操作则是调用Wri ...

随机推荐

  1. 连接MySQL报错The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone.

    MySQL time zone 时区错误 使用root用户登陆执行命令: ---> show variables like '%time_zone%'; 默认值system为美国时间:如下图: ...

  2. SAP ERP SD模块中维护销售人员

    SAP ERP SD模块中维护销售人员信息并分配销售组织   分类: SAPHCM用户指南   在SAP ERP系统,销售和分销(SD)模块中需要创建销售人员(Sales Personnels)消息, ...

  3. laravel 框架的 csrf

    由于 laravel 框架自带 csrf 防护, 也就是通过中间件验证请求的 token, 所以 form 表单必须如下设置才可以正常提交, 否则会 419: <form method=&quo ...

  4. 蚂蚁风险大脑亮相ATEC城市峰会:为数字经济时代做好“安全守护”

    2019年1月4日,以“数字金融新原力(The New Force of Digital Finance)”为主题的蚂蚁金服ATEC城市峰会在上海隆重举行.大会聚焦金融数字化转型,分享新技术的发展趋势 ...

  5. svn hotcopy backup

    ================== 备份 1/2 ==================svnbackup.bat@echo off@rem Subversion Server 的安装目录set SV ...

  6. Windows 环境下进行的jenkins持续集成

    一台服务器作为代码仓库,一条服务器做持续集成代码仓库目前常见的github.gitlab.gitee持续集成常用Jenkins 服务器的配置这边都以Windows为例进行介绍 1. 安装Jenkins ...

  7. 上传图片组件封装 element ui

    // element ui 文档地址: http://element.eleme.io/#/zh-CN <template> <div> <div class=" ...

  8. Python 入门小实例笔记

    实例1:打印用户输入的姓名与手机号码知识点:编码,获取输入,变量,标准输出 #encoding=utf-8 import time #1.提示用户输入信息 name = input ("请输 ...

  9. 给Mac的Dictionary添加其他原装词典

    原文:https://www.zhihu.com/question/20428599/answer/223511099 (含下载,但需要论坛注册) 下载(百度网盘):https://blog.csdn ...

  10. poj3162

    这题卡常数了,nlogn链式前向星过了,用vector的O(n)没过. #include <iostream> #include <cstdio> #include <c ...