Windows驅動之IRP結構

Windows驅動之IRP結構

IRP是數據請求包的一個簡稱,當應用程序發起CreateFile 或者 ReadFileAPI操作設備的時候,就會將相關參數信息封裝成爲一個IRP數據包,通過IoCallDriver傳遞給驅動程序。

下面具體來分析一下IRP的各個成員的意義。

1. IRP

IRP的數據結構如下:

typedef struct _IRP {
	PMDL              MdlAddress;
	ULONG             Flags;
	union {
		struct _IRP*   MasterIrp;
		PVOID          SystemBuffer;
	} AssociatedIrp;
	IO_STATUS_BLOCK   IoStatus;
	KPROCESSOR_MODE   RequestorMode;
	BOOLEAN           PendingReturned;
	BOOLEAN           Cancel;
	KIRQL             CancelIrql;
	PDRIVER_CANCEL    CancelRoutine;
	PVOID             UserBuffer;
	union {
		struct {
			union {
				KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
				struct {
					PVOID    DriverContext[4];
				};
			};
			PETHREAD     Thread;
			LIST_ENTRY   ListEntry;
		} Overlay;
	} Tail;
} IRP, *PIRP;

kd> dt nt!_IRP
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 MdlAddress       : Ptr32 _MDL
   +0x008 Flags            : Uint4B
   +0x00c AssociatedIrp    : <unnamed-tag>
   +0x010 ThreadListEntry  : _LIST_ENTRY
   +0x018 IoStatus         : _IO_STATUS_BLOCK
   +0x020 RequestorMode    : Char
   +0x021 PendingReturned  : UChar
   +0x022 StackCount       : Char
   +0x023 CurrentLocation  : Char
   +0x024 Cancel           : UChar
   +0x025 CancelIrql       : UChar
   +0x026 ApcEnvironment   : Char
   +0x027 AllocationFlags  : UChar
   +0x028 UserIosb         : Ptr32 _IO_STATUS_BLOCK
   +0x02c UserEvent        : Ptr32 _KEVENT
   +0x030 Overlay          : <unnamed-tag>
   +0x038 CancelRoutine    : Ptr32     void 
   +0x03c UserBuffer       : Ptr32 Void
   +0x040 Tail             : <unnamed-tag>

除去一些簡單的成員,例如Type,Size等,我們來看一下各個成員的具體意義:

  1. MdlAddress : 是一個MDL的指針,當內核層和用戶層採用共享內存的結構傳遞數據的時候,這個MDL就代表共享的內存信息(共享物理內存,通過MDL映射)。這個成員生效的標記爲:DO_DIRECT_IO, METHOD_IN_DIRECT 或者METHOD_OUT_DIRECT.
  2. AssociatedIrp : 這個成員是個聯合體,其中存在一個SystemBuffer程序;當內核層使用用戶層的數據的時候是通過拷貝數據的方式來實現的話,那麼拷貝後的數據就放在了AssociatedIrp.SystemBuffer中了。這個成員生效的標記是DO_BUFFERED_IO或者METHOD_BUFFERED
  3. IoStatus : 返回的狀態信息。
  4. RequestorMode : UserModeKernelMode,指定原始I/O請求的來源。驅動程序有時需要查看這個值來決定是否要信任某些參數。
  5. PendingReturned : Pending 狀態,如果爲TRUE,則表明處理該IRP的最低級派遣例程返回了STATUS_PENDING
  6. StackCount : 設備棧的數目。
  7. CurrentLocation : 當前處於哪個設備棧的索引。
  8. Cancel : IRP是否被取消,如果爲TRUE,則表明IoCancelIrp已被調用(該函數用於取消這個請求)。如果爲FALSE,則表明沒有調用IoCancelIrp函數。
  9. CancelIrql(KIRQL) : 是一個IRQL值,表明那個專用的取消自旋鎖是在這個IRQL上獲取的.
  10. CancelRoutine(PDRIVER_CANCEL) : 是驅動程序取消例程的地址。你應該使用IoSetCancelRoutine函數設置這個域而不是直接修改該域(因爲可以原子修改)。
  11. UserBuffer(PVOID) : 用戶層參數的直接地址,設置標記METHOD_NEITHER時候有效。
  12. Tail.Overlay 是Tail聯合中的一種聯合結構,如下:

在這裏插入圖片描述

在這個圖中,以水平方向從左到右是這個聯合的三個可選成員,在垂直方向是每個結構的成員描述:

  1. Tail.Overlay.DeviceQueueEntry(KDEVICE_QUEUE_ENTRY)Tail.Overlay.DriverContext(PVOID[4])Tail.Overlayare內一個未命名聯合的兩個可選成員(只能出現一個)。I/O管理器把DeviceQueueEntry作爲設備標準請求隊列中的連接域。當IRP還沒有進入某個隊列時,如果你擁有這個IRP你可以使用這個域,你可以任意使用DriverContext中的四個指針;Tail.Overlay.DeviceQueueEntry主要用在StartIo相關例程上面。
  2. Tail.Overlay.ListEntry(LIST_ENTRY)僅能作爲你自己實現的私有隊列的連接域;這個成員比較重要,因爲如果需要自己實現IRP異步執行的隊列,那麼就需要使用這個成員將IRP掛入到自己的隊列中。

2. IO_STACK_LOCATION

任何內核模式程序在創建一個IRP時,同時還創建了一個與之關聯的IO_STACK_LOCATION結構數組:數組中的每個堆棧單元都對應一個將處理該IRP的驅動程序,另外還有一個堆棧單元供IRP的創建者使用。堆棧單元中包含該IRP的類型代碼和參數信息以及完成函數的地址。例如如下設備關係:
在這裏插入圖片描述
IO_STACK_LOCATION結構如下:

typedef struct _IO_STACK_LOCATION {
    UCHAR  MajorFunction;
    UCHAR  MinorFunction;
    UCHAR  Flags;
    UCHAR  Control;
    union {
            struct {
                PIO_SECURITY_CONTEXT SecurityContext;
                ULONG Options;
                USHORT POINTER_ALIGNMENT FileAttributes;
                USHORT ShareAccess;
                ULONG POINTER_ALIGNMENT EaLength;
            } Create;
            struct {
                ULONG Length;
                ULONG POINTER_ALIGNMENT Key;
                LARGE_INTEGER ByteOffset;
            } Read;
            struct {
                ULONG Length;
                ULONG POINTER_ALIGNMENT Key;
                LARGE_INTEGER ByteOffset;
            } Write;
            ... ...
        } Parameters;
    PDEVICE_OBJECT  DeviceObject;
    PFILE_OBJECT  FileObject;
    PIO_COMPLETION_ROUTINE CompletionRoutine;
    PVOID Context;
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;

kd> dt nt!_IO_STACK_LOCATION
   +0x000 MajorFunction    : UChar
   +0x001 MinorFunction    : UChar
   +0x002 Flags            : UChar
   +0x003 Control          : UChar
   +0x004 Parameters       : <unnamed-tag>
   +0x014 DeviceObject     : Ptr32 _DEVICE_OBJECT
   +0x018 FileObject       : Ptr32 _FILE_OBJECT
   +0x01c CompletionRoutine : Ptr32     long 
   +0x020 Context          : Ptr32 Void

這個結構的主要成員意義爲:

  1. MajorFunction(UCHAR) : 是該IRP的主功能碼。這個代碼應該爲類似IRP_MJ_READ一樣的值,並與驅動程序對象中MajorFunction表的某個派遣函數指針相對應。如果該代碼存在於某個特殊驅動程序的I/O堆棧單元中,它有可能一開始是,例如IRP_MJ_READ,而後被驅動程序轉換成其它代碼,並沿着驅動程序堆棧發送到低層驅動程序。
  2. MinorFunction(UCHAR) : 是該IRP的副功能碼。它進一步指出該IRP屬於哪個主功能類。例如,IRP_MJ_PNP請求就有約一打的副功能碼,如IRP_MN_START_DEVICEIRP_MN_REMOVE_DEVICE,等等。
  3. Parameters(union) : 是幾個子結構的聯合,每個請求類型都有自己專用的參數,而每個子結構就是一種參數。這些子結構包括Create(IRP_MJ_CREATE請求)、Read(IRP_MJ_READ請求)、StartDevice(IRP_MJ_PNPIRP_MN_START_DEVICE子類型),等等。
  4. DeviceObject(PDEVICE_OBJECT) :是與該堆棧單元對應的設備對象的地址。該域由IoCallDriver函數負責填寫。
  5. FileObject(PFILE_OBJECT) : 是內核文件對象的地址,IRP的目標就是這個文件對象。驅動程序通常在處理清除請求(IRP_MJ_CLEANUP)時使用FileObject指針,以區分隊列中與該文件對象無關的IRP。
  6. CompletionRoutine(PIO_COMPLETION_ROUTINE) : 是一個I/O完成例程的地址,該地址是由與這個堆棧單元對應的驅動程序的更上一層驅動程序設置的。你絕對不要直接設置這個域,應該調用IoSetCompletionRoutine函數,該函數知道如何參考下一層驅動程序的堆棧單元。設備堆棧的最低一級驅動程序並不需要完成例程,因爲它們必須直接完成請求。然而,請求的發起者有時確實需要一個完成例程,但通常沒有自己的堆棧單元。這就是爲什麼每一級驅動程序都使用下一級驅動程序的堆棧單元保存自己完成例程指針的原因。
  7. Context(PVOID) : 是一個任意的與上下文相關的值,將作爲參數傳遞給完成例程。

3. IRP 和 IO_STACK_LOCATION 的交互

從上面我們知道,整個設備棧來處理這個IRP,但是每個設備都應該有自己的參數信息,這個參數信息就是通過IO_STACK_LOCATION 來保管的,那麼IRP是怎麼保管IO_STACK_LOCATION的呢?下面我們來分析一下整個流程。

3.1 IoAllocateIrp

這個函數用來分配一個IRP,我們看一下IRP的分配過程:

PIRP
IopAllocateIrpPrivate(
    IN CCHAR StackSize,
    IN BOOLEAN ChargeQuota
    )
{
    //...
    packetSize = IoSizeOfIrp(StackSize);
    allocateSize = packetSize;
    //...
    irp = ExAllocatePoolWithTag(NonPagedPool, allocateSize, ' prI');
    //...
    IopInitializeIrp(irp, packetSize, StackSize);
    //...
}

這個函數中,我們通過packetSize = IoSizeOfIrp(StackSize);來計算整個IRP的大小,這個聲明如下:

#define IoSizeOfIrp( StackSize ) \
    ((USHORT) (sizeof( IRP ) + ((StackSize) * (sizeof( IO_STACK_LOCATION )))))

也就是說是IRP的大小 + 設備棧個數個IO_STACK_LOCATION.

IopInitializeIrp : 用來初始化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) + 1);           \
    (Irp)->ApcEnvironment = KeGetCurrentApcEnvironment();         \
    InitializeListHead (&(Irp)->ThreadListEntry);                 \
    (Irp)->Tail.Overlay.CurrentStackLocation =                    \
        ((PIO_STACK_LOCATION) ((UCHAR *) (Irp) +                  \
            sizeof( IRP ) +                                       \
            ( (StackSize) * sizeof( IO_STACK_LOCATION )))); }

這個IRP和IO_STACK_LOCATION交互得有三個成員:

  1. (Irp)->StackCount = (CCHAR) ((StackSize)) 設備棧的大小:
  2. (Irp)->CurrentLocation = (CCHAR) ((StackSize) + 1) : 當前設備棧,放到了最頂端的下一個。
  3. (Irp)->Tail.Overlay.CurrentStackLocation : 指向最頂層的IO_STACK_LOCATION, 從這裏發現IO_STACK_LOCATION是從後往前來使用的。

3.2 IO_STACK_LOCATION的操作

在使用IO_STACK_LOCATION的時候,我們先要獲取這個這個程序,看下Windows是怎麼獲取的:

NTSTATUS
FASTCALL
IopfCallDriver(
    IN PDEVICE_OBJECT DeviceObject,
    IN OUT PIRP Irp
    )

{
    PIO_STACK_LOCATION irpSp;
    PDRIVER_OBJECT driverObject;
    NTSTATUS status;

    ASSERT( Irp->Type == IO_TYPE_IRP );

    Irp->CurrentLocation--;

    if (Irp->CurrentLocation <= 0) {
        KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0, 0 );
    }

    irpSp = IoGetNextIrpStackLocation( Irp );
    Irp->Tail.Overlay.CurrentStackLocation = irpSp;

    irpSp->DeviceObject = DeviceObject;

    driverObject = DeviceObject->DriverObject;

    PERFINFO_DRIVER_MAJORFUNCTION_CALL(Irp, irpSp, driverObject);

    status = driverObject->MajorFunction[irpSp->MajorFunction]( DeviceObject,
                                                              Irp );

    PERFINFO_DRIVER_MAJORFUNCTION_RETURN(Irp, irpSp, driverObject);

    return status;
}

#define IoGetNextIrpStackLocation( Irp ) (\
    (Irp)->Tail.Overlay.CurrentStackLocation - 1 )

在調用設備的分發函數之前,先使用IoGetNextIrpStackLocation是的IRP中的(Irp)->Tail.Overlay.CurrentStackLocation指向正確位置,因爲初始化的時候是指向最後一個的末尾的,因此這裏要前移一個。

在分發函數中,我們可以使用IoGetCurrentIrpStackLocation獲取當前的IO_STACK_LOCATION,這個宏定義如下:

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

直接返回了IRP中的成員。

當設備棧中的設備完成處理之後,需要向下傳遞IRP,因此需要調用IoCallDriver,在上面我們看到過,IoCallDriver會調用IoGetNextIrpStackLocation下移設備棧的指針,因此我們需要對設備棧做如下之一的操作:

#define IoCopyCurrentIrpStackLocationToNext( Irp ) { \
    PIO_STACK_LOCATION irpSp; \
    PIO_STACK_LOCATION nextIrpSp; \
    irpSp = IoGetCurrentIrpStackLocation( (Irp) ); \
    nextIrpSp = IoGetNextIrpStackLocation( (Irp) ); \
    RtlCopyMemory( nextIrpSp, irpSp, FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine)); \
    nextIrpSp->Control = 0; }

#define IoSkipCurrentIrpStackLocation( Irp ) \
    (Irp)->CurrentLocation++; \
    (Irp)->Tail.Overlay.CurrentStackLocation++;
  1. IoCopyCurrentIrpStackLocationToNext 拷貝IO_STACK_LOCATION 成員到下一層。
  2. IoSkipCurrentIrpStackLocation 上移一層,是的下次使用的時候仍舊使用當前的IO_STACK_LOCATION
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章