Wednesday, September 05, 2007

Windows Driver Programming[7]

Relationship between IRP and IO_STACK_LOCATION

通常,Driver programmer 會被IoSkipCurrentIrpStackLocation()、 IoGetCurrentIrpStackLocation() 以及 IoCopyCurrentIrpStackLocationToNext() 給搞得很頭大。 其實,這三個函式都是 macro,把 macro 給展開,秘密大概就揭開了一半。

但是,首先來看看這張圖:



這是 IRP traverse 的時候,資料結構的型態。請注意一點,stack 增加的方向跟一般記憶體不同, + 表示是往前一個 stack,而 - 是往下一個 stack。

先來看 IoGetCurrentIrpStackLocation() 吧:

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

它只是取得 IRP.Tail.Overlay.CurrentStackLocation。注意,它的型別為 struct _IO_STACK_LOCATION *。 再來看 IoGetNextIrpStackLocation():

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

它只是回傳 IRP.Tail.Overlay.CurrentStackLocation - 1。 那 IoSetNextIrpStackLocation() 呢?

#define IoSetNextIrpStackLocation( Irp ) { 
 (Irp)->CurrentLocation--;
 (Irp)->Tail.Overlay.CurrentStackLocation--;
}

它也只是把 IRP.Tail.Overlay.CurrentStackLocation - 1 跟把 IRP.CurrentLocation - 1。

而 IoCallDriver() 做了什麼事呢(由書提供)?

NTSTATUS IoCallDriver(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
 IoSetNextIrpStackLocation(Irp);
 PIO_STACK_LOCATION stack = IoGetCurrentStackLocation(Irp);
 stack->DeviceObject = DeviceObject;
 ULONG fcn = stack->MajorFunction;
 PDRIVER_OBJECT driver = DeviceObject->DriverObject;
 return (*driver->MajorFunction[func])(DeviceObject, Irp);
}

Forwarding IRPs

要丟一個 IRP 的話,得呼叫 IoCallDriver(),而 IoCallDriver() 第一件事情就是先往下一個 stack location 走,然後再設定這個 stack location 的 DeviceObject。 最後,再根據這個 stack location 的 major function,呼叫這個 Device Object 的 Driver Object 所對應的函式。 注意喔,DeviceObject 是指 next DeviceObject,所以根據上圖,如果是 filter driver 要丟 IRP 的話,DeviceObject 得是 functional device object。

當 Driver 收到一個 IRP 時,它最常做的選擇便是,把它往下丟。往下丟有兩種,射後不理跟射後管理。先說射後管理,Driver 得先呼叫 IoCopyCurrentIrpStackLocationToNext(),然後再呼叫 IoCallDriver()。

#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;
}

只是把現在的 stack location 的內容複製到下一個 stack location(除了 CompletionRoutine 跟 Context 之外)。 注意,IoCopyCurrentStackLocationToNext() 會把 stack location 的 DeviceObject 也一起複製,不過沒關係,IoCallDriver() 會寫入正確的值。

如果是射後不理的方式呢?Driver 得先呼叫 IoSkipCurrentStackLocation(),然後再呼叫 IoCallDriver()。

#define IoSkipCurrentIrpStackLocation( Irp ) { 
 (Irp)->CurrentLocation++;
 (Irp)->Tail.Overlay.CurrentStackLocation++;
}

IoSkipCurrentStackLocation() 只是把 IRP.Tail.Overlay.CurrentStackLocation + 1 跟 IRP.CurrentLocation + 1,也就是往前一個 stack location。 而因為 IoCallDriver 會往下一個 stack location 走,所以結果就是 stack location 並沒有改變,但是 stack location 的 DeviceObject 會變成 lower Device Object。 而造成的後果是,你的 Driver Object 在 stack location 內消失了,而這表示你跟這個 IRP 會變成一點關係都沒有。

通常,Driver 會想知道這個 IRP 完成了沒,所以 Driver 可以呼叫 IoSetCompletionRoutine() 為這個 IRP 設定你專屬的 CompletionRoutine。 注意,IoSetCompletionRoutine() 會把你的 CompletionRoutine 設定在下個 stack location 內,而非現在的 stack location 內。

而當 physical device driver 最後完成這個 IRP,並呼叫 IoCompleteRequest()。

typedef struct _IRP {
 CSHORT Type;
 USHORT Size;

 // Define the common fields used to control the IRP.

 // Define a pointer to the Memory Descriptor List (MDL) for this I/O
 // request.  This field is only used if the I/O is "direct I/O".

 PMDL MdlAddress;

 // Flags word - used to remember various flags.

 ULONG Flags;

 // The following union is used for one of three purposes:
 //    1. This IRP is an associated IRP.  The field is a pointer to a master IRP.
 //
 //    2. This is the master IRP.  The field is the count of the number of
 //       IRPs which must complete (associated IRPs) before the master can
 //       complete.
 //
 //    3. This operation is being buffered and the field is the address of
 //       the system space buffer.
 union {
  struct _IRP *MasterIrp;
  __volatile LONG IrpCount;
  PVOID SystemBuffer;
 } AssociatedIrp;

 // Thread list entry - allows queueing the IRP to the thread pending I/O
 // request packet list.
 LIST_ENTRY ThreadListEntry;

 // I/O status - final status of operation.
 IO_STATUS_BLOCK IoStatus;

 // Requestor mode - mode of the original requestor of this operation.
 KPROCESSOR_MODE RequestorMode;

 // Pending returned - TRUE if pending was initially returned as the
 // status for this packet.
 BOOLEAN PendingReturned;

 // Stack state information.
 CHAR StackCount;
 CHAR CurrentLocation;

 // Cancel - packet has been canceled.
 BOOLEAN Cancel;

 // Cancel Irql - Irql at which the cancel spinlock was acquired.
 KIRQL CancelIrql;

 // ApcEnvironment - Used to save the APC environment at the time that the
 // packet was initialized.
 CCHAR ApcEnvironment;

 // Allocation control flags.
 UCHAR AllocationFlags;

 // User parameters.
 PIO_STATUS_BLOCK UserIosb;
 PKEVENT UserEvent;

 union {
  struct {

   union {
    PIO_APC_ROUTINE UserApcRoutine;
    PVOID IssuingProcess;
   };

   PVOID UserApcContext;

  } AsynchronousParameters;

  LARGE_INTEGER AllocationSize;
 } Overlay;

 // CancelRoutine - Used to contain the address of a cancel routine supplied
 // by a device driver when the IRP is in a cancelable state.
 __volatile PDRIVER_CANCEL CancelRoutine;

 // Note that the UserBuffer parameter is outside of the stack so that I/O
 // completion can copy data back into the user's address space without
 // having to know exactly which service was being invoked.  The length
 // of the copy is stored in the second half of the I/O status block. If
 // the UserBuffer field is NULL, then no copy is performed.
 PVOID UserBuffer;

 // Kernel structures
 //
 // The following section contains kernel structures which the IRP needs
 // in order to place various work information in kernel controller system
 // queues.  Because the size and alignment cannot be controlled, they are
 // placed here at the end so they just hang off and do not affect the
 // alignment of other fields in the IRP.

 union {
  struct {
   union {

    // DeviceQueueEntry - The device queue entry field is used to
    // queue the IRP to the device driver device queue.
    KDEVICE_QUEUE_ENTRY DeviceQueueEntry;

    struct {
     // The following are available to the driver to use in
     // whatever manner is desired, while the driver owns the
     // packet.
     PVOID DriverContext[4];
    } ;
   } ;

   // Thread - pointer to caller's Thread Control Block.
   PETHREAD Thread;

   // Auxiliary buffer - pointer to any auxiliary buffer that is
   // required to pass information to a driver that is not contained
   // in a normal buffer.
   PCHAR AuxiliaryBuffer;

   // The following unnamed structure must be exactly identical
   // to the unnamed structure used in the minipacket header used
   // for completion queue entries.
   struct {

    // List entry - used to queue the packet to completion queue, among others.
    LIST_ENTRY ListEntry;

    union {

     // Current stack location - contains a pointer to the current
     // IO_STACK_LOCATION structure in the IRP stack.  This field
     // should never be directly accessed by drivers.  They should
     // use the standard functions.
     struct _IO_STACK_LOCATION *CurrentStackLocation;

     // Minipacket type.
     ULONG PacketType;
    };
   };

   // Original file object - pointer to the original file object
   // that was used to open the file.  This field is owned by the
   // I/O system and should not be used by any other drivers.
   PFILE_OBJECT OriginalFileObject;

  } Overlay;

  // APC - This APC control block is used for the special kernel APC as
  // well as for the caller's APC, if one was specified in the original
  // argument list.  If so, then the APC is reused for the normal APC for
  // whatever mode the caller was in and the "special" routine that is
  // invoked before the APC gets control simply deallocates the IRP.
  KAPC Apc;

  // CompletionKey - This is the key that is used to distinguish
  // individual I/O operations initiated on a single file handle.
  PVOID CompletionKey;

 } Tail;

} IRP, *PIRP;

typedef struct _IO_STATUS_BLOCK {
 union {
  NTSTATUS Status;
  PVOID Pointer;
 };

 // A common use of the Information field is to hold the total number of bytes transferred
 // by an operation such as IRP_MJ_READ that transfers data.

 ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef struct _IO_STACK_LOCATION {
 UCHAR MajorFunction;
 UCHAR MinorFunction;
 UCHAR Flags;
 UCHAR Control;

 // The following user parameters are based on the service that is being
 // invoked.  Drivers and file systems can determine which set to use based
 // on the above major and minor function codes.

 union {

  // System service parameters for:  NtCreateFile
  struct {
   PIO_SECURITY_CONTEXT SecurityContext;
   ULONG Options;
   USHORT FileAttributes;
   USHORT ShareAccess;
   ULONG EaLength;
  } Create;

  // System service parameters for:  NtReadFile
  struct {
   ULONG Length;
   ULONG Key;
   LARGE_INTEGER ByteOffset;
  } Read;

  // System service parameters for:  NtWriteFile
  struct {
   ULONG Length;
   ULONG Key;
   LARGE_INTEGER ByteOffset;
  } Write;

  // System service parameters for:  NtQueryDirectoryFile
  struct {
   ULONG Length;
   PUNICODE_STRING FileName;
   FILE_INFORMATION_CLASS FileInformationClass;
   ULONG FileIndex;
  } QueryDirectory;

  // System service parameters for:  NtNotifyChangeDirectoryFile
  struct {
   ULONG Length;
   ULONG CompletionFilter;
  } NotifyDirectory;

  // System service parameters for:  NtQueryInformationFile
  struct {
   ULONG Length;
   FILE_INFORMATION_CLASS FileInformationClass;
  } QueryFile;

  // System service parameters for:  NtSetInformationFile
  struct {

   ULONG Length;
   FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass;
   PFILE_OBJECT FileObject;

   union {

    struct {
     BOOLEAN ReplaceIfExists;
     BOOLEAN AdvanceOnly;
    };

    ULONG ClusterCount;
    HANDLE DeleteHandle;
   };

  } SetFile;

  // System service parameters for:  NtQueryEaFile
  struct {
   ULONG Length;
   PVOID EaList;
   ULONG EaListLength;
   ULONG EaIndex;
  } QueryEa;

  // System service parameters for:  NtSetEaFile
  struct {
   ULONG Length;
  } SetEa;

  // System service parameters for:  NtQueryVolumeInformationFile
  struct {
   ULONG Length;
   FS_INFORMATION_CLASS FsInformationClass;
  } QueryVolume;

  // System service parameters for:  NtSetVolumeInformationFile
  struct {
   ULONG Length;
   FS_INFORMATION_CLASS FsInformationClass;
  } SetVolume;

  // System service parameters for:  NtFsControlFile
  //
  // Note that the user's output buffer is stored in the UserBuffer field
  // and the user's input buffer is stored in the SystemBuffer field.
  struct {
   ULONG OutputBufferLength;
   ULONG InputBufferLength;
   ULONG FsControlCode;
   PVOID Type3InputBuffer;
  } FileSystemControl;

  // System service parameters for:  NtLockFile/NtUnlockFile
  struct {
   PLARGE_INTEGER Length;
   ULONG POINTER_ALIGNMENT Key;
   LARGE_INTEGER ByteOffset;
  } LockControl;

  // System service parameters for:  NtFlushBuffersFile
  //
  // No extra user-supplied parameters.

  // System service parameters for:  NtCancelIoFile
  // No extra user-supplied parameters.

  // System service parameters for:  NtDeviceIoControlFile
  // Note that the user's output buffer is stored in the UserBuffer field
  // and the user's input buffer is stored in the SystemBuffer field.
  struct {
   ULONG OutputBufferLength;
   ULONG InputBufferLength;
   ULONG IoControlCode;
   PVOID Type3InputBuffer;
  } DeviceIoControl;

  // System service parameters for:  NtQuerySecurityObject
  struct {
   SECURITY_INFORMATION SecurityInformation;
   ULONG Length;
  } QuerySecurity;

  // System service parameters for:  NtSetSecurityObject
  struct {
   SECURITY_INFORMATION SecurityInformation;
   PSECURITY_DESCRIPTOR SecurityDescriptor;
  } SetSecurity;

  // Non-system service parameters.
  //
  // Parameters for MountVolume
  struct {
   PVPB Vpb;
   PDEVICE_OBJECT DeviceObject;
  } MountVolume;

  // Parameters for VerifyVolume
  struct {
   PVPB Vpb;
   PDEVICE_OBJECT DeviceObject;
  } VerifyVolume;

  // Parameters for Scsi with internal device contorl.
  struct {
   struct _SCSI_REQUEST_BLOCK *Srb;
  } Scsi;

  // System service parameters for:  NtQueryQuotaInformationFile
  struct {
   ULONG Length;
   PSID StartSid;
   PFILE_GET_QUOTA_INFORMATION SidList;
   ULONG SidListLength;
  } QueryQuota;

  // System service parameters for:  NtSetQuotaInformationFile
  struct {
   ULONG Length;
  } SetQuota;

  // Parameters for IRP_MN_QUERY_DEVICE_RELATIONS
  struct {
   DEVICE_RELATION_TYPE Type;
  } QueryDeviceRelations;

  // Parameters for IRP_MN_QUERY_INTERFACE
  struct {
   CONST GUID *InterfaceType;
   USHORT Size;
   USHORT Version;
   PINTERFACE Interface;
   PVOID InterfaceSpecificData;
  } QueryInterface;


  // Parameters for IRP_MN_QUERY_CAPABILITIES
  struct {
   PDEVICE_CAPABILITIES Capabilities;
  } DeviceCapabilities;

  // Parameters for IRP_MN_FILTER_RESOURCE_REQUIREMENTS
  struct {
   PIO_RESOURCE_REQUIREMENTS_LIST IoResourceRequirementList;
  } FilterResourceRequirements;

  // Parameters for IRP_MN_READ_CONFIG and IRP_MN_WRITE_CONFIG
  struct {
   ULONG WhichSpace;
   PVOID Buffer;
   ULONG Offset;
   ULONG Length;
  } ReadWriteConfig;

  // Parameters for IRP_MN_SET_LOCK
  struct {
   BOOLEAN Lock;
  } SetLock;

  // Parameters for IRP_MN_QUERY_ID
  struct {
   BUS_QUERY_ID_TYPE IdType;
  } QueryId;

  // Parameters for IRP_MN_QUERY_DEVICE_TEXT
  struct {
   DEVICE_TEXT_TYPE DeviceTextType;
   LCID LocaleId;
  } QueryDeviceText;

  // Parameters for IRP_MN_DEVICE_USAGE_NOTIFICATION
  struct {
   BOOLEAN InPath;
   BOOLEAN Reserved[3];
   DEVICE_USAGE_NOTIFICATION_TYPE Type;
  } UsageNotification;

  // Parameters for IRP_MN_WAIT_WAKE
  struct {
   SYSTEM_POWER_STATE PowerState;
  } WaitWake;

  // Parameter for IRP_MN_POWER_SEQUENCE
  struct {
   PPOWER_SEQUENCE PowerSequence;
  } PowerSequence;

  // Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER
  struct {
   union {
    ULONG SystemContext;
    SYSTEM_POWER_STATE_CONTEXT SystemPowerStateContext;
   };

   POWER_STATE_TYPE Type;
   POWER_STATE State;
   POWER_ACTION ShutdownType;
  } Power;

  struct {
   ULONG SystemContext;
   POWER_STATE_TYPE Type;
   POWER_STATE State;
   POWER_ACTION ShutdownType;
  } Power;

  // Parameters for StartDevice
  struct {
   PCM_RESOURCE_LIST AllocatedResources;
   PCM_RESOURCE_LIST AllocatedResourcesTranslated;
  } StartDevice;

  // Parameters for Cleanup
  //
  // No extra parameters supplied

  // WMI Irps
  struct {
   ULONG_PTR ProviderId;
   PVOID DataPath;
   ULONG BufferSize;
   PVOID Buffer;
  } WMI;

  // Others - driver-specific
  struct {
   PVOID Argument1;
   PVOID Argument2;
   PVOID Argument3;
   PVOID Argument4;
  } Others;

 } Parameters;

 // Save a pointer to this device driver's device object for this request
 // so it can be passed to the completion routine if needed.
 // IoCallDriver() fills this field.

 PDEVICE_OBJECT DeviceObject;

 // The following location contains a pointer to the file object for this
 // request.

 PFILE_OBJECT FileObject;

 // The following routine is invoked depending on the flags in the above flags field.
 // Use IoSetCompletionRoutine() to set this field.

 PIO_COMPLETION_ROUTINE CompletionRoutine;

 // The following is used to store the address of the context parameter
 // that should be passed to the CompletionRoutine.
 // Use IoSetCompletionRoutine() to set this field.

 PVOID Context;

} IO_STACK_LOCATION, *PIO_STACK_LOCATION;

2 comments:

klhsieh said...

感謝詳盡的解說

return (*driver->MajorFunction[func])(DeviceObject, Irp);
應為
return (*driver->MajorFunction[fcn])(device, Irp);

ref:http://www.microsoft.com/mspress/books/WW/sampchap/2507b.aspx

OD said...

可能是書的版本不一樣,看起來結果是相同的,只是參數名字不同。

codeblock