第七章 Windows 2000的对象管理
翻译:Kendiv( fcczj@263.net )
更新:Thursday, May 19, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
在Windows 2000内部几乎没有什么比它的对象还有趣。如果可以像观看行星表面一样查看操作系统的内存空间,那么对象看起来就像活在行星上的生物。存在着多种类型的对象,有大有小,有复杂的也有简单的,它们通过多种方式互相影响。一个巧妙的、结构化的对象管理机制是Windows 2000的一大特色,不过这一机制几乎没有文档记载。本章将试图带你深入这个庞大而复杂的世界里。很不幸的是,Windows 2000的这一部分是微软保护最好的秘密,许多问题在这里仍然没有答案。不过,我希望本章能成为一个起点,以帮助我们探索“在此之前,还没有人去过的地方”。
Windows 2000对象的结构
本书光盘上有一个很大的头文件名为:w2k_def.h,它位于\src\common\include目录下,它为Windows 2000系统编程者那颗痛苦跳动着的心带来了一丝喜悦。该文件包含多年来深入研究Windows NT/200所获取的常量和类型定义。w2k_def.h可以用于Win32应用程序也可用于内核模式的驱动程序中,它使用条件编译选项来区分不同的构建环境。例如,Win32应用程序无法使用ntdef.h和ntddk.h,因为这俩个文件包含很多内核数据类型的定义。因此,w2k_def.h包含了所有可以在DDK头文件中找到的#define和typedef定义,未文档化的内容需要这些定义。为了在构建内核模式的驱动程序时,出现避免重定义错误,这些定义被放入了#ifdef _USER_MODE_子句中,因此,如果没有定义_USER_MDE_没有定义,编译器将忽略它们。这意味着你必须在包含w2k_def.h的源代码中放入#define _USER_MODE_,这一语句必须位于#include “w2k_def.h”之前,这样才能允许预处理器在Win32应用程序或DLL中处理DDK中的定义。#ifdef _USER_MODE_的#else子句包含少量Windows 2000 DDK头文件中缺失的定义,如SECURITY_DESCRIPTOR和SECURITY_DESCRIPTOR_CONTROL类型。
对象的基本分类
尽管对象是Windows 2000送给我们的一个大礼,但在DDK中你很难发现有关它们的内部结构的有用信息。由ntoskrnl.exe导出的21个用于对象管理的Ob*()函数,仅有6个出现在DDK文档中。这些函数接受一个指向对象的指针作为参数,该指针参数的类型通常为PVOID。如果你在DDK的主要头文件ntdef.h和ntddk.h中查找与对象相关的类型定义,你会发现几乎没有什么有用的信息。有些重要的对象数据类型仅被定义为一个占位符(placeholder)。例如,OBJECT_TYPE结构出现方式为:typedef struct _OBJECT_TYPE *POBJECT_TYPE;这样做只是为了让编译器高兴而以,没有透露出任何有关该对象内部的信息。
无论你在何时遇到对象指针,你都会发现从其线形地址来看,它将常驻内存的结构体划分为两部分:一个对象头和一个对象体。对象指针并没有指向对象自身的基地址,而是指向了对象体,由于紧接着对象头的就是对象体,所以可以给对象指针加上一个负的偏移量来访问对象头。对象体的内部结构完全依赖于对象的类型,对于不同的类型其差别很大。最简单的对象是Event对象,该对象只有一个16字节的对象体。最复杂对象的代表就是进程和线程对象,这俩个对象的大小达到了几百字节。基本上,可以将对象体的类型划分为如下三个主要类别:
1. Dispatcher对象 此类对象位于系统的最底层,在它们的对象体的开始处都有一个共享的公共数据结构----DISPATCHER_HEADER(参见列表7-1)。在它们的对象头中包含一个对象类型ID和对象体的长度(保存在32位的DWORD变量中)。所有Dispatcher对象结构体的名字都以字母K开始,这表示它们是内核(Kernel)对象。DISPATCHER_HEADER结构的存在使得对象是“可等待的(waitable)”。这意味着,此种类型的对象可以被传递给同步函数KeWaitForSingleObject()和KeWaitForMultipleObjects(),Win32函数WaitForSingleObject()和WaitForMultipleObjects()就构建于它们之上。
typedef struct _DISPATCHER_HEADER
{
/*000*/ BYTE Type; // DISP_TYPE_*
/*001*/ BYTE Absolute;
/*002*/ BYTE Size; // number of DWORDs
/*003*/ BYTE Inserted;
/*004*/ LONG SignalState;
/*008*/ LIST_ENTRY WaitListHead;
/*010*/ }
DISPATCHER_HEADER,
* PDISPATCHER_HEADER,
**PPDISPATCHER_HEADER;
#define DISPATCHER_HEADER_ \
sizeof (DISPATCHER_HEADER)
列表7-1. DISPATCHER_HEADER结构的定义
译注:
可以在本书CD的\src\common目录下的w2k_def.h中找到该结构的定义。
2. I/O系统数据结构(I/O对象) 这类对象是最高层的对象,其对象体的开始位置是一个SHORT类型的成员,该成员用来标识该对象的ID。通常,此ID之后还有一个SHORT或WORD类型的成员用来记录对象体的大小。不过,此类对象并不都遵守这一规则。
3. 其他对象 不属于上述两种对象的对象。
从现在起就要注意Dispatcher对象和I/O对象的类型ID,因为这些ID都是单独维护的,所以有些ID可能会发生重复。表7-1给出了我所知道的Dispatcher对象的类型。出现在表7-1的“C结构”一栏的某些结构体在DDK头文件ntddk.h中有相应的定义。但很不幸的是,很多非常重要的结构,如KPROCESS和KTHREAD都没有官方给出的定义(译注:可以理解,这些结构在未来的版本中可能会发生变化)。不过无需担心,稍后我们将详细讨论这些特殊对象类型的细节。在w2k_def.h中你可以找到所有未文档化的结构体(当然这仅限于我了解得那些)的定义,附录C也会列出这些结构体的定义。
表7-1. Dispatcher对象汇总
ID | 类 型 | C结构 | 定 义 |
0 | DISP_TYPE_NOTIFICATION_EVENT | KEVENT | ntddk.h |
1 | DISP_TYPE_SYNCHRONIZATION_EVENT | KEVENT | ntddk.h |
2 | DISP_TYPE_MUTANT | KMUTANT,KMUTEX | ntddk.h |
3 | DISP_TYPE_PROCESS | KPROCESS | w2k_def.h |
4 | DISP_TYPE_QUEUE | KQUEUE | w2k_def.h |
5 | DISP_TYPE_SEMAPHORE | KSEMAPHORE | ntddk.h |
6 | DISP_TYPE_THREAD | KTHREAD | w2k_def.h |
8 | DISP_TYPE_NOTIFICATION_TIMER | KTIMER | ntddk.h |
9 | DISP_TYPE_SYNCHRONIZATION_TIMER | KTIMER | ntddk.h |
表7-2列出了到目前为止所有我知道的I/O对象。仅有前13个可以在ntddk.h中找到它们的定义,
表7-2. I/O对象汇总
ID | 类 型 | C结构 | 定 义 |
1 | IO_TYPE_AD_APTER | ADAPTER_OBJECT | |
2 | IO_TYPE_CONTROLLER | CONTROLLER_OBJECT | ntddk.h |
3 | IO_TYPE_DEVICE | DEVICE_OBJECT | ntddk.h |
4 | IO_TYPE_DRIVER | DRIVER_OBJECT | ntddk.h |
5 | IO_TYPE_FILE | FILE_OBJECT | ntddk.h |
6 | IO_TYPE_IRP | IRP | ntddk.h |
7 | IO_TYPE_MASTER_ADAPTER | | |
8 | IO_TYPE_OPEN_PACKET | | |
9 | IO_TYPE_TIMER | IO_TIMER | w2k_def.h |
10 | IO_TYPE_VPB | VPB | ntddk.h |
11 | IO_TYPE_ERROR_LOG | IO_ERROR_LOG_ENTRY | w2k_def.h |
12 | IO_TYPE_ERROR_MESSAGE | IO_ERROR_LOG_MESSAGE | ntddk.h |
13 | IO_TYPE_DEVICE_OBJECT_EXTENSION | DEVOBJ_EXTENSION | ntddk.h |
18 | IO_TYPE_APC | KAPC | ntddk.h |
19 | IO_TYPE_DPC | KDPC | ntddk.h |
20 | IO_TYPE_DEVICE_QUEUE | KDEVICE_QUEUE | ntddk.h |
21 | IO_TYPE_EVENT_PAIR | KEVENT_PAIR | w2k_def.h |
22 | IO_TYPE_INTERRUPT | KINTERRUPT | |
23 | IO_TYPE_PROFILE | KPROFILE | |
对象的表头(header)
Windows 2000对于对象体的大小和结构没有任何限制。与此相反的是,对象的表头则几乎没有什么自由可言。图7-1给出了一个对象实例的内存布局,该实例拥有完整的对象特性,并且其拥有最多的表头字段(header fields)。每一种对象特性都至少拥有一个基本的OBJECT_HEADER结构,紧随该结构之后就是对象体,在对象体之前最多可有四个可选的结构,这些结构用于提供了有关对象的附加信息。前面已经提到过,一个对象指针总是指向对象体,而不是对象的表头,因此需要在对象指针上增加一个负的偏移量才能访问表头字段。基本的表头包含有关可选表头字段的位置及其是否存在的信息,这些可选字段都位于OBJECT_HEADER结构之上,其顺序如图7-1所示。不过,这种顺序并不是强制的,你的应用程序不应该依赖这一顺序。OBJECT_HEADER结构中的信息足够定位所有的表头字段(唯一不能确定的就是它们的顺序)。唯一列外的是:如果OBJECT_CREATOR_INFO结构存在的话,那么它将总是出现在OBJECT_HEADER结构之前。
图7-1. 对象的内存布局
#define OB_FLAG_CREATE_INFO 0x01 // has OBJECT_CREATE_INFO
#define OB_FLAG_KERNEL_MODE 0x02 // created by kernel
#define OB_FLAG_CREATOR_INFO 0x04 // has OBJECT_CREATOR_INFO
#define OB_FLAG_EXCLUSIVE 0x08 // OBJ_EXCLUSIVE
#define OB_FLAG_PERMANENT 0x10 // OBJ_PERMANENT
#define OB_FLAG_SECURITY 0x20 // has security descriptor
#define OB_FLAG_SINGLE_PROCESS 0x40 // no HandleDBList
typedef struct _OBJECT_HEADER
{
/*000*/ DWORD PointerCount; // number of references
/*004*/ DWORD HandleCount; // number of open handles
/*008*/ POBJECT_TYPE ObjectType;
/*
/*00D*/ BYTE HandleDBOffset; // -> OBJECT_HANDLE_DB
/*00E*/ BYTE QuotaChargesOffset; // -> OBJECT_QUOTA_CHARGES
/*
/*010*/ union
{ // OB_FLAG_CREATE_INFO ? ObjectCreateInfo : QuotaBlock
/*010*/ PQUOTA_BLOCK QuotaBlock;
/*010*/ POBJECT_CREATE_INFO ObjectCreateInfo;
/*014*/ };
/*014*/ PSECURITY_DESCRIPTOR SecurityDescriptor;
/*018*/ }
OBJECT_HEADER,
* POBJECT_HEADER,
**PPOBJECT_HEADER;
#define OBJECT_HEADER_ \
sizeof (OBJECT_HEADER)
列表7-2. OBJECT_HEADER结构定义
列表7-2给出了OBJECT_HEADER结构的定义,该结构中的各个成员分别用于如下目的:
l PonterCount成员用于记录当前有多少指针指向此对象。这与COM中的引用计数非常类似。ntoskrnl.exe中的ObReferenceObject()、ObReferenceObjectByHandle()、ObReferenceObjectByName()和ObReferenceObjectByPointer()函数用于增加PointerCount,而ObfDereferenceObject()和ObDereferenceObject()则用于减少PointerCount。
l HandleCount成员用来记录当前有多少个打开的句柄引用了此对象。
l ObjectType成员指向一个OBJECT_TYPE结构(稍后将讨论),该结构用来代表一个类型对象,在对象的创建中将使用该结构。
l NameOffset成员给出了对象表头中的OBJECT_NAME结构的地址(用OBJECT_HEADER结构的地址减去NameOffset就可得到),如果为0,则表示对象表头中不存在OBJECT_NAME结构。
l HandleDBOffset成员给出了对象表头中的OBJECT_HANDLE_DB结构的地址(用OBJECT_HEADER结构的地址减去HandleDBOffset就可得到),如果为0,则表示对象表头中不存在OBJECT_HANDLE_DB结构。
l QuotachargesOffset成员给出了对象表头中的OBJECT_QUOTA_CHARGES结构的地址(用OBJECT_HEADER结构的地址减去QuotachargesOffset就可得到),如果为0,则表示对象表头中不存在OBJECT_QUOTA_CHARGES结构。
l ObjectFlags指出了对象的多个属性(每个属性占用一个二进制位),列表7-2的顶部列出了这些属性。如果OB_FLAG_CREATOR_INFO位被设置了,那么就意味着在对象表头中存在一个OBJECT_CREATOR_INFO结构,该结构紧挨着OBJECT_HEADER,并位于OBJECT_HEADER之前。在《Windows NT/2000 Native API Reference》中,Gray Nebbett在介绍ZwQuerySystemInformation()函数所使用的SystemObjectInformation标志时提到过对象属性,不过他的描述与本书给出的略有不同。表7-3给出了它们之间的区别。
l QuotaBlock和ObjectCreateInfo成员是互斥的(它们两个构成了一个union)。如果ObjectFlags成员的OB_FLAG_CREATE_INFO标志被设置,那么该成员(此时为ObjectCreateInfo)将包含一个指向OBJECT_CREATE_INFO结构(稍后介绍)的指针。否则,该成员(此时为QuotaBlock)将指向一个QUOTA_BLOCK结构,该结构提供了Paged和NonPaged内存池的使用量的相关信息。很多对象都将它们的QuotaBlock指针指向系统内部的PspDefaultQuotaBlock结构。该union可以为NULL。