刘收获

导航

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

 

posted on 2019-02-21 17:37  沉疴  阅读(1135)  评论(0编辑  收藏  举报