寫博客整理記錄一下IRP相關的知識點,加深一下印象。數組
全部的I/O請求都是以IRP的形式提交的。當I/O管理器爲了響應某個線程調用的的I/O API的時候,就會構造一個IRP,用於在I/O系統處理這個請求的過程當中表明該請求。安全
0x01 IRP與IO_STACK_LOCATION的結構體概覽less
IRP由兩部分組成,一個固定大小的頭(即IRP結構體的大小)以及一個或多個I/O棧單元(即IO_STACK_LOCATION數組)異步
先看頭部的IRP結構體,主要信息有:請求的類型和大小,請求是同步仍是異步,緩衝區指針,可被改變的狀態值等等。ide
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的構建與下發ui
1.首先看IRP的構建IoAllocateIrpthis
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函數中的關鍵信息:spa
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