最近闲下来了,总结一下Windows(当前最新的桌面操作系统时Windows 11)系统内核中几个重要的对象
文件 FILE_OBJECT
typedef struct _FILE_OBJECT {
CSHORT Type; //只读,如果是文件对象,值为5
CSHORT Size; //只读,文件对象大小(in byte),不包括对象扩展
PDEVICE_OBJECT DeviceObject; //指向文件打开设备的指针
PVPB Vpb; //关联当前文件对象的卷参数块指针,!=NULL时,文件驻留在挂载的卷上
PVOID FsContext; //关于文件状态的指针,由关联驱动程序维护,对于文件系统驱动,它指向FSRTL_ADVANCED_FCB_HEADER,否则,系统可能蓝屏;通常,这个Header结构内嵌在FCB中;但,对于NTFS,这个Header内嵌在SCB中。
PVOID FsContext2; //关于文件状态的指针(不透明),由关联驱动程序维护;在WDM驱动栈中,只有功能设备能使用上述两个上下文指针。
PSECTION_OBJECT_POINTERS SectionObjectPointer; //用于缓存管理器交互,由文件系统设置,只读
PVOID PrivateCacheMap; //用于缓存管理器交互,由文件系统设置,只读,不透明
NTSTATUS FinalStatus; //只读,指示文件对象I/O请求的最终状态;在特定同步时使用。
struct _FILE_OBJECT *RelatedFileObject; //和当前文件管理的已打开文件对象,该指针通常(不总是)指向当前文件所在目录,该指针旨在处理IRP_MJ_CREATE时有用。
BOOLEAN LockOperation; //只读,一旦是TRUE,这个值会一直保持到对象销毁。
BOOLEAN DeletePending; //只读,如果=TRUE,表示有进程将文件设置为“等待删除”,只是动作还未执行。
BOOLEAN ReadAccess; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
BOOLEAN WriteAccess; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
BOOLEAN DeleteAccess; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
BOOLEAN SharedRead; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
BOOLEAN SharedWrite; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
BOOLEAN SharedDelete; //只读,表示当前对象所有者的权限,检查和/或设置文件的共享访问权限时使用。
ULONG Flags; //
UNICODE_STRING FileName; //卷上打开的文件的名称,如果打开的是卷,则无效;该值仅在IRP_MJ_CREATE Pre阶段有效,Post阶段或其他IRP处理过程中无效。
LARGE_INTEGER CurrentByteOffset; //只读,文件偏移量(byte),由文件系统维护,仅用于被以同步方式打开的文件对象。指向文件流的当前指针位置,该指针在成功完成读写操作时进行更新
__volatile ULONG Waiters; //同步访问目标文件的等待进程数。
__volatile ULONG Busy; //只读,当前访问文件的进程是否繁忙。
PVOID LastLock; //事件锁,不透明,用于文件对象的最后一个锁。
KEVENT Lock; //事件锁,不透明,用于控制对文件的同步访问,仅适用于以“同步访问”方式访问文件的进程。
KEVENT Event; //不透明,用户未提供时,系统将它指向I/O请求的完成信号。
__volatile PIO_COMPLETION_CONTEXT CompletionContext; //由IO管理器进行使用,在完成一个IRP的时候,发送一个消息到本地过程调用(LPC)端口
KSPIN_LOCK IrpListLock; //不透明,指向KSPIN_LOCK,用于同步文件对象的IRP列表访问的自旋锁。
LIST_ENTRY IrpList; //不透明,指向与文件对象关联的 IRP 列表头。
__volatile _IOP_FILE_OBJECT_EXTENSION *FileObjectExtension; //不透明,指向FOBX结构,可以通过FsRtlxxx访问。
struct _IOP_FILE_OBJECT_EXTENSION;//无
} FILE_OBJECT, *PFILE_OBJECT;
根据微软的文档介绍:
- 应将文件对象中的不透明成员视为不可访问。
- 驱动程序可以使用只读成员获取相关信息,但不能修改只读成员。
- 不能通过检查FILE_OBJECT内容确定文件类型(目录、文件、卷),应通过ZwQueryInformationFile。
- DeviceObject 和VPNB,由IO管理器在发送create请求给文件系统驱动之前初始化
FsContext
、FsContext2
、SectionObjectPointer
、PrivateCacheMap
由文件系统驱动和缓存管理器进行初始化和维护,IO管理器不维护这些域中的内容,但是IO管理器会检查和使用FsContext域的内容FileName
:由IO管理器进行初始化表示要打开的文件、卷或物理设备的字符串,可以是相对路径也可以是绝对路径。为相对路径名时,可通过查询RelatedFileObject域的非空值拼装出绝对路径。
驱动对象
typedef struct _DRIVER_OBJECT {
CSHORT Type; //无
CSHORT Size; //无
PDEVICE_OBJECT DeviceObject; //指向由驱动创建的设备对象,应使用IoCreateDevide更新该值,可以通过该结构的NextDevice遍历驱动程序创建的所有设备。
ULONG Flags; //无
PVOID DriverStart; //无
ULONG DriverSize; //无
PVOID DriverSection; //无
PDRIVER_EXTENSION DriverExtension; //指向驱动的扩展结构体,该结构中只有AddDevice可以修改,用来存储驱动程序的AddDevice函数的指针。
UNICODE_STRING DriverName; //驱动程序的名称字符串,一般为“\Driver\驱动名称”
PUNICODE_STRING HardwareDatabase; //指向注册表路径“\Registry\Mechine\Hardware”,存储硬件配置信息,驱动程序只有读取权限。
PFAST_IO_DISPATCH FastIoDispatch; //指向驱动的Fast I/O入口点,该成员制备FSD和网络传输驱动用
PDRIVER_INITIALIZE DriverInit; //由I/O管理器配置,DriverEntry的入口点函数
PDRIVER_STARTIO DriverStartIo; //驱动的StartIo函数指针,在DriverEntry中设置(可选)
PDRIVER_UNLOAD DriverUnload; //驱动的卸载函数指针,在DriverEntry中设置(可选)
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
//派遣函数指针列表,用于处理函数关心的IRP
} DRIVER_OBJECT, *PDRIVER_OBJECT;
开发过程中,通常需要重点关注的几个参数如下:
- DeviceObject:该成员指向由驱动程序创建的设备,可能由一个,也可能由多个(如电脑上的扬声器,就有两个,如果要播放立体环绕音,就需要控制两个设备进行不同的IRP传输)
- DriverUnload:当驱动程序卸载时调用函数的指针,如果不支持驱动卸载可以留NULL,如果支持,可以在DriverUnload做一些清理工作。
- MajorFunction:一组指针,指向用户注册的用于处理IRP的函数,这些函数都是"Pre"阶段收到IRP处理通知,如果需要在IRP返回用户时继续处理,需要在函数内注册完成函数(参考IoSetCompletionRoutine)
设备对象
typedef struct _DEVICE_OBJECT {
CSHORT Type; //只读,表示对象类型,设备对象=3
USHORT Size; //只读,本结构体大小(byte),包括设备扩展,但不包含DeviceObjectExtension 指向的成员大小
LONG ReferenceCount; //只读,I/O管理器用,记录打开设备的句柄数
struct _DRIVER_OBJECT *DriverObject; //只读,设备的驱动程序(通过调用IoCreateDevice或IoCreateDeviceSecure产生该对象)。
struct _DEVICE_OBJECT *NextDevice; //指向本设备的驱动对象创建的兄弟设备,由I/O管理器维护。
struct _DEVICE_OBJECT *AttachedDevice; //指向被绑定设备对象(通常是过滤驱动),如果没有=NULL(通常是功能驱动)。
struct _IRP *CurrentIrp; //如果驱动有StartIo函数,指针指向当前正在处理的IRP,否则为NULL。
PIO_TIMER Timer; //可读写,I/O管理器会每秒钟调用一次驱动提供的时钟处理函数。
ULONG Flags; //指示性标记,,可以通过“或”操作使用1~n个值。
ULONG Characteristics; //驱动设备的附加信息,可以通过“或”操作使用1~n个值
__volatile PVPB Vpb; //不透明,指向和设备对象关联的volume parameter block (VPB)
PVOID DeviceExtension; //由驱动程序定义,给设备对象使用
DEVICE_TYPE DeviceType; //指示当前设备类型,详细信息参考本文链接“Specify device types”
CCHAR StackSize; //处理IRP的设备栈数量,I/O管理器自动设置。
union {
LIST_ENTRY ListEntry; //双向链表
WAIT_CONTEXT_BLOCK Wcb; //I/O管理器使用的上下文
} Queue; //无
ULONG AlignmentRequirement;//数传输时对设备地址对齐的要求,值只能是Wdm.h中FILE_XXX_ALIGNMENT中的一个
KDEVICE_QUEUE DeviceQueue; //不透明,设备对象队列。
KDPC Dpc; //延迟过程调用
ULONG ActiveThreadCount; //保留
PSECURITY_DESCRIPTOR SecurityDescriptor; //如需要,请通过调用ZwSetSecurityObject更新该值。
KEVENT DeviceLock; //不透明,I/O管理器用
USHORT SectorSize; //当设备对象为卷时,表示卷的扇区大小(byte),I/O管理器通过该参数确保在禁用了中间缓冲时文件操作都能正确对齐。创建设备对象时,使用默认的系统字节/扇区值。一下请可少见,但有:文件系统驱动程序、遗留驱动器和minifilter驱动程序可以在发生挂载时根据底层卷硬件的几何形状更新此值。其他驱动程序不应该修改这个成员。
USHORT Spare1; //系统用,不透明
struct _DEVOBJ_EXTENSION *DeviceObjectExtension;//给I/O管理器和PnP管理器使用的设备扩展指针,不透明。
PVOID Reserved; //保留
} DEVICE_OBJECT, *PDEVICE_OBJECT;
-
系统用一个设备对象(Device Object)代表一个设备,驱动程序通过IoCreateDevice 和IoCreateDeviceSecure创建设备,系统根据配置自动创建设备对象。
-
底层驱动调用IoCreateDevice后,需要设置AlinmentRequirement值,方法如下:
-
设备实际对齐要求 - 1
-
对比设备对象的AlinmentRequirement
-
如果步骤一结果大,则设备对象的AlinmentRequirement = 设备实际对齐要求 - 1,否则保留当前设备对象的AlinmentRequirement值。
-
-
StackSize:通常情况下该变量只需要查询,不需要修改维护,但如果开发者在绑定到某设备时使用了IoGetDeviceObjectPointer,则需要调用者在自己的设备对象中对该变量手动+1(当前设备对象的StackSize = 被绑定设备的DEVICE_OJBECT->StackSize+1)。
-
尽量用 IoAttachDevice 或 IoAttachDeviceToDeviceStack代替IoGetDeviceObjectPointer
IRP(I/O Request Package)
typedef struct _IRP {
CSHORT Type;
USHORT Size;
PMDL MdlAddress; //指向描述用户缓冲区的 MDL(如果驱动程序使用的是直接I/O,且IRP是Read、Write、Device_Control或Internal_Device_Control(IOCTL代码指定METHOD_IN_DIRECT)则 MDL 描述包含设备或驱动程序数据的缓冲区)
ULONG Flags; //只读,供文件系统驱动使用,一个或多个IRO_XXX按位OR。
union {
struct _IRP *MasterIrp;
__volatile LONG IrpCount;
PVOID SystemBuffer; //如果驱动程序使用的是Buffered I/O,请参考文章最后链接“I/O控制代码的缓冲区说明”。
} AssociatedIrp;
LIST_ENTRY ThreadListEntry;
IO_STATUS_BLOCK IoStatus; //调用 IoCompleteRequest前,存储状态和信息的 IO_STATUS_BLOCK 结构
KPROCESSOR_MODE RequestorMode; //指示发起者的模式(UserMode或KernelMode)
BOOLEAN PendingReturned; //如果=TRUE,表示驱动程序已将IRP挂起,所有IoCompletion都要检查这个值。
CHAR StackCount;
CHAR CurrentLocation;
BOOLEAN Cancel; //当前IRP已经被设置为Cancel,当前处理者适当处理。
KIRQL CancelIrql; //调用 ioAcquireCancelSpinLock 时驱动程序运行所在的IRQL
CCHAR ApcEnvironment;
UCHAR AllocationFlags;
union {
PIO_STATUS_BLOCK UserIosb;
PVOID IoRingContext;
};
PKEVENT UserEvent;
union {
struct {
union {
PIO_APC_ROUTINE UserApcRoutine;
PVOID IssuingProcess;
};
union {
PVOID UserApcContext;
#if ...
_IORING_OBJECT *IoRing;
#else
struct _IORING_OBJECT *IoRing;
#endif
};
} AsynchronousParameters;
LARGE_INTEGER AllocationSize;
} Overlay;
__volatile PDRIVER_CANCEL CancelRoutine; //驱动程序提供的“取消”函数指针,如果取消 IRP,则调用该例程。 NULL 指示 IRP 当前不可取消。
PVOID UserBuffer; //存放数据的缓冲区,请参考文章最后链接“I/O控制代码的缓冲区说明”。
union {
struct {
union {
KDEVICE_QUEUE_ENTRY DeviceQueueEntry; //如果 IRP 在与驱动程序的设备对象关联的设备队列中排队,则此字段链接设备队列中的 IRP。 仅当驱动程序正在处理 IRP 时,才能使用这些链接。
struct {
PVOID DriverContext[4]; //如果 IRP 不在与驱动程序的设备对象关联的设备队列中排队,驱动程序可以使用此字段来存储最多四个指针。 此字段只能在驱动程序拥有 IRP 时使用。
};
};
PETHREAD Thread; //向调用方线程控制块(TCB)的指针。 对于源自用户模式的请求,I/O 管理器始终将此字段设置为指向发出请求的线程的 TCB。
PCHAR AuxiliaryBuffer;
struct {
LIST_ENTRY ListEntry; //如果驱动程序管理自己的内部 IRP 队列,则它使用此字段将一个 IRP 链接到下一个 IRP。 仅当驱动程序在其队列中保存 IRP 或正在处理 IRP 时,才能使用这些链接。
union {
struct _IO_STACK_LOCATION *CurrentStackLocation;
ULONG PacketType;
};
};
PFILE_OBJECT OriginalFileObject;
} Overlay;
KAPC Apc;
PVOID CompletionKey;
} Tail;
} IRP;
参考文档:
- FILE_OBJECT (wdm.h) - Windows drivers | Microsoft Learn
- DRIVER_OBJECT (wdm.h) - Windows drivers | Microsoft Learn
- Specifying Device Types - Windows drivers | Microsoft Learn
- DEVICE_OBJECT (wdm.h) - Windows drivers | Microsoft Learn
- Initializing a Device Object - Windows drivers | Microsoft Learn
- _IRP (wdm.h) - Windows drivers | Microsoft Learn
- I/O 控制代码的缓冲区说明 - Windows drivers | Microsoft Learn