IRP小结 0x01 IRP & IO_STACK_LOCATION(结合WRK理解)
写博客整理记录一下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结构体,主要信息有:请求的类型和大小,请求是同步还是异步,缓冲区指针,可被改变的状态值等等。
1 typedef struct _IRP { 2 CSHORT Type;//结构类型 3 USHORT Size;//irp的实际分配长度(包含后面的栈空间数组) 4 struct _MDL *MdlAddress;//关联的MDL链表 5 ULONG Flags;//irp标志 6 union { 7 //(一个irp可以分成n个子irp发给下层的驱动) 8 struct _IRP *MasterIrp;//当该irp是子irp时表示所属的主irp 9 volatile LONG IrpCount;//当该irp是主irp时,表示子irp个数 10 PVOID SystemBuffer;//关联的系统缓冲地址 11 } AssociatedIrp; 12 LIST_ENTRY ThreadListEntry;//用来挂入线程的未决(指Pending)irp链表 13 IO_STATUS_BLOCK IoStatus;//该irp的完成结果(内置的) 14 KPROCESSOR_MODE RequestorMode;//表示是来自用户模式还是内核模式的irp请求 15 BOOLEAN PendingReturned;//表示下层设备当初处理该irp时是否是Pending异步方式处理的 16 CHAR StackCount;//本irp的栈层数,也即本结构体后面的数组元素个数 17 CHAR CurrentLocation;//从上往下数,当前的栈空间位置序号(每个栈层就是一个栈空间) 18 BOOLEAN Cancel;//表示用户是否发出了取消该irp的命令 19 KIRQL CancelIrql;//取消时的irql 20 CCHAR ApcEnvironment;//该irp当初分配时,线程的apc状态(挂靠态/常态) 21 UCHAR AllocationFlags;//用于保存当初分配该irp结构时的分配标志 22 PIO_STATUS_BLOCK UserIosb;//可为空。表示用户自己提供的一个结构,用来将完成结果返回给用户 23 PKEVENT UserEvent;//可为空。表示用户自己提供的irp完成事件 24 union { 25 struct { 26 _ANONYMOUS_UNION union { 27 PIO_APC_ROUTINE UserApcRoutine;//用户提供的APC例程 28 PVOID IssuingProcess; 29 } DUMMYUNIONNAME; 30 PVOID UserApcContext;//APC例程的参数 31 } AsynchronousParameters; 32 LARGE_INTEGER AllocationSize;//本结构当初分配时总的分配长度(包含后面的数组) 33 } Overlay; 34 volatile PDRIVER_CANCEL CancelRoutine;//本irp关联的取消例程(取消时将执行这个函数) 35 PVOID UserBuffer;//关联的用户空间缓冲区地址(直接使用可能不安全) 36 union { 37 struct { 38 _ANONYMOUS_UNION union { 39 KDEVICE_QUEUE_ENTRY DeviceQueueEntry;//用来挂入设备对象内置的irp队列 40 _ANONYMOUS_STRUCT struct { 41 PVOID DriverContext[4]; 42 } DUMMYSTRUCTNAME; 43 } DUMMYUNIONNAME; 44 PETHREAD Thread;//该irp的发起者线程 45 PCHAR AuxiliaryBuffer;//关联的辅助缓冲 46 _ANONYMOUS_STRUCT struct { 47 LIST_ENTRY ListEntry; 48 _ANONYMOUS_UNION union { 49 //这个字段与CurrentLocation的作用一样,只是一个表示指针,一个表示序号 50 struct _IO_STACK_LOCATION *CurrentStackLocation;//当前的栈空间位置 51 ULONG PacketType; 52 } DUMMYUNIONNAME; 53 } DUMMYSTRUCTNAME; 54 //irp本来是发给设备的,但是我们也可以看做是发给文件对象(各栈层可能有变动) 55 struct _FILE_OBJECT *OriginalFileObject;//本irp最初发往的文件对象 56 } Overlay; 57 KAPC Apc;//与本次irp相关的APC例程 58 PVOID CompletionKey; 59 } Tail; 60 } IRP, *PIRP;
每个IRP结构体后面紧跟一个数组,那就是IRP的I/O设备栈,数组中每个元素的类型为IO_SATCK_LOCATION(只列出部分字段),它包含的关键字段有主功能码次功能码,以及不同请求对应的功能参数等:
1 typedef struct _IO_STACK_LOCATION { 2 UCHAR MajorFunction;//主功能码 3 UCHAR MinorFunction;//次功能码 4 UCHAR Flags; 5 UCHAR Control;//DeviceControl的控制码 6 7 union { 8 9 struct { 10 ULONG Length;//读请求的长度 11 ULONG POINTER_ALIGNMENT Key; 12 LARGE_INTEGER ByteOffset;//读请求的文件偏移位置 13 } Read; 14 15 struct { 16 ULONG Length; //写请求的长度 17 ULONG POINTER_ALIGNMENT Key; 18 LARGE_INTEGER ByteOffset; //写请求的文件偏移位置 19 } Write; 20 21 22 struct { 23 ULONG OutputBufferLength; 24 ULONG POINTER_ALIGNMENT InputBufferLength; 25 ULONG POINTER_ALIGNMENT IoControlCode; 26 PVOID Type3InputBuffer; 27 } DeviceIoControl;//NtDeviceIoControlFile 28 29 struct { 30 PVOID Argument1; 31 PVOID Argument2; 32 PVOID Argument3; 33 PVOID Argument4; 34 } Others;//没有列举的结构可以用这几个字段 35 36 ... 37 } Parameters;//一个复杂的联合体,对应各种irp的参数 38 39 PDEVICE_OBJECT DeviceObject;//本栈层的设备对象 40 PFILE_OBJECT FileObject;//关联的文件对象 41 PIO_COMPLETION_ROUTINE CompletionRoutine;//记录着上层设置的完成例程 42 PVOID Context;//完成例程的参数 43 44 } IO_STACK_LOCATION, *PIO_STACK_LOCATION;
0x02 从WRK源码看IRP的构建与下发
1.首先看IRP的构建IoAllocateIrp
1 PIRP 2 IopAllocateIrpPrivate( 3 IN CCHAR StackSize, 4 IN BOOLEAN ChargeQuota 5 ) 6 7 /*++ 8 9 Routine Description: 10 11 This routine allocates an I/O Request Packet from the system nonpaged pool. 12 The packet will be allocated to contain StackSize stack locations. The IRP 13 will also be initialized. 14 15 Arguments: 16 17 StackSize - Specifies the maximum number of stack locations required. 18 19 ChargeQuota - Specifies whether quota should be charged against thread. 20 21 Return Value: 22 23 The function value is the address of the allocated/initialized IRP, 24 or NULL if one could not be allocated. 25 26 --*/ 27 28 { 29 USHORT allocateSize; 30 UCHAR fixedSize; 31 PIRP irp; 32 UCHAR lookasideAllocation; 33 PNPAGED_LOOKASIDE_LIST lookasideList; 34 UCHAR mustSucceed; 35 PP_NPAGED_LOOKASIDE_NUMBER number; 36 USHORT packetSize; 37 PKPRCB prcb; 38 39 // 40 // If the size of the packet required is less than or equal to those on 41 // the lookaside lists, then attempt to allocate the packet from the 42 // lookaside lists. 43 //IopLargeIrpStackLocations的值是8 44 45 irp = NULL; 46 fixedSize = 0; 47 mustSucceed = 0; 48 packetSize = IoSizeOfIrp(StackSize);//((USHORT) (sizeof( IRP ) + ((StackSize) * (sizeof( IO_STACK_LOCATION )))))注意这里是Irp大小+设备栈大小的总大小 49 allocateSize = packetSize; 50 //如果栈层数小于等于8又不计较配额浪费,那么就从预置的irp容器分配 51 if ((StackSize <= (CCHAR)IopLargeIrpStackLocations) && 52 ((ChargeQuota == FALSE) || (IopLookasideIrpFloat < IopLookasideIrpLimit))) { 53 fixedSize = IRP_ALLOCATED_FIXED_SIZE; 54 number = LookasideSmallIrpList; 55 if (StackSize != 1) { 56 allocateSize = IoSizeOfIrp((CCHAR)IopLargeIrpStackLocations);//对齐8个栈层大小 57 number = LookasideLargeIrpList; 58 } 59 60 prcb = KeGetCurrentPrcb(); 61 lookasideList = prcb->PPLookasideList[number].P;//尝试从该容器的P链表中分配出一个irp 62 lookasideList->L.TotalAllocates += 1;//该链表总的分配请求计数++ 63 irp = (PIRP)ExInterlockedPopEntrySList(&lookasideList->L.ListHead, 64 &lookasideList->Lock);//分配内存 65 if (irp == NULL) {//如果分配失败 66 lookasideList->L.AllocateMisses += 1;//该链表的分配失败计数++ 67 //再尝试从该容器的L链表中分配出一个irp 68 lookasideList = prcb->PPLookasideList[number].L; 69 lookasideList->L.TotalAllocates += 1; 70 irp = (PIRP)ExInterlockedPopEntrySList(&lookasideList->L.ListHead, 71 &lookasideList->Lock); 72 } 73 } 74 75 // 76 // If an IRP was not allocated from the lookaside list, then allocate 77 // the packet from nonpaged pool and charge quota if requested. 78 // 79 80 lookasideAllocation = 0; 81 if (!irp) {//如果仍然分配失败或者尚未分配 82 if (fixedSize != 0) { 83 lookasideList->L.AllocateMisses += 1; 84 } 85 86 // 87 // There are no free packets on the lookaside list, or the packet is 88 // too large to be allocated from one of the lists, so it must be 89 // allocated from nonpaged pool. If quota is to be charged, charge it 90 // against the current process. Otherwise, allocate the pool normally. 91 // 92 93 if (ChargeQuota) { 94 try { 95 irp = ExAllocatePoolWithQuotaTag(NonPagedPool, allocateSize,' prI');//直接从非分页池中分配 96 97 } except(EXCEPTION_EXECUTE_HANDLER) { 98 NOTHING; 99 } 100 101 } else { 102 103 // 104 // Attempt to allocate the pool from non-paged pool. If this 105 // fails, and the caller's previous mode was kernel then allocate 106 // the pool as must succeed. 107 // 108 109 irp = ExAllocatePoolWithTag(NonPagedPool, allocateSize, ' prI'); 110 if (!irp) { 111 mustSucceed = IRP_ALLOCATED_MUST_SUCCEED; 112 if (KeGetPreviousMode() == KernelMode ) { 113 irp = ExAllocatePoolWithTag(NonPagedPoolMustSucceed, 114 allocateSize, 115 ' prI'); 116 } 117 } 118 } 119 120 if (!irp) { 121 return NULL; 122 } 123 124 } else { 125 if (ChargeQuota != FALSE) { 126 lookasideAllocation = IRP_LOOKASIDE_ALLOCATION; 127 InterlockedIncrement( &IopLookasideIrpFloat ); 128 } 129 ChargeQuota = FALSE; 130 } 131 132 // 133 // Initialize the packet. 134 // 135 //分配完irp后,做一些基本字段的初始化 136 IopInitializeIrp(irp, packetSize, StackSize); 137 irp->AllocationFlags = (fixedSize | lookasideAllocation | mustSucceed); 138 if (ChargeQuota) { 139 irp->AllocationFlags |= IRP_QUOTA_CHARGED; 140 } 141 142 return irp; 143 } 144 145 #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整个结构(包括设备栈)。
1 #define IopInitializeIrp( Irp, 2 PacketSize, ,//实际分配的大小 3 StackSize ) {//栈层数 \ 4 RtlZeroMemory( (Irp), (PacketSize) ); \ 5 (Irp)->Type = (CSHORT) IO_TYPE_IRP; \ 6 (Irp)->Size = (USHORT) ((PacketSize)); \ 7 (Irp)->StackCount = (CCHAR) ((StackSize)); \ 8 (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1); //注意这里:初始栈空间位置在栈顶的上面 \ 9 (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment(); \ 10 InitializeListHead (&(Irp)->ThreadListEntry); \ 11 (Irp)->Tail.Overlay.CurrentStackLocation = \ 12 ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) + \ 13 sizeof( IRP ) + \ 14 ( (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 <= 0) { KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0, 0 ); } 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 - 1 )
可以看到,上层驱动在调用这个函数,将irp发到下层设备时,会自动在内部将当前栈空间位置向下滑动一个位置,指向下层的栈空间(递减IRP的CurrentLocation,并获得下一层的IO_STACK_LOCATION,设置到IRP的CurrentStackLocation指针中)。
PS:
1 ddk提供了一个宏,用来移动irp的栈空间位置 2 3 #define IoSetNextIrpStackLocation(Irp) \ 4 5 { \ 6 7 Irp->CurrentLocation--;\ //序号向下滑动一项 8 9 Irp->Tail.Overlay.CurrentStackLocation--;\ //数组元素指针也向下滑动一项 10 11 } 12 13 下面的宏实际上获取的就是当前栈空间的位置 14 15 #define IoGetCurrentIrpStackLocation(irp) irp->Tail.Overlay.CurrentStackLocation 16 17 下面的宏实际上获取的就是下层栈空间的位置 18 19 #define IoGetNextIrpStackLocation(irp) irp->Tail.Overlay.CurrentStackLocation – 1