《Windows内核编程》---基本数据结构
驱动对象:
每个驱动程序都会有唯一的驱动对象与之对应,并且这个驱动对象是在驱动加载时被内核中的对象管理程序所创建的。驱动对象用
DRIVER_OBJECT数据结构表示,它作为驱动的一个实例被内核加载,并且内核对一个驱动只加载一个实例。确切地说,是由内核中的I/O
管理器负责加载的,驱动程序需要在DriverEntry中初始化。驱动对象的结构定义如下(wdm.h):
typedef struct _DRIVER_OBJECT {
//结构的类型和大小
CSHORT Type;
CSHORT Size;
//每个驱动程序会有一个或多个设备对象,其中,每个设备对象都有一个指针指向下一个驱动对象
//最后一个设备对象指向空。DeviceObject指向驱动对象的第一个设备对象。通过DeviceObject,就
//可以遍历驱动对象中的所有设备对象了。
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
// The following section describes where the driver is loaded. The count
// field is used to count the number of times the driver has had its
// registered reinitialization routine invoked.
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
// 记录驱动设备的名字,用UNICODE字符串记录,该字符串一般/Driver/[驱动程序名称]
UNICODE_STRING DriverName;
//设备的硬件数据库键名,也是UNICODE字符串记录。一般为
// /REGISTRY/MACHINE/HADRWARE/DESCRIPTION/SYSTEM
PUNICODE_STRING HardwareDatabase;
//文件驱动中用到的派遣函数
PFAST_IO_DISPATCH FastIoDispatch;
// The following section describes the entry points to this particular
// driver. Note that the major function dispatch table must be the last
// field in the object so that it remains extensible.
PDRIVER_INITIALIZE DriverInit;
//记录StartIO例程的函数地址,用于串行化操作
PDRIVER_STARTIO DriverStartIo;
//指定驱动卸载时所用的回调函数地址
PDRIVER_UNLOAD DriverUnload;
//MajorFunction域记录的是一个函数指针数组,也就是MajorFunction是一个数组,数组中的每个
//成员记录着一个指针,每一个指针指向的是一个函数。这个函数是处理IRP的派遣函数
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
实际上如果写一个驱动程序,或者说编写一个内核模块,要在Windows中加载,就必须填写上面的结构,来告诉Windows程序提供的功能
。注意内核模块并不生成一个进程,它只是写一组回调函数让Windows调用,且这组回调函数必须符合Windows内核规定的格式。上面代
码中的“快速IO分发函数”FastIoDispatch和“普通分发函数”MajorFunction就是这样一种回调函数。这些函数用来处理发送给这个内
核模块的请求。
Windows中很多组件都拥有自己的DRIVER_OBJECT,例如:所有的硬件驱动程序、所有的类驱动(Disk、Cdrom…)、文件系统(NTFS和
FastFat,有各自的DRIVER_OBJECT),以及许多其他的内核组件。我们可以使用一个软件WinObj来查看所有的内核对象。
设备对象:
每个驱动程序会创建一个或多个设备对象,用DEVICE_OBJECT数据结构表示。每个设备对象都会有一个指针指向下一个设备对象,因此就
形成一个设备链。设备链的第一个设备是由DRIVER_OBJECT结构体中指明的。
设备对象是内核中的重要对象,其重要性不亚于Windows GUI编程中的窗口。窗口是唯一可以接收消息的对象,任何消息都是发送到一个
窗口中的;而在内核编程中,大部分“消息”是以请求IRP的方式传递的。而设备对象(DEVICE_OBJECT)是唯一可以接收请求的实体,
任何一个请求IRP都是发送给某个设备对象的。
设备对象的结构是DEVICE_OBJECT,常常被简称为DO。一个DO可以代表很多不同的东西,例如一个实际的硬盘、或实现一个类似管道的功
能等等。我们总是在内核程序中生成一个DO,而一个内核程序是用一个驱动对象表示的,因此,一个设备对象总是属于一个驱动对象。
在WDK的wdm.h文件中DO的定义如下:
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT)
_DEVICE_OBJECT {
//结构的类型和大小
CSHORT Type;
USHORT Size;
//引用计数
LONG ReferenceCount;
//指向驱动程序中的驱动对象,同属于一个驱动程序的驱动对象指向的是同一驱动对象
struct _DRIVER_OBJECT *DriverObject;
//下一个设备对象。
//这里指的下一个设备对象是同属于一个驱动对象的设备,也就是同一个驱动程序创建的若干设备
//对象,每个设备对象根据NextDevice域形成链表,从而可以枚举每个设备对象
struct _DEVICE_OBJECT *NextDevice;
//指向下一个设备对象,这里指的是,如果有更高一层的驱动附加到这个驱动的时候
//AttachedDevice指向的就是那个更高一层的驱动
struct _DEVICE_OBJECT *AttachedDevice;
//在使用StartIO例程的时候,此域指向的是当前IRP结构
struct _IRP *CurrentIrp;
PIO_TIMER Timer;
//此域是一个32位的无符号整型,每一位有具体的含义
//DO_BUFFERED_IO---读写操作使用缓冲方式(系统复制缓冲区)访问用户模式数据
//DO_EXCLUSIVE---一次只允许一个线程打开设备句柄
//DO_DIRECT_IO---读写操作使用直接方式(内存描述符表)访问用户模式数据
//DO_DEVICE_INITIALIZING---设备对象正在初始化
//DO_POWER_PAGABLE---必须在PASSIVE_LEVEL级上处理IRP_MJ_PNP请求
//DO_POWER_INRUSH---设备上电期间需要大电流
ULONG Flags;
ULONG Characteristics;
__volatile PVPB Vpb;
//指向设备扩展对象,每个设备都会指定一个设备扩展对象,设备扩展对象记录的是设备自己
//特殊定义的结构体,即程序员自己定义的结构体。另外,在驱动开发中,应该尽量避免全局变量的
//使用,因为全局变量涉及不容易同步问题。解决的方法是:将全局变量存在设备扩展中
PVOID DeviceExtension;
//设备类型,当制作虚拟设备时,应选择FILE_DEVICE_UNKNOWN类型的设备
DEVICE_TYPE DeviceType;
//IRP栈大小。在多层驱动情况下,驱动与驱动之间会形成类似堆栈的结构,IRP会依次从
//最高层传递到最底层
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
//设备在大容量传输时,需要内存对齐,以保证传输速度
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
// The following field is for exclusive use by the filesystem to keep
// track of the number of Fsp threads currently using the device
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT;
typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;
一个驱动对象可以生成多个设备对象,而Windows向设备对象发送请求时,这些请求是被驱动对象的分发函数所捕获的,即当Windows内
核向一个设备发送一个请求时,驱动对象的分发函数中的某一个会被调用,分发函数原型如下:
//参数device是请求的目标设备;参数irp是请求的指针
NTSTATUE ASCEDispatch(PDEVICE_OBJECT device, PIRP irp);
附:设备扩展:
设备对象记录“通用”设备的信息,而另外一些“特殊”信息记录在设备扩展里。各个设备扩展由程序员自己定义,每个设备的设备扩
展不尽相同。设备扩展是由程序员指定内容和大小,由I/O管理器创建的,并保存在非分页内存中。
在驱动程序中,尽量避免使用全局函数,因为全局函数往往导致函数的不可重入性。重入性指的是在多线程程序中,多个函数并行运行
,函数的运行结果不会根据函数的调用先后顺序而导致不同。解决的办法是,将全局变量以设备扩展的形式存储,并加以适当的同步保
护措施。除此之外,在设备扩展中还会记录下列一些内容:
设备对象的反向指针;
设备状态或驱动环境变量;
中断对象指针;
控制器对象指针。
由于设备扩展是驱动程序专用的,它的结构必须在驱动程序的头文件中定义。
请求IRP:
内核中大部分请求以IRP的形式发送。IRP是一个内核数据结构,比较复杂,因为它要表示无数种实际请求。在WDK的wdm.h中可看到IRP的
结构:
// I/O Request Packet (IRP) definition
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _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; //IPR栈空间大小
CHAR CurrentLocation; //IRP当前栈空间
// 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;
typedef IRP *PIRP;
上面出现IRP栈空间,是因为一个IRP往往要传递n个设备才能得以完成,而在传递过程中会有一些“中间转换”,导致请求的参数变化。
为了保存这些变换,我们给每次中转都留一个“栈空间”,用于保存中间参数。
常见的请求:
生成请求:主功能号为IRP_MJ_CREATE
查询请求:主功能号为IRP_MJ_QUERY_INFORMATION
设置请求:主功能号为IRP_MJ_SET_INFORMATION
控制请求:主功能号为IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL
关闭请求:主功能号为IRP_MJ_CLOSE
请求指针:IRP的指针,即PIRP或IRP*
========
Windows内核常见数据结构(基本类型)
http://laokaddk.blog.51cto.com/368606/338722/学内核从基本数据结构开始吧,就像学C语言时从学习int,char开始一样.
只列出目前见到和用到的,其它后面再补充~
常用数据结构:
数字:
lkd> dt _ULARGE_INTEGER
ntdll!_ULARGE_INTEGER
+0x000 LowPart : Uint4B
+0x004 HighPart : Uint4B
+0x000 u : __unnamed
+0x000 QuadPart : Uint8B
lkd> dt _LARGE_INTEGER
ntdll!_LARGE_INTEGER
+0x000 LowPart : Uint4B
+0x004 HighPart : Int4B
+0x000 u : __unnamed
+0x000 QuadPart : Int8B
字符串:
lkd> dt _STRING
nt!_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Char
lkd> dt _UNICODE_STRING
ntdll!_UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32 Uint2B
单链表头(看来数据结构要学好啊)::
lkd> dt _SLIST_HEADER
ntdll!_SLIST_HEADER
+0x000 Alignment : Uint8B
+0x000 Next : _SINGLE_LIST_ENTRY
+0x004 Depth : Uint2B
+0x006 Sequence : Uint2B
链表结点:
lkd> dt _KNODE
ntdll!_KNODE
+0x000 ProcessorMask : Uint4B
+0x004 Color : Uint4B
+0x008 MmShiftedColor : Uint4B
+0x00c FreeCount : [2] Uint4B
+0x018 DeadStackList : _SLIST_HEADER //链表头
+0x020 PfnDereferenceSListHead : _SLIST_HEADER
+0x028 PfnDeferredList : Ptr32 _SINGLE_LIST_ENTRY
+0x02c Seed : UChar
+0x02d Flags : _flags
单链表的指针:
lkd> dt _SINGLE_LIST_ENTRY
ntdll!_SINGLE_LIST_ENTRY
+0x000 Next : Ptr32 _SINGLE_LIST_ENTRY
双向链表指针:
lkd> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
内核队列:
lkd> dt _KQUEUE
ntdll!_KQUEUE
+0x000 Header : _DISPATCHER_HEADER
+0x010 EntryListHead : _LIST_ENTRY
+0x018 CurrentCount : Uint4B
+0x01c MaximumCount : Uint4B
+0x020 ThreadListHead : _LIST_ENTRY
一个很多地方用到的头部结构:
lkd> dt _DISPATCHER_HEADER
ntdll!_DISPATCHER_HEADER
+0x000 Type : UChar
+0x001 Absolute : UChar
+0x002 Size : UChar
+0x003 Inserted : UChar
+0x004 SignalState : Int4B
+0x008 WaitListHead : _LIST_ENTRY
========
IRP结构
一、IRP 简介
IRP是I/O Request Pcaket 的缩写,即I/O请求包。驱动与驱动之间通过 IRP 进行通信。而使用驱动的应用层调用的
CreatFile,ReadFile,WriteFile,DeviceIoControl 等函数,说到底也是使用 IRP 和驱动进行通信。IRP由I/O管理器根据用户态程序提
出的请求创建并传给相应的驱动程序。在分层的驱动程序中,这个过程很复杂,一个IRP常常要穿越几层驱动程序。
二、IRP结构
IRP功能的复杂性也决定了IRP结构的复杂性。正确理解IRP的结构是理解驱动程序开发的基础。另外,IRP的创建是由I/O管理器
在非分页内存进行的。
一个IRP有两部分组成:头部区域和I/O堆栈位置。
1)IRP的头部区域是一个固定的部分,起始就是一个IRP结构。
2)在这个头部的后面是一个I/O stack locations,这是一个IO_STACK_LOCATIONS的结构体数 组,这个数组中元素的个数视具
体情况而定。由 IoAllocateIrp( IN CCHAR StackSize , IN BOOLEAN ChargeQuota ) 时的参数 StackSize 决定。而 StackSize 通常
由 IRP 发往的目标 DEVICE_OBJECT 的 +30 char StackSize 决定。而这个 StackSize 是由设备对象连入所在的设备栈时,根据在设备
栈中位置决定的。
下面看看IRP结构(头部区域)和IO_STACK_LOCATIONS(I/O堆栈)的结构的定义
1. IRP结构介绍,结构图如下,其中灰色部分为不可见区域,这里主要讲解一下可见区域。
1.1 PMDL MdlAddress : 设备执行直接I/O时,指向用户空间的内存描述表
1.2 ULONG Flags: 包含一些对驱动程序只读的标志。但这些标志与WDM驱动程序无关
1.3 AssociatedIrp.SystemBuffer : SystemBuffer指针指向一个数据缓冲区,该缓冲区位于内核模式的非分页内存中I/O管理器
把用户模式程序发送给驱动程序的数据复制到这个缓冲区,这也是创建IRP过程的一部分。对于读请求,设备驱动程序把读出的数据填到
这个缓冲区,然后I/O管理器再把缓冲区的内容复制到用户模式缓冲区。
1.4 IoStatus : 是一个结构体IO_STATUS_BLOCK, 这个结构体仅包含两个域,驱动程序在最终完成请求时设置这个结构。
IoStatus.Status : 将收到一个NTSTATUS代码。
IoStatus.Information 的类型为ULONG_PTR,它将收到一个信息值,该信息值的确切含义要取决于具体的IRP类型和请求完成的状态。
Information域的一个公认用法是用于保存数据传输操作。某些PnP请求把这个域作为指向另外一个结构的指针,这个结构通常包含查询
请求的结果。
1.5 RequestorMode将等于一个枚举常量UserMode或KernelMode,指定原始I/O请求的来源。驱动程序有时需要查看这个值来决
定是否要信任某些参数。
1.6 PendingReturned(BOOLEAN)如果为TRUE,则表明处理该IRP的最低级派遣例程返回了STATUS_PENDING。完成例程通过参考该
域来避免自己与派遣例程间的潜在竞争。
1.7 Cancel(BOOLEAN)如果为TRUE,则表明IoCancelIrp已被调用,该函数用于取消这个请求。如果为FALSE,则表明没有调用
IoCancelIrp函数。取消IRP是一个相对复杂的主题,我将在本章的最后详细描述它。
1.8 CancelIrql(KIRQL)是一个IRQL值,表明那个专用的取消自旋锁是在这个IRQL上获取的。当你在取消例程中释放自旋锁时应
参考这个域。
1.9 CancelRoutine(PDRIVER_CANCEL)是驱动程序取消例程的地址。你应该使用IoSetCancelRoutine函数设置这个域而不是直接
修改该域。
2.0 UserBuffer(PVOID) 对于METHOD_NEITHER方式的IRP_MJ_DEVICE_CONTROL请求,该域包含输出缓冲区的用户模式虚拟地址。
该域还用于保存读写请求缓冲区的用户模式虚拟地址,但指定了DO_BUFFERED_IO或DO_DIRECT_IO标志的驱动程序,其读写例程通常不需
要访问这个域。当处理一个METHOD_NEITHER控制操作时,驱动程序能用这个地址创建自己的MDL。
2. IO堆栈
MajorFunction(UCHAR)是该IRP的主功能码
MinorFunction(UCHAR)是该IRP的副功能码
Parameters(union)是几个子结构的联合,每个请求类型都有自己专用的参数,而每个子结构就是一种参数。这些子结构包
括Create(IRP_MJ_CREATE请求)、Read(IRP_MJ_READ请求)、StartDevice(IRP_MJ_PNP的IRP_MN_START_DEVICE子类型),等等。
DeviceObject(PDEVICE_OBJECT)是与该堆栈单元对应的设备对象的地址。该域由IoCallDriver函数负责填写。
FileObject(PFILE_OBJECT)是内核文件对象的地址,IRP的目标就是这个文件对象。驱动程序通常在处理清除请求
(IRP_MJ_CLEANUP)时使用FileObject指针,以区分队列中与该文件对象无关的IRP。
CompletionRoutine(PIO_COMPLETION_ROUTINE)是一个I/O完成例程的地址,该地址是由与这个堆栈单元对应的驱动程序的更上一
层驱动程序设置的。你绝对不要直接设置这个域,应该调用IoSetCompletionRoutine函数,该函数知道如何参考下一层驱动程序的堆栈
单元。设备堆栈的最低一级驱动程序并不需要完成例程,因为它们必须直接完成请求。然而,请求的发起者有时确实需要一个完成例程
,但通常没有自己的堆栈单元。这就是为什么每一级驱动程序都使用下一级驱动程序的堆栈单元保存自己完成例程指针的原因。
IO堆栈总结:
1)I/O堆栈位置的主要目的是,保存一个I/O请求的函数代码和参数。
2)I/O堆栈数量实际上就是参与I/O请求的I/O层的数量。
3)在一个IRP中,上层驱动负责为下层驱动设置堆栈位置指针。
i)驱动程序可以为每个IRP调用IoGetCurrentStackLocation来获得指向其自身堆栈位置的指针,
ii)上层驱动程序必须调用IoGetNextIrpStackLocation来获得指向下层驱动程序堆栈位置的指针。
因此,上层驱动可以在传送IRP给下层驱动之前设置堆栈位置的内容。
4)上层驱动调用IoCallDriver,将DeviceObject成员设置成下层驱动目标设备对象。当上层驱动完成IRP时,IoCompletion 函数被调用
,I/O管理器传送给IoCompletion函数一个指向上层驱动的设备对象的指针。
=========================================================================================
假设某过滤驱动的分层结构如下:
1 FIDO <-- 此时你在这里调用IoAllocateIrp()创建一个先的IRP往下层驱动FDO传送
2 FDO
3 PDO
假设IO堆栈单元有3个,第3个IO堆栈单元表示new_IRP当前IO堆栈单元 第2个表示FDO的IO堆栈单元 第1个表示PDO的堆栈单元那么如果要
发送此IRP到下层驱动FDO,则预先初始化new_IRP的FDO的IO堆栈单元,也就是第二个IO堆栈单元。此时new_IRP 的 StackCount = 2,
CurrentLocation = 3 这里的3表示new_IRP的当前IO堆栈单元 如果你需要把此IRP往FDO驱动传递,那么不用初始化这个IO堆栈单元,为
什么呢?
因为IoCallDriver() 内部会调用 类似 IoSetNextIrpStackLocation() 的操作来调整 CurrentLocation的索引。也就是 索引-1 ;所以
要调用IoCallDriver() 把new_IRP传递到FDO这个下层驱动,需要先调用IoGetNextIrpStackLocation() 获取FDO对应的IO堆栈单元,并
进行初始化。初始化完成后才能调用IoCallDriver()此时new_IRP的 CurrentLocation = 2 这个索引才是指向FDO的IO堆栈单元
下面是来自WMD一书的例子:我上面的话就是为了理解下面的代码片断
发往派遣例程
创建完IRP后,你可以调用IoGetNextIrpStackLocation函数获得该IRP第一个堆栈单元的指针。然后初始化这个堆栈单元。在初始化过程
的最后,你需要填充MajorFunction代码。堆栈单元初始化完成后,就可以调用IoCallDriver函数把IRP发送到设备驱动程序:
PDEVICE_OBJECT DeviceObject; //something gives you this
PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(Irp);
stack->MajorFunction = IRP_MJ_Xxx;
NTSTATUS status = IoCallDriver(DeviceObject, Irp);
IRP中的第一个堆栈单元指针被初始化成指向该堆栈单元之前的堆栈单元,因为I/O堆栈实际上是IO_STACK_LOCATION结构数组,你可以认
为这个指针被初始化为指向一个不存在的“-1”元素,因此当我们要初始化第一个堆栈单元时我们实际需要的是“下一个”堆栈单元。
IoCallDriver将沿着这个堆栈指针找到第0个表项,并提取我们放在那里的主功能代码,在上例中为IRP_MJ_Xxx。然后IoCallDriver函数
将利用DriverObject指针找到设备对象中的MajorFunction表。IoCallDriver将使用主功能代码索引这个表,最后调用找到的地址(派遣
函数)。
你可以把IoCallDriver函数想象为下面代码:
NTSTATUS IoCallDriver(PDEVICE_OBJECT device, PIRP Irp)
{
IoSetNextIrpStackLocation(Irp);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
stack->DeviceObject = device;
ULONG fcn = stack->MajorFunction;
PDRIVER_OBJECT driver = device->DriverObject;
return (*driver->MajorFunction[fcn])(device, Irp);
}
========