[11]Windows内核情景分析---设备驱动
设备驱动
设备栈:从上层到下层的顺序依次是:过滤设备、类设备、过滤设备、小端口设备【过、类、过滤、小端口】
驱动栈:因设备堆栈原因而建立起来的一种堆栈
老式驱动:指不提供AddDevice的驱动,又叫NT式驱动
Wdm驱动:指提供了AddDevice的驱动
驱动初始化:指IO管理器加载驱动后,调用驱动的DriverEntry、AddDevice函数
设备栈中上层设备与下层设备的绑定关系不是一对一,而是一对多。一个设备可以同时绑定到N个下层设备上去,而一个下层设备,也可以同时被N个上层设备绑定,但注意形式上只可被一个上层设备绑定,因为设备对象的AttachedDevice字段指的就是那个形式上绑定在它上面的设备。
相关结构定义:
typedef struct _DRIVER_OBJECT {
CSHORT Type;//本结构的类型
CSHORT Size;//本结构的实际长度
PDEVICE_OBJECT DeviceObject;//设备对象链(第一个设备对象)
ULONG Flags;//驱动标志
//下面两个字段经常用于检测SSDT hook、DispactcRoutine hook等
PVOID DriverStart;//本驱动的sys模块在内存中的起始地址
ULONG DriverSize;//长度
PVOID DriverSection;//实际上是一个LDR_DATA_TABLE_ENTRY*指针,指向它的模块描述符结构
PEXTENDED_DRIVER_EXTENSION DriverExtension;//标准驱动扩展(所有驱动都有一个标准驱动扩准)
UNICODE_STRING DriverName;//“Driver\服务名” 或 “FileSystem\服务名”(也在对象头中保存)
PUNICODE_STRING HardwareDatabase;//注册表硬件配置键路径
struct _FAST_IO_DISPATCH *FastIoDispatch;//用于文件系统的一组快速IO例程
PDRIVER_INITIALIZE DriverInit;//sys文件的oep(一般就是DriverEntry,当然可以是其他名字)
PDRIVER_STARTIO DriverStartIo;//StartIO irp队列机制专用函数
PDRIVER_UNLOAD DriverUnload;//驱动的卸载例程
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];//各irp的派遣函数
} DRIVER_OBJECT, *PDRIVER_OBJECT;
每个驱动对象都有一个标准的驱动扩展,且紧跟在驱动对象后面。
typedef struct _EXTENDED_DRIVER_EXTENSION
{
struct _DRIVER_OBJECT *DriverObject; //所属驱动对象
PDRIVER_ADD_DEVICE AddDevice; //本驱动的AddDevice例程
ULONG Count; //本驱动注册的重初始化例程的历史调用次数
UNICODE_STRING ServiceKeyName; //“Driver\服务名” 或 “FileSystem\服务名”
PIO_CLIENT_EXTENSION ClientDriverExtension;//用户提供的自定义驱动扩展
PFS_FILTER_CALLBACKS FsFilterCallbacks;
} EXTENDED_DRIVER_EXTENSION, *PEXTENDED_DRIVER_EXTENSION;
typedef struct _DEVICE_OBJECT {
CSHORT Type; //本结构的类型
USHORT Size; //本结构的实际长度
LONG ReferenceCount;//引用计数
struct _DRIVER_OBJECT *DriverObject;//所属驱动对象
struct _DEVICE_OBJECT *NextDevice;//在所属驱动的设备链中的下一个设备
struct _DEVICE_OBJECT *AttachedDevice;//绑定的上层设备
struct _IRP *CurrentIrp;//用于StartIO机制,表示当前正在处理中的irp
PIO_TIMER Timer;//一个内置的秒级精度定时器(定时器例程运行在DISPACH_LEVEL)
ULONG Flags;//设备标志
ULONG Characteristics;//设备特征
volatile PVPB Vpb;//卷参数块。文件卷设备、物理卷设备都有一个Vpb,指向关联的卷信息
PVOID DeviceExtension;//关键。自定义设备扩展部分(紧跟在本结构体后面)
DEVICE_TYPE DeviceType;//设备类型,与绑定的下层设备的设备类型相同
CCHAR StackSize;//设备栈中自本设备以下的栈层数(包含本层设备)
union {
LIST_ENTRY ListEntry;//用来挂入全局的‘文件系统驱动cdo链表’
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;//对齐粒度
KDEVICE_QUEUE DeviceQueue;//本设备内置的irp队列,用于异步irp
KDPC Dpc;
ULONG ActiveThreadCount;//用于卷设备,表示当前打开线程计数
PSECURITY_DESCRIPTOR SecurityDescriptor;//SD安全描述符
KEVENT DeviceLock;//
USHORT SectorSize;//扇区大小,用于卷设备(磁盘卷设备一般为512B,关盘卷设备为2048B)
//内置的标准设备扩展部分(紧跟在自定义设备扩展后面)
struct _EXTENDED_DEVOBJ_EXTENSION *DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;
typedef struct _EXTENDED_DEVOBJ_EXTENSION
{
CSHORT Type;//结构类型
USHORT Size;//总设备扩展(自定义设备扩展+标准设备扩展)的长度
PDEVICE_OBJECT DeviceObject;//所属设备对象
ULONG PowerFlags;
struct DEVICE_OBJECT_POWER_EXTENSION *Dope;
ULONG ExtensionFlags;
struct _DEVICE_NODE *DeviceNode;//设备节点(只有最底层的pdo才有设备节点)
PDEVICE_OBJECT AttachedTo;//关键。最近一次绑到的下层设备
LONG StartIoCount;
LONG StartIoKey;
ULONG StartIoFlags;
struct _VPB *Vpb;//文件卷参数块
} EXTENDED_DEVOBJ_EXTENSION, *PEXTENDED_DEVOBJ_EXTENSION;
注意设备扩展由两部分组成:自定义设备扩展、标准设备扩展,共同构成设备扩展信息。
typedef struct _IRP {
CSHORT Type;//结构类型
USHORT Size;//本irp的实际分配长度(包含后面的栈空间数组)
struct _MDL *MdlAddress;//关联的MDL链表
ULONG Flags;//irp标志
union {
//(一个irp可以分成n个子irp发给下层的驱动)
struct _IRP *MasterIrp;//当该irp是子irp时表示所属的主irp
volatile LONG IrpCount;//当该irp是主irp时,表示子irp个数
PVOID SystemBuffer;//关联的系统缓冲
} AssociatedIrp;
LIST_ENTRY ThreadListEntry;//用来挂入线程的未决(指Pending)irp链表
IO_STATUS_BLOCK IoStatus;//该irp的完成结果(内置的)
KPROCESSOR_MODE RequestorMode;//表示是来自用户模式还是内核模式的irp请求
BOOLEAN PendingReturned;//表示下层设备当初处理该irp时是否是Pending异步方式处理的
CHAR StackCount;//本irp的栈层数,也即本结构体后面的数组元素个数
CHAR CurrentLocation;//从上往下数,当前的栈空间位置序号(每个栈层就是一个栈空间)
BOOLEAN Cancel;//表示用户是否发出了取消该irp的命令
KIRQL CancelIrql;//取消时的irql
CCHAR ApcEnvironment;//该irp当初分配时,线程的apc状态(挂靠态/常态)
UCHAR AllocationFlags;//用于保存当初分配该irp结构时的分配标志
PIO_STATUS_BLOCK UserIosb;//可为空。表示用户自己提供的一个结构,用来将完成结果返回给用户
PKEVENT UserEvent;//可为空。表示用户自己提供的irp完成事件
union {
struct {
_ANONYMOUS_UNION union {
PIO_APC_ROUTINE UserApcRoutine;//用户提供的APC例程
PVOID IssuingProcess;
} DUMMYUNIONNAME;
PVOID UserApcContext;//APC例程的参数
} AsynchronousParameters;
LARGE_INTEGER AllocationSize;//本结构当初分配时总的分配长度(包含后面的数组)
} Overlay;
volatile PDRIVER_CANCEL CancelRoutine;//本irp关联的取消例程(取消时将执行这个函数)
PVOID UserBuffer;//关联的用户空间缓冲区地址(直接使用可能不安全)
union {
struct {
_ANONYMOUS_UNION union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry;//用来挂入设备对象内置的irp队列
_ANONYMOUS_STRUCT struct {
PVOID DriverContext[4];
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
PETHREAD Thread;//该irp的发起者线程
PCHAR AuxiliaryBuffer;//关联的辅助缓冲
_ANONYMOUS_STRUCT struct {
LIST_ENTRY ListEntry;
_ANONYMOUS_UNION union {
//这个字段与CurrentLocation的作用一样,只是一个表示指针,一个表示序号
struct _IO_STACK_LOCATION *CurrentStackLocation;//当前的栈空间位置
ULONG PacketType;
} DUMMYUNIONNAME;
} DUMMYSTRUCTNAME;
//irp本来是发给设备的,但是我们也可以看做是发给文件对象(各栈层可能有变动)
struct _FILE_OBJECT *OriginalFileObject;//本irp最初发往的文件对象
} Overlay;
KAPC Apc;//与本次irp相关的APC例程
PVOID CompletionKey;
} Tail;
} IRP, *PIRP;
每个irp结构体后面紧跟一个数组,那就是irp的设备栈。设备栈中的每一层叫栈层,数组中每个元素的类型为IO_SATCK_LOCATION,表示的就是一个栈空间、一个栈层。实际上,我们可以把IRP结构理解为irp的头部,后面的额数组理解为irp的身体。一个irp就是由一个irp头部和一个栈空间数组组成。Irp头部中的CurrentLocation字段记录了该irp在各层驱动的处理进度。该数组中,第一个元素表示设备栈的栈底,最后一个元素表示栈顶。每当将irp转发到下层设备时,irp头部中的CurrentLocation字段递减,而不是递增,就是因为这个缘故。
每个栈空间用来主要用来记录该irp的主功能码,次功能码,参数,以及各层的完成函数,因为在应用程序一次宏观的irp请求中,各层的请求可能不尽相同。
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;//当irp下发到本层时的主功能码(下发过程一般维持不变)
UCHAR MinorFunction; //当irp下发到本层时的次功能码(下发过程一般维持不变)
UCHAR Flags;//本栈层的标志
UCHAR Control;//控制标志
union {
…
struct {
ULONG Length;//读请求的长度
ULONG POINTER_ALIGNMENT Key;
LARGE_INTEGER ByteOffset;//读请求的文件偏移位置
} Read;
struct {
ULONG Length; //写请求的长度
ULONG POINTER_ALIGNMENT Key;
LARGE_INTEGER ByteOffset; //写请求的文件偏移位置
} Write;
…
} Parameters;//一个复杂的联合体,对应各种irp的参数
PDEVICE_OBJECT DeviceObject;//本栈层的设备对象
PFILE_OBJECT FileObject;//关联的文件对象
//当本栈层完成该层的irp后,会回到上层调用上层的完成例程。这就是为什么在irp尚未完成的情况下,贸然卸载上层驱动,会导致下层驱动调用上层的完成例程时因为完成例程无效而蓝屏崩溃。
PIO_COMPLETION_ROUTINE CompletionRoutine;//记录着上层的完成例程
PVOID Context;//完成例程的参数
} IO_STACK_LOCATION, *PIO_STACK_LOCATION;
下面的函数用于创建一个设备对象
NTSTATUS
IoCreateDevice(IN PDRIVER_OBJECT DriverObject,//指定驱动(可以是其它第三方驱动)
IN ULONG DeviceExtensionSize,//自定义设备扩展的大小
IN PUNICODE_STRING DeviceName,//设备对象的名字
IN DEVICE_TYPE DeviceType,//设备类型
IN ULONG DeviceCharacteristics,//设备特征
IN BOOLEAN Exclusive,//同一时刻是否只能被一个进程独占打开
OUT PDEVICE_OBJECT *DeviceObject)//返回
{
WCHAR AutoNameBuffer[20];
PAGED_CODE();//确保当前irql<=PASSIVE_LEVEL
//根据设备数量自动生成的设备名称
if (DeviceCharacteristics & FILE_AUTOGENERATED_DEVICE_NAME)
{
swprintf(AutoNameBuffer,L"\\Device\\%08lx",
InterlockedIncrementUL(&IopDeviceObjectNumber));
RtlInitUnicodeString(&AutoName, AutoNameBuffer);
DeviceName = &AutoName;
}
InitializeObjectAttributes(&ObjectAttributes,DeviceName,OBJ_KERNEL_HANDLE,NULL,NULL);
//if 同一时刻只能被一个进程独占打开
if (Exclusive) ObjectAttributes.Attributes |= OBJ_EXCLUSIVE;
//设备对象永久存于对象目录中,直至对象被完全销毁
if (DeviceName) ObjectAttributes.Attributes |= OBJ_PERMANENT;
//向上对齐8B后的自定义设备扩展大小
AlignedDeviceExtensionSize = (DeviceExtensionSize + 7) &~ 7;
//设备对象体的总长
TotalSize = sizeof(DEVICE_OBJECT) + //设备对象本身
AlignedDeviceExtensionSize + //自定义设备扩展部分
sizeof(EXTENDED_DEVOBJ_EXTENSION); //标准设备扩展部分
*DeviceObject = NULL;
//创建分配设备对象(在非分页池中)
Status = ObCreateObject(KernelMode,IoDeviceObjectType,&ObjectAttributes,KernelMode,NULL,
TotalSize,0,0, (PVOID*)&CreatedDeviceObject);
RtlZeroMemory(CreatedDeviceObject, TotalSize);
CreatedDeviceObject->Type = IO_TYPE_DEVICE;//固定
//这个长度值是设备对象自身的长度加上自定义设备扩展的长度
CreatedDeviceObject->Size = sizeof(DEVICE_OBJECT) + (USHORT)DeviceExtensionSize;
//取得标准设备扩展部分的位置
DeviceObjectExtension = ((ULONG)(CreatedDeviceObject + 1) +AlignedDeviceExtensionSize);
CreatedDeviceObject->DeviceObjectExtension = DeviceObjectExtension;//记录位置
DeviceObjectExtension->Type = IO_TYPE_DEVICE_OBJECT_EXTENSION;//固定
DeviceObjectExtension->Size = 0;
PoInitializeDeviceObject(DeviceObjectExtension);//初始化电源管理器
DeviceObjectExtension->DeviceObject = CreatedDeviceObject;
CreatedDeviceObject->DeviceType = DeviceType;
CreatedDeviceObject->Characteristics = DeviceCharacteristics;
//记录自定义设备扩展部分的位置
CreatedDeviceObject->DeviceExtension = DeviceExtensionSize ? CreatedDeviceObject + 1 :NULL;
CreatedDeviceObject->StackSize = 1;//注意:设备对象初始的栈层数都为1
CreatedDeviceObject->AlignmentRequirement = 0;
CreatedDeviceObject->Flags = DO_DEVICE_INITIALIZING;//标记设备对象正在初始化过程中
if (Exclusive) CreatedDeviceObject->Flags |= DO_EXCLUSIVE;
if (DeviceName) CreatedDeviceObject->Flags |= DO_DEVICE_HAS_NAME;
//物理卷设备在创建时都会自动分配一个vpb(后面我们会看到,文件卷设备在绑定物理卷设备后,也会记录对应的vpb)
if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM) ||
(CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE))
{
Status = IopCreateVpb(CreatedDeviceObject);
KeInitializeEvent(&CreatedDeviceObject->DeviceLock,SynchronizationEvent,TRUE);
}
//磁盘卷设备的扇区大小默认512B,光盘为2048B
switch (DeviceType)
{
case FILE_DEVICE_DISK_FILE_SYSTEM:
case FILE_DEVICE_DISK:
case FILE_DEVICE_VIRTUAL_DISK:
CreatedDeviceObject->SectorSize = 512;
break;
case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
CreatedDeviceObject->SectorSize = 2048;
}
//文件系统中cdo和文件卷都需要挂入相应的全局的链表中
if ((CreatedDeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_FILE_SYSTEM) || //文件卷
(CreatedDeviceObject->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) || //cdo
(CreatedDeviceObject->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM)) //cdo
{
InitializeListHead(&CreatedDeviceObject->Queue.ListEntry);
}
Else//其他的一般设备都有一个DeviceQueue表示irp队列。
{
KeInitializeDeviceQueue(&CreatedDeviceObject->DeviceQueue);
}
//插入对象目录
Status = ObInsertObject(CreatedDeviceObject,NULL,FILE_READ_DATA | FILE_WRITE_DATA,
1, (PVOID*)&CreatedDeviceObject,&TempHandle);
ObReferenceObject(DriverObject);
ASSERT((DriverObject->Flags & DRVO_UNLOAD_INVOKED) == 0);//确保驱动没在卸载当中
CreatedDeviceObject->DriverObject = DriverObject;
IopEditDeviceList(DriverObject, CreatedDeviceObject, IopAdd);//挂入驱动对象的设备链表头
if (CreatedDeviceObject->Vpb) PoVolumeDevice(CreatedDeviceObject);//电源管理相关
ObCloseHandle(TempHandle, KernelMode);//关闭临时的设备句柄(因为我们不需要这个句柄)
*DeviceObject = CreatedDeviceObject;//返回
return STATUS_SUCCESS;
}
设备对象是用来接收处理irp的,可以把一个irp发给任意设备对象。
#define IoCallDriver IofCallDriver
NTSTATUS FASTCALL //注意这个函数,是在上层驱动的上下文中调用
IofCallDriver(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
PDRIVER_OBJECT DriverObject;
PIO_STACK_LOCATION StackPtr;
DriverObject = DeviceObject->DriverObject;
Irp->CurrentLocation--;//当前栈层位置向下滑动,指向下层栈空间
if (Irp->CurrentLocation <= 0)//CurrentLocation是序号,不是索引
KeBugCheckEx(NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR)Irp, 0, 0, 0);//蓝屏
StackPtr = IoGetNextIrpStackLocation(Irp);
Irp->Tail.Overlay.CurrentStackLocation = StackPtr;//当前栈空间指向了下层
StackPtr->DeviceObject = DeviceObject;//记录好下层的设备
//关键。调用下层驱动对应irp的派遣函数
return DriverObject->MajorFunction[StackPtr->MajorFunction](DeviceObject,Irp);
}
如上,可以看到,上层驱动在调用这个函数,将irp发到下层设备时,会自动在内部将当前栈空间位置向下滑动一个位置,指向下层的栈空间。
ddk提供了一个宏,用来移动irp的栈空间位置
#define IoSetNextIrpStackLocation(Irp) \
{ \
Irp->CurrentLocation--;\ //序号向下滑动一项
Irp->Tail.Overlay.CurrentStackLocation--;\ //数组元素指针也向下滑动一项
}
下面的宏实际上获取的就是当前栈空间的位置
#define IoGetCurrentIrpStackLocation(irp) irp->Tail.Overlay.CurrentStackLocation
下面的宏实际上获取的就是下层栈空间的位置
#define IoGetNextIrpStackLocation(irp) irp->Tail.Overlay.CurrentStackLocation – 1
应用程序常常调用DeviceIoControl这个API与设备驱动通信,它实际上调研NtDeviceIoControlFile这个系统服务
NTSTATUS
NtDeviceIoControlFile(IN HANDLE DeviceHandle,//实际上应该叫hFile
IN HANDLE Event OPTIONAL,//用户自己提供的irp完成事件
IN PIO_APC_ROUTINE UserApcRoutine OPTIONAL,//用户提供的APC例程
IN PVOID UserApcContext OPTIONAL,//APC参数
OUT PIO_STATUS_BLOCK IoStatusBlock,//用户自己提供的Io状态块
IN ULONG IoControlCode,
IN PVOID InputBuffer,
IN ULONG InputBufferLength OPTIONAL,
OUT PVOID OutputBuffer,
IN ULONG OutputBufferLength OPTIONAL)
{
return IopDeviceFsIoControl(DeviceHandle,Event,UserApcRoutine,UserApcContext,
IoStatusBlock,IoControlCode,InputBuffer,InputBufferLength,
OutputBuffer,OutputBufferLength,
TRUE);//表示是普通的DeviceIoControl请求
}
必须注意DeviceHandle参数应该叫做文件句柄而不是设备句柄。在Windows内核中,几乎从来没有“设备句柄”这种概念。虽然,可以为设备对象创建一个设备句柄,但很少这么做,因为设备句柄几乎没有什么作用。相反,应用程序一般都是调用CreateFile(它内部会打开设备对象,创建文件对象,返回文件对象的句柄),以后,就可以用这个文件句柄,找到对应的文件对象及其设备对象去访问设备了。
上面函数在调用时,用户可以提供一个Event和IoStatusBlock,当irp完成时,就会触发用户提供的这个事件,然后返回完成结果到用户提供的IoStatusBlock中。用户还可以提供apc例程,irp在完成后会自动将这个apc插入到线程的用户apc队列中,然后返回用户空间时执行这个apc。
NTSTATUS
IopDeviceFsIoControl(IN HANDLE DeviceHandle,IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE UserApcRoutine OPTIONAL,
IN PVOID UserApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,IN ULONG IoControlCode,
IN PVOID InputBuffer,IN ULONG InputBufferLength OPTIONAL,
OUT PVOID OutputBuffer,IN ULONG OutputBufferLength OPTIONAL,
IN BOOLEAN IsDevIoCtl)//表示是否是普通的DeviceIoControl
{
PKEVENT EventObject = NULL;
BOOLEAN LockedForSynch = FALSE;
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
AccessType = IO_METHOD_FROM_CTL_CODE(IoControlCode);//提取io控制码里面的io方式
if (PreviousMode != KernelMode) 检查用户空间地址是否可读可写,略
//获得文件句柄对应的文件对象
Status = ObReferenceObjectByHandle(DeviceHandle,0,IoFileObjectType,PreviousMode,
(PVOID*)&FileObject,&HandleInformation);
//Io完成端口不支持APC
if ((FileObject->CompletionContext) && (UserApcRoutine))
{
ObDereferenceObject(FileObject);
return STATUS_INVALID_PARAMETER;
}
if (PreviousMode != KernelMode)
{
DesiredAccess = (ACCESS_MASK)((IoControlCode >> 14) & 3);//提取io控制码中的访问权限
//检查访问权限
if ((DesiredAccess != FILE_ANY_ACCESS) &&
(HandleInformation.GrantedAccess & DesiredAccess) != DesiredAccess)
{
ObDereferenceObject(FileObject);
return STATUS_ACCESS_DENIED;
}
}
if (Event)//将用户提供的irp完成事件初始化为无信号状态
{
Status = ObReferenceObjectByHandle(Event,EVENT_MODIFY_STATE,ExEventObjectType,
PreviousMode, (PVOID*)&EventObject,NULL);
KeClearEvent(EventObject);
}
if (FileObject->Flags & FO_SYNCHRONOUS_IO)
{
IopLockFileObject(FileObject);
LockedForSynch = TRUE;//标记当初打开设备时,是同步方式
}
//一般的设备都是直接打开的,上面没有绑定文件卷设备,若是这样,获取栈顶的设备
if (FileObject->Flags & FO_DIRECT_DEVICE_OPEN)
DeviceObject = IoGetAttachedDevice(FileObject->DeviceObject);
Else //物理卷设备可能不是直接打开的,上面绑定有文件卷设备,若是这样,就获取栈顶的文件卷
DeviceObject = IoGetRelatedDeviceObject(FileObject);
//这儿要强调下,为什么设备栈栈顶的过滤设备能最先得到irp?根本原因就是io管理器在将irp发给设备时,并不是直接发给当初CreateFile打开的额那个设备,而是要先调用IoGetAttachedDevice/ IoGetRelatedDeviceObject这两个关键函数获得栈顶的设备,然后将irp发给它。
KeClearEvent(&FileObject->Event);//文件对象内部的那个内置事件也初始化为无信号状态
//关键。由IO管理器分配、生成一个irp(注意初始时,该irp的当前栈空间位置在栈顶的上面)
//注意irp的StackSize就是栈顶设备的StackSize
Irp = IoAllocateIrp(DeviceObject->StackSize, FALSE);
//下面是irp的初始化,这部分可由我们自行完成(前提是必须熟悉irp中各个字段的含义)
Irp->UserIosb = IoStatusBlock;//记录用户自提供的io状态块
Irp->UserEvent = EventObject;//记录用户自提供的的irp完成事件
Irp->Overlay.AsynchronousParameters.UserApcRoutine = UserApcRoutine;//记录用户提供的APC
Irp->Overlay.AsynchronousParameters.UserApcContext = UserApcContext;
Irp->Cancel = FALSE;//初始时尚未被用户取消
Irp->CancelRoutine = NULL;//初始时无取消例程
Irp->PendingReturned = FALSE;//下层尚未Pending返回
Irp->RequestorMode = PreviousMode;//来自指定模式的irp请求
Irp->MdlAddress = NULL;//由具体的io方式决定
Irp->AssociatedIrp.SystemBuffer = NULL; //由具体的io方式决定
Irp->Flags = 0;
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
Irp->Tail.Overlay.OriginalFileObject = FileObject;//最初发往的文件对象
Irp->Tail.Overlay.Thread = PsGetCurrentThread();//该irp的发起者线程
//此时的下层栈空间位置也即栈顶位置(指第一个栈空间)
//在下发irp前,先构造好下层栈空间中的功能码和参数
StackPtr = IoGetNextIrpStackLocation(Irp);
StackPtr->FileObject = FileObject;
StackPtr->MajorFunction = IsDevIoCtl ?
IRP_MJ_DEVICE_CONTROL ://普通的控制请求irp
IRP_MJ_FILE_SYSTEM_CONTROL;//专用于文件系统cdo的控制请求irp
StackPtr->MinorFunction = 0;
StackPtr->Control = 0;
StackPtr->Flags = 0;
StackPtr->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
StackPtr->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
StackPtr->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
StackPtr->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
switch (AccessType)//检查该控制请求irp的io方式
{
case METHOD_BUFFERED://若是缓冲方式,就由系统分配一个内核缓冲
_SEH2_TRY
{
BufferLength = max(InputBufferLength , OutputBufferLength);
if (BufferLength)
{
Irp->AssociatedIrp.SystemBuffer =
ExAllocatePoolWithTag(NonPagedPool,BufferLength,TAG_SYS_BUF);
if (InputBuffer)
{
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,InputBuffer,
InputBufferLength);
}
//标记该irp是缓冲io方式,并且完成时需要释放内核缓冲
Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;
if (OutputBuffer) Irp->Flags |= IRP_INPUT_OPERATION;
Irp->UserBuffer = OutputBuffer;
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
IopCleanupAfterException(FileObject, Irp, EventObject, NULL);
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
break;
case METHOD_IN_DIRECT:
case METHOD_OUT_DIRECT:
_SEH2_TRY
{
if ((InputBufferLength) && (InputBuffer))//输入缓冲固定不能使用直接io方式
{
Irp->AssociatedIrp.SystemBuffer =
ExAllocatePoolWithTag(NonPagedPool,InputBufferLength,TAG_SYS_BUF);
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer,InputBuffer,
InputBufferLength);
Irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;
}
if (OutputBuffer)//输出缓冲可使用直接io方式(mdl方式)
{
Irp->MdlAddress = IoAllocateMdl(OutputBuffer,OutputBufferLength,
FALSE,FALSE,Irp);
MmProbeAndLockPages(Irp->MdlAddress,PreviousMode,
(AccessType == METHOD_IN_DIRECT) ?IoReadAccess : IoWriteAccess);
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
IopCleanupAfterException(FileObject, Irp, EventObject, NULL);
_SEH2_YIELD(return _SEH2_GetExceptionCode());
}
_SEH2_END;
break;
case METHOD_NEITHER:
Irp->UserBuffer = OutputBuffer;
StackPtr->Parameters.DeviceIoControl.Type3InputBuffer = InputBuffer;//第三类方式
}
//发给文件系统cdo设备的控制请求都会延迟完成
Irp->Flags |= (!IsDevIoCtl) ? IRP_DEFER_IO_COMPLETION : 0;
//发送该irp,异步返回或同步等待完成
return IopPerformSynchronousRequest(DeviceObject,Irp,FileObject,!IsDevIoCtl,
PreviousMode,LockedForSynch,IopOtherTransfer);
}
//下面的函数用来获取设备栈栈顶的设备
PDEVICE_OBJECT
IoGetAttachedDevice(PDEVICE_OBJECT DeviceObject)
{
while (DeviceObject->AttachedDevice)
DeviceObject = DeviceObject->AttachedDevice;
return DeviceObject;
}
下面的函数用来获取文件对象关联设备的设备栈栈顶的设备(用于文件系统)
PDEVICE_OBJECT
IoGetRelatedDeviceObject(IN PFILE_OBJECT FileObject)
{
PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject;//先获得当初打开的设备
if ((FileObject->Vpb) && (FileObject->Vpb->DeviceObject))//if打开卷设备产生的文件对象
{
ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));
DeviceObject = FileObject->Vpb->DeviceObject;//文件卷设备
}
//最典型,若是打开物理卷设备产生的文件对象,并且上面绑定有文件卷设备
else if (!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN) &&
(FileObject->DeviceObject->Vpb) &&
(FileObject->DeviceObject->Vpb->DeviceObject))
{
DeviceObject = FileObject->DeviceObject->Vpb->DeviceObject;//文件卷设备
}
else
DeviceObject = FileObject->DeviceObject;//当初直接打开的物理卷设备或其他普通设备
if (DeviceObject->AttachedDevice)
{
DeviceObject = IoGetAttachedDevice(DeviceObject);//目标设备栈的栈顶设备
}
return DeviceObject;
}
上面这个函数一般用于文件系统。物理卷设备往往不是直接打开的,它上面一般都会绑定着一个文件卷设备。但是文件卷与物理卷之间的绑定关系没有形式上绑定(也即不是通过设备对象的AttachedDevice字段来维护绑定关系),而是通过一个vpb来记录他们之间的绑定关系。物理卷设备所在的设备栈与文件卷设备所在的设备栈是两个不同的设备栈。这样,IoGetAttachedDevice就没法获得文件卷设备栈的栈顶对象,需要通过IoGetRelatedDeviceObject这个专用的函数来获得绑定了物理卷设备的最上面的文件卷设备。
所有irp都是由io管理器分配生成的
PIRP
IoAllocateIrp(IN CCHAR StackSize,//指该irp包含的栈层数
IN BOOLEAN ChargeQuota)//指是否计较内存配额浪费,一般传FALSE
{
PIRP Irp = NULL;
USHORT Size;//该irp的实际需要分配的长度
PKPRCB Prcb;
UCHAR Flags = 0;
PNPAGED_LOOKASIDE_LIST List = NULL;
PP_NPAGED_LOOKASIDE_NUMBER ListType = LookasideSmallIrpList;//irp容器类型
Size = IoSizeOfIrp(StackSize);//获得irp的有效长度
if (ChargeQuota) Flags |= IRP_QUOTA_CHARGED;
//如果栈层数小于等于8又不计较配额浪费,那么就从预置的irp容器分配
if ((StackSize <= 8) && (ChargeQuota == FALSE))
{
Flags = IRP_ALLOCATED_FIXED_SIZE;//标记为分配的是固定长度
if (StackSize != 1)
{
Size = IoSizeOfIrp(8);//对齐8个栈层大小
ListType = LookasideLargeIrpList;//改用8号irp容器
}
Prcb = KeGetCurrentPrcb();
//先尝试从该容器的P链表中分配出一个irp
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
List->L.TotalAllocates++;//递增该链表总的分配请求计数
Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);//分配
if (!Irp)//if 分配失败
{
List->L.AllocateMisses++;//递增该链表的分配失败计数
//再尝试从该容器的L链表中分配出一个irp
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
List->L.TotalAllocates++;//递增该链表总的分配请求计数
Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead); //分配
}
}
if (!Irp)//如果仍然分配失败或者尚未分配
{
//if 从L链表中分配失败
if (Flags & IRP_ALLOCATED_FIXED_SIZE) List->L.AllocateMisses++;//递增分配失败计数
Irp = ExAllocatePoolWithTag(NonPagedPool, Size, TAG_IRP);//直接从非分页池中分配
}
Else Flags &= ~IRP_QUOTA_CHARGED;
//分配完irp后,做一些基本的初始化
IoInitializeIrp(Irp, Size, StackSize);
Irp->AllocationFlags = Flags;//记录分配标志
return Irp;
}
#define IoSizeOfIrp(_StackSize) sizeof(IRP) + _StackSize * sizeof(IO_STACK_LOCATION)
如上,由于irp频繁分配,所以内核中准备了两个irp容器。一个单层的irp容器、一个8层的irp容器。当irp栈层数小于等于8时,并且不计较配额浪费时,就从容器中分配irp结构,这样,能加快分配速度。
Irp分配后,系统内部会做一些基本的初始化,我们看:
VOID
IoInitializeIrp(IN PIRP Irp,
IN USHORT PacketSize,//实际分配的长度
IN CCHAR StackSize)//栈层数
{
RtlZeroMemory(Irp, PacketSize);//整个结构全部清0
Irp->Type = IO_TYPE_IRP;
Irp->Size = PacketSize;
Irp->StackCount = StackSize;
Irp->CurrentLocation = StackSize + 1;//初始栈空间位置在栈顶的上面
Irp->Tail.Overlay.CurrentStackLocation = (PIO_STACK_LOCATION)(Irp + 1) + StackSize;
Irp->ApcEnvironment = KeGetCurrentThread()->ApcStateIndex;
InitializeListHead(&Irp->ThreadListEntry);//用来挂入线程的irp链表
}
IopDeviceFsIoControl函数内部会调用下面的函数来将构造好的irp发到目标设备,并且异步返回或者同步等待处理完毕后再返回。
NTSTATUS
IopPerformSynchronousRequest(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PFILE_OBJECT FileObject,
IN BOOLEAN Deferred,
IN KPROCESSOR_MODE PreviousMode,
IN BOOLEAN SynchIo,//关键参数
IN IOP_TRANSFER_TYPE TransferType)
{
NTSTATUS Status;
PKNORMAL_ROUTINE NormalRoutine;
PVOID NormalContext;
KIRQL OldIrql;
PAGED_CODE();
IopQueueIrpToThread(Irp);//将irp挂入线程的pending irp链表中
IopUpdateOperationCount(TransferType);
Status = IoCallDriver(DeviceObject, Irp);//将irp发给指定的设备
if (Deferred)//很多irp请求都需要延迟完成
{
if (Status != STATUS_PENDING)
{
ASSERT(!Irp->PendingReturned);
KeRaiseIrql(APC_LEVEL, &OldIrql);
//IoCompleteRequest的后半工作
IopCompleteRequest(&Irp->Tail.Apc,&NormalRoutine,&NormalContext,
(PVOID*)&FileObject,&NormalContext);
KeLowerIrql(OldIrql);
}
}
//if文件对象当初在CreateFile打开设备时,声明的是同步IO方式,以后的每次irp就一直等待完成后才返回退出函数
if (SynchIo)
{
if (Status == STATUS_PENDING)//即使下层驱动是异步完成的,也要一直等到其完成后再返回
{
Status = KeWaitForSingleObject(&FileObject->Event,//等待文件对象的内置完成事件
Executive,PreviousMode,
(FileObject->Flags & FO_ALERTABLE_IO),//Alertable
NULL);
//如果是被用户apc或者强制唤醒要求给强制唤醒的,就夭折该次irp
if ((Status == STATUS_ALERTED) || (Status == STATUS_USER_APC))
IopAbortInterruptedIrp(&FileObject->Event, Irp);
Status = FileObject->FinalStatus;
}
IopUnlockFileObject(FileObject);
}
return Status;
}
如上,这个函数将irp发给指定设备,然后立即返回或者等到其完成后再返回
下面这个函数用来将irp挂入线程的pending irp链表(当线程终止时,会自动扫描它的pending irp链表,调用IoCancelIrp一一给予取消)
VOID IopQueueIrpToThread(IN PIRP Irp)
{
KIRQL OldIrql;
KeRaiseIrql(APC_LEVEL, &OldIrql);
InsertHeadList(&Irp->Tail.Overlay.Thread->IrpList, &Irp->ThreadListEntry);
KeLowerIrql(OldIrql);
}
BOOLEAN IoCancelIrp(IN PIRP Irp)
{
KIRQL OldIrql;
PDRIVER_CANCEL CancelRoutine;
IoAcquireCancelSpinLock(&OldIrql);//锁定
Irp->Cancel = TRUE;//标记该irp已被用户发起了取消请求
CancelRoutine = (PVOID)IoSetCancelRoutine(Irp, NULL);//获得该irp的取消例程
if (CancelRoutine)//if 那个irp设有取消例程,就调用
{
Irp->CancelIrql = OldIrql;
CancelRoutine(IoGetCurrentIrpStackLocation(Irp)->DeviceObject, Irp);//调用取消例程
return TRUE;
}
IoReleaseCancelSpinLock(OldIrql);
return FALSE;
}
设备的绑定:
NTSTATUS
IoAttachDevice(PDEVICE_OBJECT SourceDevice,//我们的设备
PUNICODE_STRING TargetDeviceName,//要绑定的目标设备的名字
PDEVICE_OBJECT *AttachedDevice)//返回实际绑定的原栈顶设备
{
NTSTATUS Status;
PFILE_OBJECT FileObject = NULL;
PDEVICE_OBJECT TargetDevice = NULL;
//先根据名称获得目标设备对象
Status = IopGetDeviceObjectPointer(TargetDeviceName,FILE_READ_ATTRIBUTES,
&FileObject,&TargetDevice,IO_ATTACH_DEVICE_API);
if (!NT_SUCCESS(Status)) return Status;
Status = IoAttachDeviceToDeviceStackSafe(SourceDevice,TargetDevice,AttachedDevice);
ObDereferenceObject(FileObject);
return Status;
}
//对于设备对象,除了可以使用ObReferenceObjectByName根据名称得到对象指针外,也可使用下面的函数来获得设备对象的指针
NTSTATUS
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,IN ACCESS_MASK DesiredAccess,
OUT PFILE_OBJECT *FileObject,OUT PDEVICE_OBJECT *DeviceObject,
IN ULONG AttachFlag)
{
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK StatusBlock;
PFILE_OBJECT LocalFileObject;
HANDLE FileHandle;
NTSTATUS Status;
InitializeObjectAttributes(&ObjectAttributes,ObjectName,OBJ_KERNEL_HANDLE,NULL,NULL);
//先打开设备,给那个设备发送一个IRP_MJ_CREATE
Status = ZwOpenFile(&FileHandle,DesiredAccess,&ObjectAttributes,&StatusBlock,
0,FILE_NON_DIRECTORY_FILE | AttachFlag);
if (!NT_SUCCESS(Status)) return Status;
Status = ObReferenceObjectByHandle(FileHandle,0,IoFileObjectType,KernelMode,
(PVOID*)&LocalFileObject,NULL);
if (NT_SUCCESS(Status))
{
*DeviceObject = IoGetRelatedDeviceObject(LocalFileObject);//这个函数已看过
*FileObject = LocalFileObject;
}
ZwClose(FileHandle);//关闭文件句柄,内部会发送IRP_MJ_CLEANUP
return Status;
}
IoAttachDevice实质上调用的是IoAttachDeviceToDeviceStackSafe,我们看:
NTSTATUS
IoAttachDeviceToDeviceStackSafe(IN PDEVICE_OBJECT SourceDevice,
IN PDEVICE_OBJECT TargetDevice,
IN OUT PDEVICE_OBJECT *AttachedToDeviceObject)
{
if (!IopAttachDeviceToDeviceStackSafe(SourceDevice,TargetDevice,AttachedToDeviceObject))
return STATUS_NO_SUCH_DEVICE;
return STATUS_SUCCESS;
}
继续看:
PDEVICE_OBJECT
IopAttachDeviceToDeviceStackSafe(IN PDEVICE_OBJECT SourceDevice,//我们的设备
IN PDEVICE_OBJECT TargetDevice,//要绑定到的目标设备
OUT PDEVICE_OBJECT *AttachedToDeviceObject OPTIONAL)
{
//获得我们设备的标准设备扩展
PEXTENDED_DEVOBJ_EXTENSION SourceDeviceExtension = IoGetDevObjExtension(SourceDevice);
PDEVICE_OBJECT AttachedDevice = IoGetAttachedDevice(TargetDevice);//获取栈顶的设备
//检查栈顶设备的当前状态是否可以被绑定
if ((AttachedDevice->Flags & DO_DEVICE_INITIALIZING) ||
(IoGetDevObjExtension(AttachedDevice)->ExtensionFlags &
(DOE_UNLOAD_PENDING | DOE_DELETE_PENDING | DOE_REMOVE_PENDING | DOE_REMOVE_PROCESSED)))
{
AttachedDevice = NULL;
}
Else //绑定到目标设备所在设备栈的栈顶设备
{
AttachedDevice->AttachedDevice = SourceDevice;//关键。原栈顶设备记录绑定到它的设备
SourceDevice->StackSize = AttachedDevice->StackSize + 1;//绑定后自动确定StackSize
SourceDevice->AlignmentRequirement = AttachedDevice->AlignmentRequirement;
SourceDevice->SectorSize = AttachedDevice->SectorSize;//绑定后自动确定扇区大小
if (IoGetDevObjExtension(AttachedDevice)->ExtensionFlags & DOE_START_PENDING)
{
//传播标志
IoGetDevObjExtension(SourceDevice)->ExtensionFlags |= DOE_START_PENDING;
}
//关键。在标准设备扩展中也会记录实际绑定到的下层设备(不过我们一般是在自定义设备扩展中记录实际绑到的下层设备)。不过由于一个设备可以同时绑定到N个下层设备,所以这个隐藏的AttachedTo字段记录的是最近一次绑到的下层设备
SourceDeviceExtension->AttachedTo = AttachedDevice;
}
if (AttachedToDeviceObject) *AttachedToDeviceObject = AttachedDevice;//返回
return AttachedDevice;
}
如上,这个函数将我们的设备绑定到目标设备(其实是绑定到目标设备所在设备栈的栈顶设备),绑定过程中,系统会自动确定我们设备的StackSize,并将实际绑到的设备记录在隐藏字段AttachedTo中。
如果知道了两个设备的指针,可以直接使用下面的函数进行绑定,不过,这个函数有一个缺点,他不会返回实际绑到的下层设备。
NTSTATUS
IoAttachDeviceByPointer(IN PDEVICE_OBJECT SourceDevice,IN PDEVICE_OBJECT TargetDevice)
{
PDEVICE_OBJECT AttachedDevice;
NTSTATUS Status = STATUS_SUCCESS;
AttachedDevice = IoAttachDeviceToDeviceStack(SourceDevice, TargetDevice);
if (!AttachedDevice) Status = STATUS_NO_SUCH_DEVICE;
return Status;
}
下面的函数可以解决这个缺点,返回实际绑到的设备,若返回NULL,则表示绑定失败
PDEVICE_OBJECT
IoAttachDeviceToDeviceStack(IN PDEVICE_OBJECT SourceDevice,IN PDEVICE_OBJECT TargetDevice)
{
return IopAttachDeviceToDeviceStackSafe(SourceDevice,TargetDevice,NULL);
}
驱动的加载过程:
Windows系统中共分4种类型的驱动
#define SERVICE_KERNEL_DRIVER 0x00000001 //普通内核驱动
#define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 //文件系统驱动
#define SERVICE_ADAPTER 0x00000004 //适配器驱动
#define SERVICE_RECOGNIZER_DRIVER 0x00000008 //文件系统识别器驱动
每个驱动的类型记录在注册表中对应服务键下的type值中。
非文件系统驱动的驱动对象名都是“Driver\服务名”形式,文件系统驱动的驱动对象名则是“FileSystem\服务名”形式。这点小差别要注意。
驱动有四种加载时机:
1、 系统引导时加载
2、 系统初始化时加载
3、 SCM服务管理器在系统启动后的自动加载
4、 运行时动态加载
老式驱动和WDM驱动都支持动态加载。
老式驱动可以使用NtLoadDriver这个系统服务进行动态加载(实际上SCM服务管理器最终就是通过这个函数加载驱动的),WDM驱动则在设备热插拔时由Pnp管理器调用IopLoadServiceModule动态加载。
(注意:凡是由NtLoadDriver加载的驱动都强制变成老式驱动,即使它提供了AddDevice)
//老式驱动一般都是以服务的方式加载到系统中的,我们看;
NTSTATUS NtLoadDriver(IN PUNICODE_STRING DriverServiceName) //参数表示服务键的全路径
{
UNICODE_STRING CapturedDriverServiceName = { 0, 0, NULL };
KPROCESSOR_MODE PreviousMode;
LOAD_UNLOAD_PARAMS LoadParams;
NTSTATUS Status;
PAGED_CODE();
PreviousMode = KeGetPreviousMode();//一般都是用户空间的SCM在调用本函数
//检查当前令牌是否具有加载驱动的特权(加载驱动很危险,必须把好这个关口,做足权限检查)
if (!SeSinglePrivilegeCheck(SeLoadDriverPrivilege, PreviousMode))
return STATUS_PRIVILEGE_NOT_HELD;
//将DriverServiceName参数从用户空间拷贝到内核空间的CapturedDriverServiceName
Status = ProbeAndCaptureUnicodeString(&CapturedDriverServiceName,PreviousMode,
DriverServiceName);
LoadParams.ServiceName = &CapturedDriverServiceName;//服务名
LoadParams.DriverObject = NULL;//NULL表示加载,否则表示卸载
KeInitializeEvent(&LoadParams.Event, NotificationEvent, FALSE);
if (PsGetCurrentProcess() == PsInitialSystemProcess)
IopLoadUnloadDriver(&LoadParams);//if当前进程是’system’,立即就地调用
//将这个加载任务打包成一个工作项,交由系统工作者线程去完成。由于系统工作者线程本身就是’system’进程中的线程,因此,这就是为什么每个驱动的DriverEntry总是运行在’system’进程中
Else //最典型
{
ExInitializeWorkItem(&LoadParams.WorkItem,IopLoadUnloadDriver, (PVOID)&LoadParams);
ExQueueWorkItem(&LoadParams.WorkItem, DelayedWorkQueue);
KeWaitForSingleObject(&LoadParams.Event, UserRequest, KernelMode,FALSE, NULL);
}
ReleaseCapturedUnicodeString(&CapturedDriverServiceName,PreviousMode);
return LoadParams.Status;
}
如上,上面的函数检查权限通过后,就打包成工作项,转入‘system’进程中去加载驱动。关键的函数是IopLoadUnloadDriver,我们看:
VOID IopLoadUnloadDriver(PLOAD_UNLOAD_PARAMS LoadParams)//既用来加载,也可用来卸载
{
if (LoadParams->DriverObject)
{
(*LoadParams->DriverObject->DriverUnload)(LoadParams->DriverObject);//调用卸载例程
LoadParams->Status = STATUS_SUCCESS;
KeSetEvent(&LoadParams->Event, 0, FALSE);
return;
}
RtlInitUnicodeString(&ImagePath, NULL);
ServiceName = *LoadParams->ServiceName;//要加载的目标驱动的服务名
cur = LoadParams->ServiceName->Buffer + (LoadParams->ServiceName->Length / 2) - 1;
while (LoadParams->ServiceName->Buffer != cur)
{
if(*cur == L'\\')
{
ServiceName.Buffer = cur + 1;
ServiceName.Length = LoadParams->ServiceName->Length -
(USHORT)((ULONG_PTR)ServiceName.Buffer - (ULONG)LoadParams->ServiceName->Buffer);
break;
}
cur--;
}
//上面的循环提取服务键全路径末尾的服务名
IopDisplayLoadingMessage(&ServiceName);
RtlZeroMemory(&QueryTable, sizeof(QueryTable));
RtlInitUnicodeString(&ImagePath, NULL);
QueryTable[0].Name = L"Type";
QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
QueryTable[0].EntryContext = &Type;//从注册表的服务键查询服务的类型,查询结果放在这个字段
QueryTable[1].Name = L"ImagePath";
QueryTable[1].Flags = RTL_QUERY_REGISTRY_DIRECT;
QueryTable[1].EntryContext = &ImagePath;//查询sys文件的路径
Status = RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
LoadParams->ServiceName->Buffer, QueryTable, NULL, NULL);
//创建设备节点,注意第二个参数为NULL,因为这个函数加载的驱动都是老式驱动,老式驱动是没有硬件pdo,但是为了统一,系统会在内部为其创建一个模拟的‘硬件pdo’。
Status = IopCreateDeviceNode(IopRootDeviceNode, NULL, &ServiceName, &DeviceNode);
if (!NT_SUCCESS(Status)) …
//检查对象目录中这个驱动是否已经加载过了,因为若已经加载了,驱动对象会进入对象目录中。
Status = IopGetDriverObject(&DriverObject,&ServiceName,
(Type == SERVICE_FILE_SYSTEM_DRIVER || Type == SERVICE_RECOGNIZER_DRIVER ));
if (!NT_SUCCESS(Status))//if 这个驱动以前没加载过(防止重复加载)
{
//关键。加载驱动的sys文件到内存,跟加载exe、dll文件相似。
Status = MmLoadSystemImage(&ImagePath, NULL, NULL, 0, &ModuleObject, &BaseAddress);
if (!NT_SUCCESS(Status) && Status != STATUS_IMAGE_ALREADY_LOADED) …
RtlCreateUnicodeString(&DeviceNode->ServiceName, ServiceName.Buffer);
if (NT_SUCCESS(Status))
{
//关键。为其创建一个DriverObject,并调用驱动的DriverEntry函数进行初始化
Status = IopInitializeDriverModule(DeviceNode,ModuleObject,
&DeviceNode->ServiceName,
(Type == SERVICE_FILE_SYSTEM_DRIVER || Type == 8 SERVICE_RECOGNIZER_DRIVER),
&DriverObject);
if (!NT_SUCCESS(Status)) //if DriverEntry返回失败
{
MmUnloadSystemImage(ModuleObject);
IopFreeDeviceNode(DeviceNode);
LoadParams->Status = Status;
KeSetEvent(&LoadParams->Event, 0, FALSE);
return;
}
}
//下面的两行实际上一点不起作用,纯属多余,因此,完全可以删去不看。
IopInitializeDevice(DeviceNode, DriverObject);//尝试调用其AddDevice函数
Status = IopStartDevice(DeviceNode);//尝试启动设备
}
Else //若驱动先前已经加载了,就失败返回。
{
ObDereferenceObject(DriverObject);
IopFreeDeviceNode(DeviceNode);
}
LoadParams->Status = Status;
KeSetEvent(&LoadParams->Event, 0, FALSE);
}
上面函数关键的工作,就是将驱动文件加载到内存,然后为为驱动创建一个对象,再调用驱动的DriverEntry函数。不过,这儿有一个小问题要注意:就是需要为老式驱动创建一个用作栈底基石的pdo,其实老式设备根本没有硬件pdo,只不过是系统为了方便统一管理,为老式驱动也模拟创建一个硬件pdo,然后纳入PNP框架统一管理。创建设备节点的函数IopCreateDeviceNode我们后面会看。
NTSTATUS FASTCALL
IopGetDriverObject(PDRIVER_OBJECT *DriverObject,PUNICODE_STRING ServiceName,
BOOLEAN FileSystem)//表示该驱动的类型是否是文件系统
{
PDRIVER_OBJECT Object;
WCHAR NameBuffer[MAX_PATH];
UNICODE_STRING DriverName;
NTSTATUS Status;
*DriverObject = NULL;
DriverName.Buffer = NameBuffer;
DriverName.Length = 0;
DriverName.MaximumLength = sizeof(NameBuffer);
if (FileSystem)
RtlAppendUnicodeToString(&DriverName,”L\\FileSystem\\”);
else
RtlAppendUnicodeToString(&DriverName,L”\\Driver\\”);
RtlAppendUnicodeStringToString(&DriverName, ServiceName);
Status = ObReferenceObjectByName(&DriverName,
OBJ_OPENIF | OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
NULL,0,IoDriverObjectType,KernelMode,NULL, (PVOID*)&Object);
if (!NT_SUCCESS(Status))//检查是否有这个驱动对象
return Status;
*DriverObject = Object;//返回找到的驱动对象
return STATUS_SUCCESS;
}
如上,驱动对象本身也是一种内核对象,他也是有名称的。这个函数检查对象目录中是否有指定的驱动对象,若有,就说明,这个驱动已被加载了,否则,就还未加载。
当加载完驱动文件后,就会调用下面的函数为其创建一个驱动对象,调用DriverEntry
NTSTATUS FASTCALL
IopInitializeDriverModule(
IN PDEVICE_NODE DeviceNode,//没使用
IN PLDR_DATA_TABLE_ENTRY ModuleObject,//该驱动模块的描述信息
IN PUNICODE_STRING ServiceName,//服务名
IN BOOLEAN FileSystemDriver,//是否为文件系统驱动
OUT PDRIVER_OBJECT *DriverObject)//返回创建的驱动对象
{
const WCHAR ServicesKeyName[] = L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\";
WCHAR NameBuffer[MAX_PATH];
UNICODE_STRING DriverName;
UNICODE_STRING RegistryKey;
PDRIVER_INITIALIZE DriverEntry;
PDRIVER_OBJECT Driver;
NTSTATUS Status;
DriverEntry = ModuleObject->EntryPoint;//关键。得到该驱动的oep,即DriverEntry
if (ServiceName != NULL && ServiceName->Length != 0)
{
RegistryKey.Length = 0;
RegistryKey.MaximumLength = sizeof(ServicesKeyName) + ServiceName->Length;
RegistryKey.Buffer = ExAllocatePool(PagedPool, RegistryKey.MaximumLength);
RtlAppendUnicodeToString(&RegistryKey, ServicesKeyName);
RtlAppendUnicodeStringToString(&RegistryKey, ServiceName);//构造好服务键全路径
}
else
RtlInitUnicodeString(&RegistryKey, NULL);
if (ServiceName && ServiceName->Length > 0)
{
if (FileSystemDriver == TRUE)
wcscpy(NameBuffer, ”L\\FileSystem\\”);
else
wcscpy(NameBuffer, ”L\\Driver\\”);
RtlInitUnicodeString(&DriverName, NameBuffer);
DriverName.MaximumLength = sizeof(NameBuffer);
RtlAppendUnicodeStringToString(&DriverName, ServiceName);//构造好这个驱动的驱动对象名
}
else
DriverName.Length = 0;
//上面构造好这个驱动的驱动对象名后,下面就开始创建驱动对象了
Status = IopCreateDriver(DriverName.Length > 0 ? &DriverName : NULL,
DriverEntry,&RegistryKey,ModuleObject,&Driver);
RtlFreeUnicodeString(&RegistryKey);
*DriverObject = Driver;//返回创建的驱动对象
if (!NT_SUCCESS(Status)) return Status; //DriverEntry可能返回失败到这里来
IopReadyDeviceObjects(Driver);
if (PnpSystemInit)
IopReinitializeDrivers();//调用所有注册了重初始化例程的驱动他们的重初始化例程
return STATUS_SUCCESS;
}
继续看:
NTSTATUS
IopCreateDriver(IN PUNICODE_STRING DriverName OPTIONAL,//驱动对象的名称,一般都会提供
IN PDRIVER_INITIALIZE InitializationFunction,//DriverEntry
IN PUNICODE_STRING RegistryPath,//服务键的全路径
PLDR_DATA_TABLE_ENTRY ModuleObject,//驱动模块信息
OUT PDRIVER_OBJECT *pDriverObject)//返回创建的驱动对象
{
ULONG RetryCount = 0;
try_again:
if (!DriverName)//如果没提供名称,根据当前TickCount随机生成一个驱动对象名称(少见)
{
NameLength = (USHORT)swprintf(NameBuffer,L"\\Driver\\%08u",KeTickCount);
LocalDriverName.Length = NameLength * sizeof(WCHAR);
LocalDriverName.MaximumLength = LocalDriverName.Length + sizeof(UNICODE_NULL);
LocalDriverName.Buffer = NameBuffer;
}
Else //最典型
LocalDriverName = *DriverName;
//LocalDriverName就表示最后的驱动对象名称
ObjectSize = sizeof(DRIVER_OBJECT) + sizeof(EXTENDED_DRIVER_EXTENSION);
InitializeObjectAttributes(&ObjectAttributes,&LocalDriverName,
OBJ_PERMANENT | OBJ_CASE_INSENSITIVE,NULL,NULL);
//驱动对象体的大小为驱动对象结构本身的大小 + 后面紧跟的标准驱动扩展结构的大小
Status = ObCreateObject(KernelMode,IoDriverObjectType,&ObjectAttributes,KernelMode,
NULL,ObjectSize,0,0, (PVOID*)&DriverObject);
RtlZeroMemory(DriverObject, ObjectSize);
DriverObject->Type = IO_TYPE_DRIVER;
DriverObject->Size = sizeof(DRIVER_OBJECT);
DriverObject->Flags = DRVO_LEGACY_DRIVER;
DriverObject->DriverExtension = (PDRIVER_EXTENSION)(DriverObject + 1);//指向尾部的扩展
DriverObject->DriverExtension->DriverObject = DriverObject;
DriverObject->DriverInit = InitializationFunction;//DriverEntry
DriverObject->DriverSection = ModuleObject;//指向驱动的模块信息
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
DriverObject->MajorFunction[i] = IopInvalidDeviceRequest;//默认的irp派遣函数是这个
ServiceKeyName.Buffer = ExAllocatePoolWithTag(PagedPool,LocalDriverName.Length +
sizeof(WCHAR),TAG_IO);
ServiceKeyName.Length = LocalDriverName.Length;
ServiceKeyName.MaximumLength = LocalDriverName.MaximumLength;
RtlCopyMemory(ServiceKeyName.Buffer,LocalDriverName.Buffer,LocalDriverName.Length);
ServiceKeyName.Buffer[ServiceKeyName.Length / sizeof(WCHAR)] = UNICODE_NULL;
DriverObject->DriverExtension->ServiceKeyName = ServiceKeyName;//驱动对象的名称,\0终止
RtlCopyMemory(&DriverObject->DriverName,&ServiceKeyName,sizeof(UNICODE_STRING));
//看到没,会把驱动对象挂入对象目录中,从而可防止重复加载
Status = ObInsertObject(DriverObject,NULL,FILE_READ_DATA,0,NULL,&hDriver);
//由TickCount生成的随机驱动对象名称可能会产生冲突,因此重试。
if (!DriverName && (Status == STATUS_OBJECT_NAME_COLLISION) && (RetryCount < 100))
{
RetryCount++;
goto try_again;
}
Status = ObReferenceObjectByHandle(hDriver,0,IoDriverObjectType,KernelMode,
(PVOID*)&DriverObject,NULL);
ZwClose(hDriver);
DriverObject->HardwareDatabase = &IopHardwareDatabaseKey;//固定
DriverObject->DriverStart = ModuleObject ? ModuleObject->DllBase : 0;
DriverObject->DriverSize = ModuleObject ? ModuleObject->SizeOfImage : 0;
//关键。调用我们的DriverEntry
Status = (*InitializationFunction)(DriverObject, RegistryPath);
if (!NT_SUCCESS(Status)) //我们的DriverEntry可能返回失败
{
DriverObject->DriverSection = NULL;
ObMakeTemporaryObject(DriverObject);
ObDereferenceObject(DriverObject);
}
else
*pDriverObject = DriverObject;//返回
//即使我们误将派遣函数填为NULL,也会被系统后台自动更为IopInvalidDeviceRequest
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++)
{
if (DriverObject->MajorFunction[i] == NULL)
DriverObject->MajorFunction[i] = IopInvalidDeviceRequest;
}
return Status;
}
//下面是默认的irp派遣函数,它仅仅简单以失败方式完成这个irp
NTSTATUS NTAPI
IopInvalidDeviceRequest(PDEVICE_OBJECT DeviceObject,PIRP Irp)
{
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_DEVICE_REQUEST;
}
每当加载了一个驱动,调用了DriverEntry函数后,就就会接着调用IopReinitializeDrivers这个函数,检测系统中是否有驱动注册了‘重初始化例程’,若有就调用之。
VOID IopReinitializeDrivers(VOID)
{
PDRIVER_REINIT_ITEM ReinitItem;
PLIST_ENTRY Entry;
Entry = ExInterlockedRemoveHeadList(&DriverReinitListHead,&DriverReinitListLock);//取下来
while (Entry)//遍历所有注册了‘重初始化例程’的驱动
{
ReinitItem = CONTAINING_RECORD(Entry, DRIVER_REINIT_ITEM, ItemEntry);
//递增那个驱动的‘重初始化例程’历史被调用次数
ReinitItem->DriverObject->DriverExtension->Count++;
ReinitItem->DriverObject->Flags &= ~DRVO_REINIT_REGISTERED;
//调用那个驱动注册的‘重初始化例程’
ReinitItem->ReinitRoutine(ReinitItem->DriverObject,ReinitItem->Context,
ReinitItem->DriverObject->DriverExtension->Count);
ExFreePool(Entry);//执行后,就删除掉
Entry = ExInterlockedRemoveHeadList(&DriverReinitListHead,&DriverReinitListLock);
}
}
驱动可以调用下面的函数这册一个‘重初始化例程’,以在其DriverEntry被调用后,可以重新得到初始化。注意‘重初始化例程’一得到调用就立马删除。
VOID
IoRegisterDriverReinitialization(IN PDRIVER_OBJECT DriverObject,
IN PDRIVER_REINITIALIZE ReinitRoutine,IN PVOID Context)
{
PDRIVER_REINIT_ITEM ReinitItem;
ReinitItem = ExAllocatePoolWithTag(NonPagedPool,sizeof(DRIVER_REINIT_ITEM),TAG_REINIT);
ReinitItem->DriverObject = DriverObject;
ReinitItem->ReinitRoutine = ReinitRoutine;
ReinitItem->Context = Context;
DriverObject->Flags |= DRVO_REINIT_REGISTERED;
//挂入全局队列
ExInterlockedInsertTailList(&DriverReinitListHead,&ReinitItem->ItemEntry,
&DriverReinitListLock);
}
至此,我们看完了老式驱动通过SCM服务管理器加载的过程。
实际上,SCM在加载自启型(AUTO_START型)的驱动时,也是最终调用的NtLoadDriver加载的。因此,我们可以说,凡是通过SCM加载的驱动,都是老式驱动。SCM只能加载老式驱动。
下面我们看系统初始化(不是指引导)时,是如何加载各个硬件设备的驱动的。
在此先介绍下背景知识:
系统在启动后,会根据总线布局层次枚举系统中所有的硬件设备,为它们创建设备节点,加入到设备节点树中,同时加载它们的端口驱动。
内核中有一个设备节点树,树中的每个节点叫‘设备节点’,每个设备节点就表示一个硬件设备。系统在初始化时,PNP管理器会从根总线逐级向下枚举出系统中的所有硬件设备,为各个硬件创建一个设备节点,然后为其创建用作栈底基石的pdo,记录在各个硬件的设备节点中,然后将设备节点按照总线位置关系挂入节点树中。因此,可以说,节点树就是用来系统中的物理硬件排放顺序的一个缩影,通过遍历这个节点树,我们不仅可以找出系统中的所有硬件设备,还可以得出他们之间的物理位置关系。
节点树中的每个节点表示一条总线或一个设备,总线也是一种特殊的设备,它也有自己的驱动。比如pci.sys就是用来驱动总线本身的一个驱动。节点树的根是一条总线,但是它实际上不存在,看不见摸不着,它是虚拟的,我们叫它‘根总线’,也可叫它‘虚拟根总线’。在根总线下面,也即节点树的第一层,一般都是一条条具体的总线。如PCI总线、PCICMA总线等,就好像PCI总线、PCICMA总线挂在根总线的下面,
PCI总线本身也是一个设备,它是根总线下面的一个设备节点。PCI总线的下面,则可以挂接其他的总线,如ISA总线、ATA总线、USB总线,PCI总线下面也可以直接挂接真正的设备,如网卡、声卡等。如果节点树中的某个设备节点是一个总线,那么他下面往往还可以挂接其他设备,就像文件夹一样。如果设备节点是个真正的设备,那么它下面一半不能在挂接其他设备,这种设备节点我们可以叫他‘叶设备’。一般来说,非总线设备都是叶设备,不能再挂接其他设备,但是也有例外的时候,比如USB 根HUB,它本身是挂在USB总线上的,但是一个HUB可以提供很多插口,外接其他USB设备或其他HUB。
我们可以说:设备节点即是硬件,硬件即是设备节点。
根总线是虚拟的,系统在初始化时,会创建一个全局的虚拟设备对象,来表示那条根总线,这个全局变量是PnpRootDeviceObject,然后会创建一个全局的根设备节点来表示那条总线,这个全局变量名是IopRootDeviceNode,同时还会创建一个根驱动对象IopRootDriverObject。这三者都是系统初始化时创建的,他们之间的关系是:PnpRootDeviceObject->DriverObject等于IopRootDriverObject,IopRootDeviceNode->PhysicalDeviceObject等于PnpRootDeviceObject。
简而言之:在根总线驱动中创建的根总线设备对象,根设备节点则代表根总线。
另外:节点树中的每个节点都表示真实的硬件设备对象,因此,设备节点中的PhysicalDeviceObject总是指那个用作堆栈基石的栈底pdo,也即‘硬件pdo’。各个硬件设备的栈底pdo都是由其所在的总线驱动为其创建的。比如,显卡是挂在pci总线上的,因此显卡的硬件pdo就是由PCI总线驱动(pci.sys)在内部创建的。因此,我们可以说,某个设备,你挂在哪条总线旗下,你的硬件pdo就是由那条总线的驱动自动为你创建的。
设备节点树反映的是物理位置布局层次关系,而设备栈反映的则是逻辑堆栈层次关系(设计设备栈的目的就是用来使驱动开发变得模块化以减轻驱动开发工作,仅此而已),设备树与设备栈之间没有必然的关系。
如果说设备树是立体的(最上面的根,最下面的是叶),那么设备栈就是水平的(离我们最近的是栈顶,最远的是栈底)。
换言之,有下面的说法:
1. 每个设备节点都有自己的设备堆栈,并且位于设备栈底。
2. 设备树中有多少个设备节点,系统中就有多少个设备栈。(各个设备栈之间是独立的,互不关联)
关于设备节点的图形展示,推荐阅读:http://technet.microsoft.com/zh-cn/library/ff554721
(注:由于本人对硬件也不是太了解,上面的大段话可能有误,望各位同行纠正)
typedef struct _DEVICE_NODE //设备节点
{
struct _DEVICE_NODE *Sibling;//下一个兄弟节点
struct _DEVICE_NODE *Child;//第一个子节点
struct _DEVICE_NODE *Parent;//父节点
struct _DEVICE_NODE *LastChild;//最后一个子节点
ULONG Level;//在节点数的层号(从上往下数,基于0的索引)
PDEVICE_OBJECT PhysicalDeviceObject;//关键字段。所属的硬件pdo(指栈底的基石pdo)
PCM_RESOURCE_LIST ResourceList;//分配到的资源列表
PCM_RESOURCE_LIST ResourceListTranslated;//转换资源列表
//每个硬件设备(包括总线)都记录在注册表中,对应这册表中的某个键。每个设备由设备ID与实例ID的组合唯一标识。(设备ID其实是设备类ID),常见的InstancePath如: Root\Legacy_设备类名\0000, Root\Legacy_设备类名\0001, Root\Legacy_设备类名\0003
UNICODE_STRING InstancePath;//本硬件设备在注册表中的的实例键路径
UNICODE_STRING ServiceName;//本硬件匹配的端口驱动
PIO_RESOURCE_REQUIREMENTS_LIST ResourceRequirements;//资源需求
ULONG BusNumber;//挂在总线位置
LIST_ENTRY DeviceTranslatorList;
……
} DEVICE_NODE, *PDEVICE_NODE;
下面的函数用来为指定的硬件设备创建一个硬件设备节点,加入到设备树,同时记录到注册表中。
NTSTATUS
IopCreateDeviceNode(PDEVICE_NODE ParentNode,//父节点
PDEVICE_OBJECT PhysicalDeviceObject,//硬件设备(老式驱动为NULL)
PUNICODE_STRING ServiceName,//匹配的驱动
PDEVICE_NODE *DeviceNode)//返回
{
UNICODE_STRING LegacyPrefix = RTL_CONSTANT_STRING(L"LEGACY_");
UNICODE_STRING UnknownDeviceName = RTL_CONSTANT_STRING(L"UNKNOWN");
Node = (PDEVICE_NODE)ExAllocatePool(NonPagedPool, sizeof(DEVICE_NODE));//分配一个节点
RtlZeroMemory(Node, sizeof(DEVICE_NODE));
if (!ServiceName)
ServiceName1 = &UnknownDeviceName;
else
ServiceName1 = ServiceName;
if (!PhysicalDeviceObject)//老式驱动是没有硬件pdo支撑的,模拟为其创建一个
{
FullServiceName.MaximumLength = LegacyPrefix.Length + ServiceName1->Length;
FullServiceName.Length = 0;
FullServiceName.Buffer = ExAllocatePool(PagedPool, FullServiceName.MaximumLength);
RtlAppendUnicodeStringToString(&FullServiceName, &LegacyPrefix);
RtlAppendUnicodeStringToString(&FullServiceName, ServiceName1);
//FullServiceName此时等于“LEGACY_服务名”
//在根总线驱动中为其创建一个模拟的硬件pdo(用作栈底基石)
Status = PnpRootCreateDevice(&FullServiceName, &PhysicalDeviceObject, &Node->InstancePath);//返回生成的实例键路径
//将该硬件设备记录到注册表中(即创建它的实例键),这就是为什么这次表的ROOT键下面有一大
//堆‘Legacy_设备类名’和‘Legacy_设备类名\XXXX’的原因,就是这个函数创建写入注册表的。
//注意是REG_OPTION_VOLATILE类型,即不会保存到注册表磁盘中,机器一重启就会丢失。
Status = IopCreateDeviceKeyPath(&Node->InstancePath, REG_OPTION_VOLATILE, &InstanceHandle);//返回实例键的hKey
Node->ServiceName.Buffer = ExAllocatePool(PagedPool, ServiceName1->Length);
Node->ServiceName.MaximumLength = ServiceName1->Length;
Node->ServiceName.Length = 0;
RtlAppendUnicodeStringToString(&Node->ServiceName, ServiceName1);//匹配的端口驱动
if (ServiceName)
{
RtlInitUnicodeString(&KeyName, L"Service");
//在注册表登记这个硬件设备的匹配端口驱动
Status = ZwSetValueKey(InstanceHandle, &KeyName, 0, REG_SZ, ServiceName->Buffer, ServiceName->Length);
}
if (NT_SUCCESS(Status))
{
//在注册表中登记这个设备是一个老式设备
RtlInitUnicodeString(&KeyName, L"Legacy");
LegacyValue = 1;
Status = ZwSetValueKey(InstanceHandle, &KeyName, 0, REG_DWORD, &LegacyValue, sizeof(LegacyValue));
if (NT_SUCCESS(Status))
{
RtlInitUnicodeString(&KeyName, L"Class");
RtlInitUnicodeString(&ClassName, L"LegacyDriver");
Status = ZwSetValueKey(InstanceHandle, &KeyName, 0, REG_SZ, ClassName.Buffer, ClassName.Length);
}
}
ZwClose(InstanceHandle);
ExFreePool(FullServiceName.Buffer);
IopDeviceNodeSetFlag(Node, DNF_LEGACY_DRIVER);//标记这个硬件是一个老式的设备
IopDeviceNodeSetFlag(Node, DNF_ADDED);//标记已经被绑定(用来模拟pnp设备)
IopDeviceNodeSetFlag(Node, DNF_STARTED);//标记已启动(用来模拟pnp设备)
}
Node->PhysicalDeviceObject = PhysicalDeviceObject;//关键。记录指定的硬件pdo
(PhysicalDeviceObject->DeviceObjectExtension)->DeviceNode = Node;//互相维护
if (ParentNode)
{
KeAcquireSpinLock(&IopDeviceTreeLock, &OldIrql);
Node->Parent = ParentNode;
Node->Sibling = ParentNode->Child;
ParentNode->Child = Node;//挂在父节点的子节点列表开头
if (ParentNode->LastChild == NULL)
ParentNode->LastChild = Node;
KeReleaseSpinLock(&IopDeviceTreeLock, OldIrql);
Node->Level = ParentNode->Level + 1;
}
PhysicalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
*DeviceNode = Node;//返回创建的设备节点
return STATUS_SUCCESS;
}
//下面这个函数专用于在根总线驱动中创建一个硬件pdo(所有老式设备,以及最上层的总线,都挂在根总线下面,因此他们的硬件pdo都是靠这个函数创建的)
NTSTATUS
PnpRootCreateDevice(
IN PUNICODE_STRING ServiceName,//服务名
OUT PDEVICE_OBJECT *PhysicalDeviceObject,//返回创建的设备对象
OUT OPTIONAL PUNICODE_STRING FullInstancePath)//返回实例键路径(‘Root\类型\实例ID’形式)
{
UNICODE_STRING PathSep = RTL_CONSTANT_STRING(L"\\");
//#define REGSTR_PATH_SYSTEMENUM TEXT("System\\CurrentControlSet\\Enum")
UNICODE_STRING EnumKeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\"REGSTR_PATH_SYSTEMENUM);
HANDLE EnumHandle, DeviceKeyHandle = INVALID_HANDLE_VALUE;
DeviceExtension = PnpRootDeviceObject->DeviceExtension;//根总线设备的扩展
KeAcquireGuardedMutex(&DeviceExtension->DeviceListLock);
//#define REGSTR_KEY_ROOTENUM TEXT("Root")
_snwprintf(DevicePath, sizeof(DevicePath) / sizeof(WCHAR), L"%s\\%wZ", REGSTR_KEY_ROOTENUM, ServiceName);
//最终DevicePath等于‘Root\服务名’形式
//PNPROOT_DEVICE为根总线驱动中定义的‘扩展中的扩展’结构
Device = ExAllocatePoolWithTag(PagedPool, sizeof(PNPROOT_DEVICE), TAG_PNP_ROOT);
RtlZeroMemory(Device, sizeof(PNPROOT_DEVICE));
RtlCreateUnicodeString(&Device->DeviceID, DevicePath);//记录该硬件设备的DeviceID
//打开Enum键
Status = IopOpenRegistryKeyEx(&EnumHandle, NULL, &EnumKeyName, KEY_READ);
if (NT_SUCCESS(Status))
{
InitializeObjectAttributes(&ObjectAttributes, &Device->DeviceID, OBJ_CASE_INSENSITIVE, EnumHandle, NULL);
//创建‘Root\服务名’键,注意是REG_OPTION_VOLATILE类型,即不会保存到注册表磁盘中
//机器一重启就会丢失。
Status = ZwCreateKey(&DeviceKeyHandle, KEY_SET_VALUE, &ObjectAttributes, 0, NULL, REG_OPTION_VOLATILE, NULL);
ZwClose(EnumHandle);
}
RtlZeroMemory(QueryTable, sizeof(QueryTable));
QueryTable[0].Name = L"NextInstance";
QueryTable[0].EntryContext = &NextInstance;
QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT | RTL_QUERY_REGISTRY_REQUIRED;
//在注册表中查询该类设备的下一个可分配的空闲实例ID号
Status = RtlQueryRegistryValues(RTL_REGISTRY_HANDLE, (PWSTR)DeviceKeyHandle,
QueryTable,NULL,NULL);
if (!NT_SUCCESS(Status))//若没有NextInstance值(这种情况少见)
{
//搜索一个空闲的尚未分配出去的实例ID号
for (NextInstance = 0; NextInstance <= 9999; NextInstance++)
{
_snwprintf(InstancePath, sizeof(InstancePath) / sizeof(WCHAR), L"%04lu", NextInstance);
Status = LocateChildDevice(DeviceExtension, DevicePath, InstancePath, &Device);
if (Status == STATUS_NO_SUCH_DEVICE) //if 找到了
break;
}
}
//经过上面的折腾后,NextInstance就是本次可用分配到的实例ID号
_snwprintf(InstancePath, sizeof(InstancePath) / sizeof(WCHAR), L"%04lu", NextInstance);
Status = LocateChildDevice(DeviceExtension, DevicePath, InstancePath, &Device);
NextInstance++;
Status = RtlWriteRegistryValue(RTL_REGISTRY_HANDLE,DeviceKeyHandle,L"NextInstance",
REG_DWORD,&NextInstance,sizeof(NextInstance));
RtlCreateUnicodeString(&Device->InstanceID, InstancePath);//‘XXXX’形式
if (FullInstancePath)
{
FullInstancePath->MaximumLength = Device->DeviceID.Length + PathSep.Length + Device->InstanceID.Length;
FullInstancePath->Length = 0;
FullInstancePath->Buffer = ExAllocatePool(PagedPool,FullInstancePath->MaximumLength);
RtlAppendUnicodeStringToString(FullInstancePath, &Device->DeviceID);
RtlAppendUnicodeStringToString(FullInstancePath, &PathSep);
RtlAppendUnicodeStringToString(FullInstancePath, &Device->InstanceID);
//最终为FullInstancePath等于‘Root\服务名\XXXX’形式
}
//关键,创建硬件pdo。(注意:除了wdm驱动,系统也会为老式驱动模拟创建一个硬件pdo)
Status = IoCreateDevice(
PnpRootDeviceObject->DriverObject,//关键。根总线驱动
sizeof(PNPROOT_PDO_DEVICE_EXTENSION),//根总线驱动中定义的设备扩展
NULL,FILE_DEVICE_CONTROLLER,
FILE_AUTOGENERATED_DEVICE_NAME,//硬件pdo的对象名都是自动生成的,\Device\XXXXXXXX形式
FALSE,&Device->Pdo);
PdoDeviceExtension = (PPNPROOT_PDO_DEVICE_EXTENSION)Device->Pdo->DeviceExtension;
RtlZeroMemory(PdoDeviceExtension, sizeof(PNPROOT_PDO_DEVICE_EXTENSION));
PdoDeviceExtension->Common.IsFDO = FALSE;//硬件pdo当然不是fdo了
PdoDeviceExtension->DeviceInfo = Device;//扩展中的扩展
//标记这个设备对象是在其所属的总线驱动枚举到后,自动在内部给创建的
Device->Pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
Device->Pdo->Flags &= ~DO_DEVICE_INITIALIZING;
//关键。根总线下面挂接的所有老式设备、其它总线,都记录在根总线设备扩展的链表中。
InsertTailList(&DeviceExtension->DeviceListHead, &Device->ListEntry);
DeviceExtension->DeviceListCount++;
*PhysicalDeviceObject = Device->Pdo;//返回创建的硬件pdo
Status = STATUS_SUCCESS;
cleanup: //清理工作,略
return Status;
}
如上,上面这个函数的主要用途就是为指定服务类型的设备分配一个实例ID,在根总线驱动中创建一个硬件pdo。
//下面的函数用来将硬件设备记录到注册表中(即创建一个对应它的实例键)
NTSTATUS
IopCreateDeviceKeyPath(IN PCUNICODE_STRING RegistryPath,//‘Root\服务名\XXXX’形式
IN ULONG CreateOptions,OUT PHANDLE Handle)
{
// #define ENUM_ROOT L"\\Registry\\Machine\\System\\CurrentControlSet\\Enum"
UNICODE_STRING EnumU = RTL_CONSTANT_STRING(ENUM_ROOT);
HANDLE hParent = NULL, hKey;
*Handle = NULL;
//打开Enum键
Status = IopOpenRegistryKeyEx(&hParent, NULL, &EnumU, KEY_CREATE_SUB_KEY);
Current = KeyName.Buffer = RegistryPath->Buffer;
Last = &RegistryPath->Buffer[RegistryPath->Length / sizeof(WCHAR)];
//下面的循环为RegistryPath路径参数中的各节创建一个键
while (Current <= Last)
{
if (Current != Last && *Current != '\\')
{
Current++;
continue;
}
dwLength = (ULONG_PTR)Current - (ULONG_PTR)KeyName.Buffer;
KeyName.MaximumLength = KeyName.Length = dwLength;
InitializeObjectAttributes(&ObjectAttributes,&KeyName,OBJ_CASE_INSENSITIVE,
hParent,NULL);
Status = ZwCreateKey(&hKey,Current == Last ? KEY_ALL_ACCESS : KEY_CREATE_SUB_KEY,
&ObjectAttributes,0,NULL,CreateOptions,NULL);
if (hParent) ZwClose(hParent);
if (Current == Last)
{
*Handle = hKey;
return STATUS_SUCCESS;
}
hParent = hKey;
Current++;
KeyName.Buffer = (LPWSTR)Current;
}
return STATUS_UNSUCCESSFUL;
}
写至此刻,我收到了家里打来的妈妈意外病逝电话,万分沉痛,万念俱灰,后面的部分实在没心情写下去,思路有点混乱。
--谨以此文悼念亲爱的妈妈
看完了老式驱动通过NtLoadDriver的加载过程,下面看Pnp驱动的加载过程
Pnp驱动最初是在启动机器,上电自检后进行加载的,这是系统初始化时的静态加载。Pnp驱动本意为即插即用,所以pnp驱动最重要的特征便是还可以在设备插入电脑时动态加载设备驱动。下面先看一下系统初始化时静态加载Pnp驱动的过程
BOOLEAN IoInitSystem(IN PLOADER_PARAMETER_BLOCK LoaderBlock)
{
…
IopEnumerateDevice(IopRootDeviceNode->PhysicalDeviceObject);//枚举出根总线下面的所有设备
…
}
//下面这个函数枚举出指定总线下面挂接的所有子设备,加载它们的驱动
NTSTATUS IopEnumerateDevice(IN PDEVICE_OBJECT DeviceObject)
{
PDEVICE_NODE DeviceNode = IopGetDeviceNode(DeviceObject);//对应的设备节点
//先将根设备报告给pnp管理器的用户模式部分
IopQueueTargetDeviceEvent(&GUID_DEVICE_ARRIVAL,&DeviceNode->InstancePath);
Stack.Parameters.QueryDeviceRelations.Type = BusRelations;//查询总线关系
//向指定的总线发出一个查询子设备请求
Status = IopInitiatePnpIrp(DeviceObject,&IoStatusBlock,
IRP_MN_QUERY_DEVICE_RELATIONS,&Stack);
//一般总线驱动会处理这种irp,返回枚举出的所有子设备
DeviceRelations = (PDEVICE_RELATIONS)IoStatusBlock.Information;
for (i = 0; i < DeviceRelations->Count; i++)
{
ChildDeviceObject = DeviceRelations->Objects[i];
ChildDeviceNode = IopGetDeviceNode(ChildDeviceObject);
if (!ChildDeviceNode)//if尚未为其创建设备节点
{
Status = IopCreateDeviceNode(DeviceNode,ChildDeviceObject,NULL,&ChildDeviceNode);
if (NT_SUCCESS(Status))
{
ChildDeviceNode->Flags |= DNF_ENUMERATED;
ChildDeviceObject->Flags |= DO_BUS_ENUMERATED_DEVICE;
}
Else 。。。
}
else
{
ChildDeviceNode->Flags |= DNF_ENUMERATED;
ObDereferenceObject(ChildDeviceObject);
}
}
ExFreePool(DeviceRelations);
//通过上面,指定总线中的所有子设备都登记到设备节点树中去了,下面开始处理每个子设备
IopInitDeviceTreeTraverseContext(&Context,DeviceNode,
IopActionInterrogateDeviceStack, DeviceNode);
//对指定总线下面的每个子设备执行IopActionInterrogateDeviceStack函数
Status = IopTraverseDeviceTree(&Context);
IopInitDeviceTreeTraverseContext(
&Context,DeviceNode,IopActionConfigureChildServices,DeviceNode);
//对指定总线下面的每个子设备执行IopActionConfigureChildServices函数
Status = IopTraverseDeviceTree(&Context);
Status = IopInitializePnpServices(DeviceNode);//加载该设备的驱动程序
return STATUS_SUCCESS;
}
//下面这个函数用来为指定总线下面的每个子设备执行指定的函数
NTSTATUS IopTraverseDeviceTree(PDEVICETREE_TRAVERSE_CONTEXT Context)
{
NTSTATUS Status;
Context->DeviceNode = Context->FirstDeviceNode;
Status = IopTraverseDeviceTreeNode(Context);//实质函数
if (Status == STATUS_UNSUCCESSFUL)
Status = STATUS_SUCCESS;
return Status;
}
//下面这个函数对指定总线及其下面的所有子孙设备调用指定函数,递归
NTSTATUS IopTraverseDeviceTreeNode(PDEVICETREE_TRAVERSE_CONTEXT Context)
{
PDEVICE_NODE ParentDeviceNode;
PDEVICE_NODE ChildDeviceNode;
NTSTATUS Status;
ParentDeviceNode = Context->DeviceNode;
Status = (Context->Action)(ParentDeviceNode, Context->Context);
if (!NT_SUCCESS(Status))
return Status;
for (ChildDeviceNode = ParentDeviceNode->Child;
ChildDeviceNode != NULL;
ChildDeviceNode = ChildDeviceNode->Sibling)
{
Context->DeviceNode = ChildDeviceNode;
Status = IopTraverseDeviceTreeNode(Context);
if (!NT_SUCCESS(Status))
return Status;
}
return Status;
}
对每个子设备都会先后执行IopActionInterrogateDeviceStack、IopActionConfigureChildServices,这两个函数所做的工作如下:
IopActionInterrogateDeviceStack函数会查询指定总线下面挂接的所有直接子设备的设备ID、实例ID,记录到注册表中,然后查询每个直接子设备的所有硬件ID、所有兼容ID、设备描述、设备能力、总线信息。然后记录到注册表中,并查询它们的资源需求。然后将每个直接子设备报告给PNP管理器的用户模式部分。
至于IopActionConfigureChildServices函数的工作不多,作用不大,忽略
NTSTATUS IopInitializePnpServices(IN PDEVICE_NODE DeviceNode)
{
DEVICETREE_TRAVERSE_CONTEXT Context;
IopInitDeviceTreeTraverseContext(&Context,DeviceNode,
IopActionInitChildServices,DeviceNode);
return IopTraverseDeviceTree(&Context);//加载指定总线下面每个子设备的驱动程序
}
//下面的函数加载对应的驱动
NTSTATUS
IopActionInitChildServices(PDEVICE_NODE DeviceNode,
PVOID Context)
{
PDEVICE_NODE ParentDeviceNode;
NTSTATUS Status;
BOOLEAN BootDrivers = !PnpSystemInit;
ParentDeviceNode = (PDEVICE_NODE)Context;
if (DeviceNode == ParentDeviceNode)
return STATUS_SUCCESS;
if (IopDeviceNodeHasFlag(DeviceNode, DNF_STARTED) ||
IopDeviceNodeHasFlag(DeviceNode, DNF_ADDED) ||
IopDeviceNodeHasFlag(DeviceNode, DNF_DISABLED))
return STATUS_SUCCESS;
if (DeviceNode->ServiceName.Buffer == NULL) …
else
{
PLDR_DATA_TABLE_ENTRY ModuleObject;
PDRIVER_OBJECT DriverObject;
Status = IopGetDriverObject(&DriverObject,&DeviceNode->ServiceName,FALSE);
if (!NT_SUCCESS(Status))
{
//加载相应的驱动
Status = IopLoadServiceModule(&DeviceNode->ServiceName, &ModuleObject);
if (NT_SUCCESS(Status) || Status == STATUS_IMAGE_ALREADY_LOADED)
{
if ((Status != STATUS_IMAGE_ALREADY_LOADED) ||
(Status == STATUS_IMAGE_ALREADY_LOADED && !DriverObject))
{
Status = IopInitializeDriverModule(DeviceNode, ModuleObject,
&DeviceNode->ServiceName, FALSE, &DriverObject);
}
Else Status = STATUS_SUCCESS;
}
}
if (NT_SUCCESS(Status))
Status = PipCallDriverAddDevice(DeviceNode, FALSE, DriverObject);
Else …
}
return STATUS_SUCCESS;
}
下面的函数在加载完驱动后进行调用,加载注册的所有上下层过滤驱动,然后调用AddDevice函数
NTSTATUS
PipCallDriverAddDevice(IN PDEVICE_NODE DeviceNode,IN BOOLEAN LoadDriver,
IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING EnumRoot = RTL_CONSTANT_STRING(ENUM_ROOT);
UNICODE_STRING ControlClass =
RTL_CONSTANT_STRING(L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Class");
PKEY_VALUE_FULL_INFORMATION KeyValueInformation = NULL;
Status = IopOpenRegistryKeyEx(&EnumRootKey,NULL,&EnumRoot,KEY_READ);
//打开对应的实例键
Status = IopOpenRegistryKeyEx(&SubKey,EnumRootKey,&DeviceNode->InstancePath,KEY_READ);
Status = IopGetRegistryValue(SubKey,REGSTR_VAL_CLASSGUID,&KeyValueInformation);
if (NT_SUCCESS(Status))
{
Buffer = (PVOID)((ULONG_PTR)KeyValueInformation + KeyValueInformation->DataOffset);
PnpRegSzToString(Buffer, KeyValueInformation->DataLength, &ClassGuid.Length);
ClassGuid.MaximumLength = KeyValueInformation->DataLength;
ClassGuid.Buffer = Buffer;
Status = IopOpenRegistryKeyEx(&ControlKey,NULL,&ControlClass,KEY_READ);
if (!NT_SUCCESS(Status)) ClassKey = NULL;
else
{
//打开对应的设备类键
Status = IopOpenRegistryKeyEx(&ClassKey,ControlKey,&ClassGuid,KEY_READ);
if (!NT_SUCCESS(Status)) ClassKey = NULL;
}
if (ClassKey)
{
RtlInitUnicodeString(&Properties, REGSTR_KEY_DEVICE_PROPERTIES);
Status = IopOpenRegistryKeyEx(&PropertiesKey,ClassKey,&Properties,KEY_READ);
if (!NT_SUCCESS(Status)) PropertiesKey = NULL;
}
}
//先加载该设备实例注册的所有下层过滤驱动以及所属设备类注册的所有下层过滤驱动
IopAttachFilterDrivers(DeviceNode, TRUE);
Status = IopInitializeDevice(DeviceNode, DriverObject);//调用AddDevice
if (NT_SUCCESS(Status))
{
//再加载该设备实例注册的所有上层过滤驱动以及所属设备类注册的所有上层过滤驱动
IopAttachFilterDrivers(DeviceNode, FALSE);
Status = IopStartDevice(DeviceNode);
}
return Status;
}
NTSTATUS
IopInitializeDevice(PDEVICE_NODE DeviceNode,PDRIVER_OBJECT DriverObject)
{
PDEVICE_OBJECT Fdo;
NTSTATUS Status;
if (!DriverObject)
{
DeviceNode->Flags |= DNF_ADDED;
return STATUS_SUCCESS;
}
if (!DriverObject->DriverExtension->AddDevice)
DeviceNode->Flags |= DNF_LEGACY_DRIVER;
if (DeviceNode->Flags & DNF_LEGACY_DRIVER)
{
DeviceNode->Flags |= DNF_ADDED + DNF_STARTED;
return STATUS_SUCCESS;
}
//关键。调用AddDevice函数
Status = DriverObject->DriverExtension->AddDevice(
DriverObject, DeviceNode->PhysicalDeviceObject);
if (!NT_SUCCESS(Status))
{
IopDeviceNodeSetFlag(DeviceNode, DNF_DISABLED);
return Status;
}
Fdo = IoGetAttachedDeviceReference(DeviceNode->PhysicalDeviceObject);
//少见,因为一般AddDevice函数中,用户都会创建一个fdo绑定在pdo上
if (Fdo == DeviceNode->PhysicalDeviceObject) …
if (Fdo->DeviceType == FILE_DEVICE_ACPI) …
IopDeviceNodeSetFlag(DeviceNode, DNF_ADDED);//标志已调用了AddDevice函数
return STATUS_SUCCESS;
}
//下面的函数过滤、分配、转换资源,最后启动设备
NTSTATUS IopStartDevice(PDEVICE_NODE DeviceNode)
{
NTSTATUS Status;
HANDLE InstanceHandle = INVALID_HANDLE_VALUE, ControlHandle = INVALID_HANDLE_VALUE;
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
if (DeviceNode->Flags & (DNF_STARTED | DNF_START_REQUEST_PENDING))
return STATUS_SUCCESS;
Status = IopAssignDeviceResources(DeviceNode);//IO管理器自动为其分配需要的资源
if (!NT_SUCCESS(Status))
goto ByeBye;
IopStartAndEnumerateDevice(DeviceNode);//分配资源后启动设备
Status = IopCreateDeviceKeyPath(&DeviceNode->InstancePath, 0, &InstanceHandle);
if (!NT_SUCCESS(Status))
goto ByeBye;
RtlInitUnicodeString(&KeyName, L"Control");
InitializeObjectAttributes(&ObjectAttributes,&KeyName,OBJ_CASE_INSENSITIVE,
InstanceHandle,NULL);
Status = ZwCreateKey(&ControlHandle, KEY_SET_VALUE, &ObjectAttributes, 0, NULL, REG_OPTION_VOLATILE, NULL);
if (!NT_SUCCESS(Status))
goto ByeBye;
RtlInitUnicodeString(&KeyName, L"ActiveService");
Status = ZwSetValueKey(ControlHandle, &KeyName, 0, REG_SZ, DeviceNode->ServiceName.Buffer, DeviceNode->ServiceName.Length);
ByeBye:
if (ControlHandle != INVALID_HANDLE_VALUE)
ZwClose(ControlHandle);
if (InstanceHandle != INVALID_HANDLE_VALUE)
ZwClose(InstanceHandle);
return Status;
}
NTSTATUS IopStartAndEnumerateDevice(IN PDEVICE_NODE DeviceNode)
{
PDEVICE_OBJECT DeviceObject;
NTSTATUS Status;
DeviceObject = DeviceNode->PhysicalDeviceObject;
if (!(DeviceNode->Flags & DNF_STARTED))//if 尚未启动
IopStartDevice2(DeviceObject);//启动设备
//当一个pnp设备启动后,需要继续往下枚举出它下面的各个设备,加入设备节点树,记录到注册表中,加载各自的驱动。由此而形成递归关系,使系统在初始化时沿着各条总线的物理布局层次, 周而复始逐级向下枚举出所有设备。这样最终枚举出所有pnp设备,建立好整个设备节点树,完成初始化。
if ((DeviceNode->Flags & DNF_STARTED) && (DeviceNode->Flags & DNF_NEED_ENUMERATION_ONLY))
{
IoSynchronousInvalidateDeviceRelations(DeviceObject, BusRelations);
IopDeviceNodeClearFlag(DeviceNode, DNF_NEED_ENUMERATION_ONLY);
Status = STATUS_SUCCESS;
}
else
Status = STATUS_SUCCESS;
return Status;
}
VOID IopStartDevice2(IN PDEVICE_OBJECT DeviceObject)
{
IO_STACK_LOCATION Stack;
PDEVICE_NODE DeviceNode;
NTSTATUS Status;
PVOID Dummy;
DeviceNode = IopGetDeviceNode(DeviceObject);
RtlZeroMemory(&Stack, sizeof(IO_STACK_LOCATION));
Stack.MajorFunction = IRP_MJ_PNP;
Stack.MinorFunction = IRP_MN_START_DEVICE;//这种irp
if (!(DeviceNode->Flags & DNF_RESOURCE_REPORTED))
{
Stack.Parameters.StartDevice.AllocatedResources =
DeviceNode->ResourceList;
Stack.Parameters.StartDevice.AllocatedResourcesTranslated =
DeviceNode->ResourceListTranslated;
}
//向栈顶设备发送这种irp,请求启动设备。
Status = IopSynchronousCall(DeviceObject, &Stack, &Dummy);
if (!NT_SUCCESS(Status))
{
IopSendRemoveDevice(DeviceObject);
DeviceNode->Flags |= DNF_START_FAILED;
return;
}
DeviceNode->Flags |= DNF_STARTED;
DeviceNode->Flags |= DNF_NEED_ENUMERATION_ONLY;//标志需要继续枚举下面设备
}
NTSTATUS IoSynchronousInvalidateDeviceRelations(
IN PDEVICE_OBJECT DeviceObject,
IN DEVICE_RELATION_TYPE Type)
{
switch (Type)
{
case BusRelations:
return IopEnumerateDevice(DeviceObject);//枚举下面设备
case PowerRelations:
return STATUS_NOT_IMPLEMENTED;
case TargetDeviceRelation:
return STATUS_SUCCESS;
default:
return STATUS_NOT_SUPPORTED;
}
}
至此,pnp驱动的静态加载过程讲解完毕,pnp设备可以在动态插入时动态加载。当系统发现一个新的pnp设备插入机器时,会调用下面的函数完成驱动加载
VOID
IoInvalidateDeviceRelations(
IN PDEVICE_OBJECT DeviceObject,
IN DEVICE_RELATION_TYPE Type)
{
PIO_WORKITEM WorkItem;
PINVALIDATE_DEVICE_RELATION_DATA Data;
Data = ExAllocatePool(NonPagedPool, sizeof(INVALIDATE_DEVICE_RELATION_DATA));
WorkItem = IoAllocateWorkItem(DeviceObject);
ObReferenceObject(DeviceObject);
Data->DeviceObject = DeviceObject;
Data->Type = Type;
Data->WorkItem = WorkItem;
IoQueueWorkItem(
WorkItem,
IopAsynchronousInvalidateDeviceRelations, //关键函数
DelayedWorkQueue,
Data);
}
VOID IopAsynchronousInvalidateDeviceRelations(
IN PDEVICE_OBJECT DeviceObject,
IN PVOID InvalidateContext)
{
PINVALIDATE_DEVICE_RELATION_DATA Data = InvalidateContext;
//重新枚举,建立设备节点树,加载驱动
IoSynchronousInvalidateDeviceRelations(Data->DeviceObject,Data->Type);
ObDereferenceObject(Data->DeviceObject);
IoFreeWorkItem(Data->WorkItem);
ExFreePool(Data);
}
我们说,总线本身也是一种设备。我们可以向一条总线发出一个请求,查询它下面挂接的所有设备。根总线驱动就会处理这种irp,返回查询结果给客户。具体的,处理这种irp的派遣函数如下:
NTSTATUS
PnpRootQueryDeviceRelations(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
PDEVICE_RELATIONS Relations = NULL, OtherRelations = Irp->IoStatus.Information;
PPNPROOT_DEVICE Device = NULL;
//从这册表中枚举出根总线下面的所有设备(因为根总线是虚拟的,所以借助注册表)
Status = EnumerateDevices(DeviceObject);
DeviceExtension = (PPNPROOT_FDO_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
Size = FIELD_OFFSET(DEVICE_RELATIONS, Objects) + sizeof(PDEVICE_OBJECT) * DeviceExtension->DeviceListCount;
if (OtherRelations)
Size += sizeof(PDEVICE_OBJECT) * OtherRelations->Count;
Relations = (PDEVICE_RELATIONS)ExAllocatePool(PagedPool, Size);
RtlZeroMemory(Relations, Size);
if (OtherRelations)
{
Relations->Count = OtherRelations->Count;
RtlCopyMemory(Relations->Objects, OtherRelations->Objects, sizeof(PDEVICE_OBJECT) * OtherRelations->Count);
}
for (NextEntry = DeviceExtension->DeviceListHead.Flink;
NextEntry != &DeviceExtension->DeviceListHead;
NextEntry = NextEntry->Flink)
{
Device = CONTAINING_RECORD(NextEntry, PNPROOT_DEVICE, ListEntry);
if (!Device->Pdo)
{
Status = IoCreateDevice(
DeviceObject->DriverObject,
sizeof(PNPROOT_PDO_DEVICE_EXTENSION),
NULL,
FILE_DEVICE_CONTROLLER,
FILE_AUTOGENERATED_DEVICE_NAME,//硬件pdo的对象名称都是自动生成的
FALSE,
&Device->Pdo);
PdoDeviceExtension = (PPNPROOT_PDO_DEVICE_EXTENSION)Device->Pdo->DeviceExtension;
RtlZeroMemory(PdoDeviceExtension, sizeof(PNPROOT_PDO_DEVICE_EXTENSION));
PdoDeviceExtension->Common.IsFDO = FALSE;
PdoDeviceExtension->DeviceInfo = Device;
Device->Pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;
Device->Pdo->Flags &= ~DO_DEVICE_INITIALIZING;
}
Relations->Objects[Relations->Count++] = Device->Pdo;
}
Irp->IoStatus.Information = (ULONG_PTR)Relations;//将枚举结果返回给客户
cleanup:…
return Status;
}
Irp请求的完成处理:当一个irp完成时,用户会调用完成这个IoCompleteRequest irp,我们看看到底一个irp是如何完成的,完成时做了哪些工作呢?
#define IoCompleteRequest IofCompleteRequest
VOID FASTCALL
IofCompleteRequest(IN PIRP Irp,IN CCHAR PriorityBoost)
{
NTSTATUS ErrorCode = STATUS_SUCCESS;
if (Irp->CurrentLocation > Irp->StackCount + 1)
KeBugCheckEx(MULTIPLE_IRP_COMPLETE_REQUESTS, (ULONG_PTR)Irp, 0, 0, 0);
ASSERT(!Irp->CancelRoutine);//必须取消了取消例程,否则蓝屏
ASSERT(Irp->IoStatus.Status != STATUS_PENDING);//不能在阻塞态调用本函数完成irp
ASSERT(Irp->IoStatus.Status != -1);
/* Get the last stack */
LastStackPtr = (PIO_STACK_LOCATION)(Irp + 1);//最底层的栈空间
if (LastStackPtr->Control & SL_ERROR_RETURNED)
ErrorCode = PtrToUlong(LastStackPtr->Parameters.Others.Argument4);
/*
* Start the loop with the current stack and point the IRP to the next stack
* and then keep incrementing the stack as we loop through. The IRP should
* always point to the next stack location w.r.t the one currently being
* analyzed, so completion routine code will see the appropriate value.
* Because of this, we must loop until the current stack location is +1 of
* the stack count, because when StackPtr is at the end, CurrentLocation is +1.
*/
//StackPtr从最底层到最顶层
for (StackPtr = IoGetCurrentIrpStackLocation(Irp),
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++;
Irp->CurrentLocation <= (Irp->StackCount + 1);
StackPtr++,
Irp->CurrentLocation++,
Irp->Tail.Overlay.CurrentStackLocation++)
{
//记录下层当初处理该irp时是否是异步返回的
Irp->PendingReturned = StackPtr->Control & SL_PENDING_RETURNED;
/* Check if we failed */
if (!NT_SUCCESS(Irp->IoStatus.Status))
{
/* Check if it was changed by a completion routine */
if (Irp->IoStatus.Status != ErrorCode)
{
/* Update the error for the current stack */
ErrorCode = Irp->IoStatus.Status;
StackPtr->Control |= SL_ERROR_RETURNED;
LastStackPtr->Parameters.Others.Argument4 = UlongToPtr(ErrorCode);
LastStackPtr->Control |= SL_ERROR_RETURNED;
}
}
IopClearStackLocation(StackPtr);//清理掉该层栈空间
//检查是否满足可以调用完成例程的条件
if ((NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_SUCCESS)) ||
(!NT_SUCCESS(Irp->IoStatus.Status) &&
(StackPtr->Control & SL_INVOKE_ON_ERROR)) ||
(Irp->Cancel &&
(StackPtr->Control & SL_INVOKE_ON_CANCEL)))
{
if (Irp->CurrentLocation == (Irp->StackCount + 1))
DeviceObject = NULL;//回溯到最高层时DeviceObject = NULL
else
DeviceObject = IoGetCurrentIrpStackLocation(Irp)->DeviceObject;
//关键。调用上层的完成例程(注意此时的栈层位于上层栈空间)
Status = StackPtr->CompletionRoutine(DeviceObject,Irp,StackPtr->Context);
//如果该层的完成例程返回结果指示需要进一步完成的话,就中止,不再向上回溯
if (Status == STATUS_MORE_PROCESSING_REQUIRED) return;
}
Else //如果不满足调用完成例程的条件
{
if ((Irp->CurrentLocation <= Irp->StackCount) && (Irp->PendingReturned))
IoMarkIrpPending(Irp);
}
}
if (Irp->Flags & IRP_ASSOCIATED_IRP)//if 这是某个主irp关联的N个副irp之一
{
MasterIrp = Irp->AssociatedIrp.MasterIrp;
MasterCount = InterlockedDecrement(&MasterIrp->AssociatedIrp.IrpCount);
//释放该irp关联的所有MDL
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
IoFreeIrp(Irp);//释放irp结构体本身
//若这是最后一个副irp,就完成掉它的主irp
if (MasterCount==0) IofCompleteRequest(MasterIrp, PriorityBoost);
return;
}
//释放该irp关联的其他辅助缓冲
if (Irp->Tail.Overlay.AuxiliaryBuffer)
{
ExFreePool(Irp->Tail.Overlay.AuxiliaryBuffer);
Irp->Tail.Overlay.AuxiliaryBuffer = NULL;
}
if (Irp->Flags & (IRP_PAGING_IO | IRP_CLOSE_OPERATION))
{
if (Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_CLOSE_OPERATION))
{
*Irp->UserIosb = Irp->IoStatus;
KeSetEvent(Irp->UserEvent, PriorityBoost, FALSE);
Flags = Irp->Flags & (IRP_SYNCHRONOUS_PAGING_IO | IRP_PAGING_IO);
if (Flags) IoFreeIrp(Irp);//释放分页IO irp
}
return;
}
Mdl = Irp->MdlAddress;//解锁该irp关联的所有MDL页面,允许换出到外存
while (Mdl)
{
MmUnlockPages(Mdl);
Mdl = Mdl->Next;
}
if ((Irp->Flags & IRP_DEFER_IO_COMPLETION) && !(Irp->PendingReturned))
return;//直接返回,不再调用IopCompleteRequest了,由用户自己调用,去完成后半部分的工作
Thread = Irp->Tail.Overlay.Thread;
FileObject = Irp->Tail.Overlay.OriginalFileObject;
if (!Irp->Cancel) //最常见
{
KeInitializeApc(&Irp->Tail.Apc,&Thread->Tcb,Irp->ApcEnvironment,
IopCompleteRequest,//进一步的完成工作以apc的方式执行
NULL,NULL,KernelMode,NULL);
KeInsertQueueApc(&Irp->Tail.Apc,FileObject,NULL,PriorityBoost);
}
Else //该irp是被取消的(也即用户要求以取消方式完成掉这个irp)
{
if (Thread)
{
KeInitializeApc(&Irp->Tail.Apc,&Thread->Tcb,Irp->ApcEnvironment,
IopCompleteRequest,
NULL,NULL,KernelMode,NULL);
KeInsertQueueApc(&Irp->Tail.Apc,FileObject,NULL,PriorityBoost);
}
else
IopCleanupIrp(Irp, FileObject);
}
}
IopCompleteRequest是后半部分的完成工作,我们看:
VOID
IopCompleteRequest(IN PKAPC Apc,
IN PKNORMAL_ROUTINE* NormalRoutine,
IN PVOID* NormalContext,
IN PVOID* SystemArgument1,
IN PVOID* SystemArgument2)
{
PFILE_OBJECT FileObject;
PIRP Irp;
PMDL Mdl, NextMdl;
PVOID Port = NULL, Key = NULL;
BOOLEAN SignaledCreateRequest = FALSE;
FileObject = (PFILE_OBJECT)*SystemArgument1;
Irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
//拷贝给用户空间,释放该irp关联的系统缓冲
if (Irp->Flags & IRP_BUFFERED_IO)
{
if ((Irp->Flags & IRP_INPUT_OPERATION) &&
(Irp->IoStatus.Status != STATUS_VERIFY_REQUIRED) &&
!(NT_ERROR(Irp->IoStatus.Status)))
{
RtlCopyMemory(Irp->UserBuffer,Irp->AssociatedIrp.SystemBuffer,
Irp->IoStatus.Information);
}
if (Irp->Flags & IRP_DEALLOCATE_BUFFER)
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
Irp->Flags &= ~(IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER);
//释放该irp关联的所有mdl
for (Mdl = Irp->MdlAddress; Mdl; Mdl = NextMdl)
{
NextMdl = Mdl->Next;
IoFreeMdl(Mdl);
}
Irp->MdlAddress = NULL;
if (!(NT_ERROR(Irp->IoStatus.Status)) ||
(NT_ERROR(Irp->IoStatus.Status) && (Irp->PendingReturned) &&
!(IsIrpSynchronous(Irp, FileObject))))
{
if ((FileObject) && (FileObject->CompletionContext))
{
Port = FileObject->CompletionContext->Port;
Key = FileObject->CompletionContext->Key;
}
*Irp->UserIosb = Irp->IoStatus;
if (Irp->UserEvent)
{
KeSetEvent(Irp->UserEvent, 0, FALSE);
if (FileObject)
{
if ((FileObject->Flags & FO_SYNCHRONOUS_IO) &&
!(Irp->Flags & IRP_OB_QUERY_NAME))
{
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
}
if (Irp->Flags & IRP_CREATE_OPERATION)
{
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
}
else if (FileObject)
{
KeSetEvent(&FileObject->Event, 0, FALSE);
FileObject->FinalStatus = Irp->IoStatus.Status;
if (Irp->Flags & IRP_CREATE_OPERATION)
{
Irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;
SignaledCreateRequest = TRUE;
}
}
//更新各自类型的历史总计转交字节数
if (!(Irp->Flags & IRP_CREATE_OPERATION))
{
if (Irp->Flags & IRP_WRITE_OPERATION)
IopUpdateTransferCount(IopWriteTransfer,Irp->IoStatus.Information);
else if (Irp->Flags & IRP_READ_OPERATION)
IopUpdateTransferCount(IopReadTransfer,Irp->IoStatus.Information);
else
IopUpdateTransferCount(IopOtherTransfer,Irp->IoStatus.Information);
}
IopUnQueueIrpFromThread(Irp);//脱出线程的阻塞irp链表
//用于用户空间的apc方式IO
if (Irp->Overlay.AsynchronousParameters.UserApcRoutine)
{
KeInitializeApc(&Irp->Tail.Apc,
KeGetCurrentThread(),
CurrentApcEnvironment,
IopFreeIrpKernelApc,
IopAbortIrpKernelApc,
(PKNORMAL_ROUTINE)Irp->
Overlay.AsynchronousParameters.UserApcRoutine,
Irp->RequestorMode,
Irp->
Overlay.AsynchronousParameters.UserApcContext);
KeInsertQueueApc(&Irp->Tail.Apc, Irp->UserIosb, NULL, 2);
}
//将一个irp完成事件插入指定完成端口的消息队列中
else if ((Port) &&
(Irp->Overlay.AsynchronousParameters.UserApcContext))
{
Irp->Tail.CompletionKey = Key;
Irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;
KeInsertQueue(Port, &Irp->Tail.Overlay.ListEntry);
}
else
IoFreeIrp(Irp);//释放irp结构体本身
}
else
{
if ((Irp->PendingReturned) && (FileObject))
{
if (Irp->Flags & IRP_SYNCHRONOUS_API)
{
*Irp->UserIosb = Irp->IoStatus;
if (Irp->UserEvent)
KeSetEvent(Irp->UserEvent, 0, FALSE);
else
KeSetEvent(&FileObject->Event, 0, FALSE);
}
else
{
FileObject->FinalStatus = Irp->IoStatus.Status;
KeSetEvent(&FileObject->Event, 0, FALSE);
}
}
IopUnQueueIrpFromThread(Irp);
IoFreeIrp(Irp);
}
}
最后是释放irp结构体本身
VOID IoFreeIrp(IN PIRP Irp)
{
PNPAGED_LOOKASIDE_LIST List;
PP_NPAGED_LOOKASIDE_NUMBER ListType = LookasideSmallIrpList;
PKPRCB Prcb;
ASSERT(IsListEmpty(&Irp->ThreadListEntry));
ASSERT(Irp->CurrentLocation >= Irp->StackCount);
if (!(Irp->AllocationFlags & IRP_ALLOCATED_FIXED_SIZE))
ExFreePoolWithTag(Irp, TAG_IRP);
else
{
if (Irp->StackCount != 1) ListType = LookasideLargeIrpList;
Prcb = KeGetCurrentPrcb();
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
List->L.TotalFrees++;
if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
{
List->L.FreeMisses++;
List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
List->L.TotalFrees++;
if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
{
List->L.FreeMisses++;
ExFreePoolWithTag(Irp, TAG_IRP);
Irp = NULL;
}
}
if (Irp)
{
InterlockedPushEntrySList(&List->L.ListHead,Irp);
}
}
}
Irp完成工作可以看出主要是:向上回溯调用各层的完成例程,资源的释放,系统缓冲释放,MDL释放,irp结构体本身的释放,触发完成事件信号等操作。