第七章 Windows 2000的对象管理
翻译:Kendiv( fcczj@263.net )
更新:Sunday, May 22, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
线程和进程环境块
你可能会很困惑:KTHREAD和EPROCESS结构中的Teb和Peb成员有什么实际价值?Teb,指向一个线程环境块(TEB),见列表7-18。TEB的第一部分是线程信息块(Thread Information Block,NT_TIB),该结构在DDK的ntddk.h和SDK的winnt.h中均有定义,其他部分则没有相应的文档记载。Windows 2000为系统中的每个线程维护一个TEB结构。在当前进程的地址空间中,其下属线程的TEB被映射到线性地址0x7FFDE000、0x7FFDD000、0x7FFDC000以此类推,每个线程均占用一个完整的4KB页。就像在第四章提到的那样,在用户模式下仍可通过FS:0x18来访问当前线程的TEB,FS:0x18处存放的是内嵌的NT_TIB的Self成员。该成员总是指向其所在TEB的线性地址(该线性地址位于当前线程的4GB地址空间中)。
// typedef struct _NT_TIB // see winnt.h / ntddk.h
// {
// /*000*/ struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
// /*004*/ PVOID StackBase;
// /*008*/ PVOID StackLimit;
// /*00C*/ PVOID SubSystemTib;
// /*010*/ union
// {
// /*010*/ PVOID FiberData;
// /*010*/ ULONG Version;
// };
// /*014*/ PVOID ArbitraryUserPointer;
// /*018*/ struct _NT_TIB *Self;
// /*01C*/ }
// NT_TIB,
// * PNT_TIB,
// **PPNT_TIB;
// -----------------------------------------------------------------
// located at 0x7FFDE000, 0x7FFDD000, ...
typedef struct _TEB
{
/*000*/ NT_TIB Tib;
/*01C*/ PVOID EnvironmentPointer;
/*020*/ CLIENT_ID Cid;
/*028*/ HANDLE RpcHandle;
/*02C*/ PPVOID ThreadLocalStorage;
/*030*/ PPEB Peb;
/*034*/ DWORD LastErrorValue;
/*038*/ }
TEB,
* PTEB,
**PPTEB;
#define TEB_ sizeof (TEB)
列表7-18. 线程环境块(TEB)
就像每个线程都用于各自的TEB一样,每个进程也有与之相关的PEB(进程环境块)。PEB结构要比TEB复杂得多,如列表7-19所示。PEB结构包含多个指向各种子结构的指针,这些子结构又引用了更多的子结构,而且这些子结构大多都没有相应的文档。列表7-19中包含其中一些子结构的原始草图,很多成员的名字都是假设的。PEB位于线性地址:0x7FFDF000处,这意味着,TEB堆栈之后的第一个4KB页存放的就是PEB。这样系统通过当前线程的TEB结构中的Peb成员就可很容易的访问PEB结构。
typedef struct _MODULE_HEADER
{
/*000*/ DWORD d000;
/*004*/ DWORD d004;
/*008*/ LIST_ENTRY List1;
/*010*/ LIST_ENTRY List2;
/*018*/ LIST_ENTRY List3;
/*020*/ }
MODULE_HEADER,
* PMODULE_HEADER,
**PPMODULE_HEADER;
#define MODULE_HEADER_ sizeof (MODULE_HEADER)
// -----------------------------------------------------------------
typedef struct _PROCESS_MODULE_INFO
{
/*000*/ DWORD Size; // 0x24
/*004*/ MODULE_HEADER ModuleHeader;
/*024*/ }
PROCESS_MODULE_INFO,
* PPROCESS_MODULE_INFO,
**PPPROCESS_MODULE_INFO;
#define PROCESS_MODULE_INFO_ sizeof (PROCESS_MODULE_INFO)
// -----------------------------------------------------------------
// see RtlCreateProcessParameters()
typedef struct _PROCESS_PARAMETERS
{
/*000*/ DWORD Allocated;
/*004*/ DWORD Size;
/*008*/ DWORD Flags; // bit 0: all pointers normalized
/*00C*/ DWORD Reserved1;
/*010*/ LONG Console;
/*014*/ DWORD ProcessGroup;
/*018*/ HANDLE StdInput;
/*01C*/ HANDLE StdOutput;
/*020*/ HANDLE StdError;
/*024*/ UNICODE_STRING WorkingDirectoryName;
/*02C*/ HANDLE WorkingDirectoryHandle;
/*030*/ UNICODE_STRING SearchPath;
/*038*/ UNICODE_STRING ImagePath;
/*040*/ UNICODE_STRING CommandLine;
/*048*/ PWORD Environment;
/*04C*/ DWORD X;
/*050*/ DWORD Y;
/*054*/ DWORD XSize;
/*058*/ DWORD YSize;
/*05C*/ DWORD XCountChars;
/*060*/ DWORD YCountChars;
/*064*/ DWORD FillAttribute;
/*068*/ DWORD Flags2;
/*06C*/ WORD ShowWindow;
/*06E*/ WORD Reserved2;
/*070*/ UNICODE_STRING Title;
/*078*/ UNICODE_STRING Desktop;
/*080*/ UNICODE_STRING Reserved3;
/*088*/ UNICODE_STRING Reserved4;
/*090*/ }
PROCESS_PARAMETERS,
* PPROCESS_PARAMETERS,
**PPPROCESS_PARAMETERS;
#define PROCESS_PARAMETERS_ sizeof (PROCESS_PARAMETERS)
// -----------------------------------------------------------------
typedef struct _SYSTEM_STRINGS
{
/*000*/ UNICODE_STRING SystemRoot; // d:\WINNT
/*008*/ UNICODE_STRING System32Root; // d:\WINNT\System32
/*010*/ UNICODE_STRING BaseNamedObjects; // \BaseNamedObjects
/*018*/ }
SYSTEM_STRINGS,
* PSYSTEM_STRINGS,
**PPSYSTEM_STRINGS;
#define SYSTEM_STRINGS_ sizeof (SYSTEM_STRINGS)
// -----------------------------------------------------------------
typedef struct _TEXT_INFO
{
/*000*/ PVOID Reserved;
/*004*/ PSYSTEM_STRINGS SystemStrings;
/*008*/ }
TEXT_INFO,
* PTEXT_INFO,
**PPTEXT_INFO;
#define TEXT_INFO_ sizeof (TEXT_INFO)
// -----------------------------------------------------------------
// located at 0x7FFDF000
typedef struct _PEB
{
/*000*/ BOOLEAN InheritedAddressSpace;
/*001*/ BOOLEAN ReadImageFileExecOptions;
/*002*/ BOOLEAN BeingDebugged;
/*003*/ BYTE b003;
/*004*/ DWORD d004;
/*008*/ PVOID SectionBaseAddress;
/*00C*/ PPROCESS_MODULE_INFO ProcessModuleInfo;
/*010*/ PPROCESS_PARAMETERS ProcessParameters;
/*014*/ DWORD SubSystemData;
/*018*/ HANDLE ProcessHeap;
/*01C*/ PCRITICAL_SECTION FastPebLock;
/*020*/ PVOID AcquireFastPebLock; // function
/*024*/ PVOID ReleaseFastPebLock; // function
/*028*/ DWORD d028;
/*02C*/ PPVOID User32Dispatch; // function
/*030*/ DWORD d030;
/*034*/ DWORD d034;
/*038*/ DWORD d038;
/*03C*/ DWORD TlsBitMapSize; // number of bits
/*040*/ PRTL_BITMAP TlsBitMap; // ntdll!TlsBitMap
/*044*/ DWORD TlsBitMapData [2]; // 64 bits
/*04C*/ PVOID p04C;
/*050*/ PVOID p050;
/*054*/ PTEXT_INFO TextInfo;
/*058*/ PVOID InitAnsiCodePageData;
/*05C*/ PVOID InitOemCodePageData;
/*060*/ PVOID InitUnicodeCaseTableData;
/*064*/ DWORD KeNumberProcessors;
/*068*/ DWORD NtGlobalFlag;
/*06C*/ DWORD d6C;
/*070*/ LARGE_INTEGER MmCriticalSectionTimeout;
/*078*/ DWORD MmHeapSegmentReserve;
/*07C*/ DWORD MmHeapSegmentCommit;
/*080*/ DWORD MmHeapDeCommitTotalFreeThreshold;
/*084*/ DWORD MmHeapDeCommitFreeBlockThreshold;
/*088*/ DWORD NumberOfHeaps;
/*08C*/ DWORD AvailableHeaps; // 16, *2 if exhausted
/*090*/ PHANDLE ProcessHeapsListBuffer;
/*094*/ DWORD d094;
/*098*/ DWORD d098;
/*09C*/ DWORD d09C;
/*0A0*/ PCRITICAL_SECTION LoaderLock;
/*0A4*/ DWORD NtMajorVersion;
/*0A8*/ DWORD NtMinorVersion;
/*0AC*/ WORD NtBuildNumber;
/*0AE*/ WORD CmNtCSDVersion;
/*0B0*/ DWORD PlatformId;
/*0B4*/ DWORD Subsystem;
/*0B8*/ DWORD MajorSubsystemVersion;
/*0BC*/ DWORD MinorSubsystemVersion;
/*0C0*/ KAFFINITY AffinityMask;
/*0C4*/ DWORD ad0C4 [35];
/*150*/ PVOID p150;
/*154*/ DWORD ad154 [32];
/*1D4*/ HANDLE Win32WindowStation;
/*1D8*/ DWORD d1D8;
/*1DC*/ DWORD d1DC;
/*1E0*/ PWORD CSDVersion;
/*1E4*/ DWORD d1E4;
/*1E8*/ }
PEB,
* PPEB,
**PPPEB;
#define PEB_ sizeof (PEB)
列表7-18. 进程环境块(PEB)
实时访问系统中的对象(Accessing Live System Objects)
前面的章节讲解了很多理论上的知识。我们现在需要一个实际的例子来演示对象管理机制,我为此编写了一个内核对象浏览器。该程序可以展示对象在分层结构中是如何排列的,以及如何获取这些对象的某些属性。不幸的是,ntoskrnl.exe没有导出该程序所需的几个关键结构和相应的函数。这意味着即使是内核驱动程序也无法访问它们,它们成了系统的个人私藏品了。从另一方面来看,第六章介绍了如何通过计算Windows 2000的符号文件来访问这些未导出的数据和代码,这里的对象浏览器正好可以测试一下这种理论是否真的可行。很不错,第六章的符号调用接口通过了测试,w2k_obj.exe示例程序的源代码位于本书CD的\src\w2k_obj目录下。不过,该程序中最有趣的部分并不在w2k_obj.c中。第六章介绍的w2k_call.dll完成了大量的工作。因此,随后给出的示例代码均来自w2k_call.c。
枚举对象目录项
你或许知道Windows 2000 DDK提供的一个小工具软件objdir.exe,它位于\ntddk\bin目录中。objdir.exe可通过为文档化的Native API函数NtQueryDirectoryObject()(由ntdll.dll导出)获取对象目录信息。已知相反的是,我的对象浏览器w2k_obj.exe将直接访问对象目录及其目录树中的叶子对象。这听起来很疯狂,但它确实做到了。最好的证据是可同时运行在Windows 2000和Windows NT 4.0,它不依赖任何与特定版本相关的代码。不可否认,这两个版本中的对象结构稍微有些不同,但基本的模型还是一致的。提供一个可直接读取原始对象结构的程序而不是使用更高一级的API函数更能验证我们在前面提到的结构定义都是正确的。
在读取系统全局数据结构之前,最先要做的事情就是锁定它们。否则,系统可能会在另一个并发线程中改写这些数据,而我们的程序将可能会读取到无效数据或者到达一个空地址处。Windows 2000为其维护的众多内部数据项提供了大量的锁。我们面临的唯一问题是:这些锁通常都没有导出。尽管内核驱动程序可完成所有在用户模式下无法完成的工作,但它还是不能安全的访问这些未导出的数据结构。不过,第六章讨论过的扩展的内核调用接口可完成这一工作,该扩展接口实现在w2k_call.dll中,它通过从操作系统的符号文件中查找这些内部符号的地址来完成这一工作。该DLL导出了如下三个对象管理Thunks,以允许访问内核的对象目录:
1. __ObpRootDirectoryMutex()返回ERESOURCE锁的地址,该锁用于同步访问对象目录。
2. __ObpRootDirectoryObject()返回指向OBJECT_DIRECTORY结构的指针, 该结构代表对象目录的Root节点。
3. __ObpTypeDirectoryObject()返回指向OBJECT_DIRECTORY结构的指针,该结构代表对象目录中的\ObjectTypes节点。
应用程序在使用指向内核对象的指针时,必须非常小心,尤其是在获取全局锁之后。如果全局锁不能被适当的释放,那么系统将可能进入死锁状态。
尽管root目录的锁叫做ObpRootDirectoryMutex,但严格来讲它并不是一个真正的Mutex。它实际上是一个ERESOURCE而不是KMUTEX。必须使用ExAcquireResourceExclusiveLite()和ExAcquireResourceSharedLite()函数来获取这些锁。函数名中的”Lite”后缀非常重要----永远不要使用这两个函数的兄弟函数:ExAcquireResourceExclusive ()和ExAcquireResourceShared ()来获取Windows 2000或Windows NT 4.0的ERSOURCE锁。从Windows NT 3.x开始ObpRootDirectoryMutex结构就做了一些修改,而ExAcquireResourceExclusive ()和ExAcquireResourceShared ()函数仅能使用旧的ERESOURCE类型,该类型包含在w2k_def.h中(也可参考附录C)。和ExAcquireResource*Lite()极为类似的函数是ExReleaseResourceLite(),该函数的老版本为:ExReleaseResource()。再次强调不要使用没有Lite后缀的函数。
本书提供的对象浏览器的基本工作方式为:首先锁定对象目录,然后获取整个层次结构中各节点的一个快照(snapshot),最后释放目录锁,然后再显示获取的快照数据。这种处理过程,可保证对系统的干扰最少,程序可有足够的时间来显示获取到的数据,而不会长时间占用系统。必须对系统对象的机构有非常深入的理解才能获取一个可靠的目录快照,因此对象浏览器器将使用很多测试用例来保证对象信息的可靠性。这一任务可划分为如下两个基本任务:
1. 复制对象目录树的结构。这包括:复制和连接多个OBJECT_DIRECTORY结构,每个OBJECT_DIRECTORY结构都代表一个独立的非叶子节点(nonleaf node)。
2. 复制对象目录树的内容。这包括:复制OBJECT_HEADER以及叶节点的与其相关的结构。
列表7-20给出的w2kDirectoryOpen()函数执行上述第一项工作。该函数先锁住根目录树,然后开始所有的子目录及OBJECT_DIRECTORY结构。为了获取完整的目录树结构,该函数必须针对每个OBJECT_DIRECTORY子结构递归的调用。由于每个对象目录节点都包含一个Hash表,该Hash表最多可有37个表项。每个Hash表项通过一个链表可存放任意数目的对象。所以,枚举目录项需要两个嵌套的循环:外循环扫描Hash表37个表项中所有非空表象项,内循环遍历表项中的链表。这就是w2kDirectoryOpen()函数所做的一切。最后获取的数据在结构上和原始模型完全一致。基本的复制动作包括:自动分配所需的内存(由w2kSpyClone()函数完成,该函数由w2k_Call.dll导出,参见列表6-30)。列表7-20中的w2kDirectoryClose()函数则用于释放w2kDirectoryOpen()所占用的内存块。
POBJECT_DIRECTORY WINAPI
w2kDirectoryOpen (POBJECT_DIRECTORY pDir)
{
DWORD i;
PERESOURCE pLock
;
PPOBJECT_DIRECTORY_ENTRY ppEntry;
POBJECT_DIRECTORY pDir1 = NULL;
if (((pLock
= __ObpRootDirectoryMutex ()) != NULL) &&
_ExAcquireResourceExclusiveLite (pLock
, TRUE))
{
if ((pDir1 = w2kSpyClone (pDir, OBJECT_DIRECTORY_)) != NULL)
{
for (i = 0; i < OBJECT_HASH_TABLE_SIZE; i++)
{
ppEntry = pDir1->HashTable + i;
while (*ppEntry != NULL)
{
if ((*ppEntry =
w2kSpyClone (*ppEntry,
OBJECT_DIRECTORY_ENTRY_))
!= NULL)
{
(*ppEntry)->Object =
w2kObjectOpen ((*ppEntry)->Object);
ppEntry = &(*ppEntry)->NextEntry;
}
}
}
}
_ExReleaseResourceLite (pLock
);
}
return pDir1;
}
// -----------------------------------------------------------------
POBJECT_DIRECTORY WINAPI
w2kDirectoryClose (POBJECT_DIRECTORY pDir)
{
POBJECT_DIRECTORY_ENTRY pEntry, pEntry1;
DWORD i;
if (pDir != NULL)
{
for (i = 0; i < OBJECT_HASH_TABLE_SIZE; i++)
{
for (pEntry = pDir->HashTable [i];
pEntry != NULL;
pEntry = pEntry1)
{
pEntry1 = pEntry->NextEntry;
w2kObjectClose (pEntry->Object);
w2kMemoryDestroy (pEntry);
}
}
w2kMemoryDestroy (pDir);
}
return NULL;
}
列表7-20. w2kDirectoryOpen()和w2kDirectorClose()函数
仔细观察列表7-20你会发现w2kDirectoryOpen()和w2kDirectorClose()在分别在其内部调用了w2kObjectOpen()和w2kObjectClose()函数。w2kObjectOpen()负责目录复制中的第二步:复制叶子对象。w2kObjectOpen()不会进行复制完整的对象,因为我们只需要识别每个对象的类型,并从对象体中复制适当的字节数即可。w2kObjectOpen()将复制对象的完整表头,及其下属的大部分结构体,然后构建一个“假的”对象体,该对象体中包含指向实际对象体的指针,和多个指向对象表头分段副本的指针。列表7-21给出了w2kObjectOpen()构建和初始化的数据结构。W2K_OBJECT_FRAME是一个集成的数据块,它包含对象表头副本和前面提到的“假的”对象体。这个“假的”对象体由W2K_OBJECT结构表示,该结构由一组指向W2K_OBJECT_FRAME各成员的指针。w2kObjectOpen()为W2K_OBJECT_FRAME结构分配内存并用原始对象的数据对其进行初始化,最后该函数将返回一个指向W2K_OBJECT_FRAME结构的Object成员的指针。如果你还记得前面讲述的有关对象体和对象头的内容,那么很容易看出,W2K_OBJECT_FRAME实际上是对实际对象的模仿。这意味着,该结构拥有和原始对象头完全相同的对象头,应用程序可以象系统访问自己的内核对象一样,使用偏移量和标志位来访问“仿造”的OBJECT_HEADER。
typedef struct _W2K_OBJECT
{
POBJECT pObject;
POBJECT_HEADER pHeader;
POBJECT_CREATOR_INFO pCreatorInfo;
POBJECT_NAME pName;
POBJECT_HANDLE_DB pHandleDB;
POBJECT_QUOTA_CHARGES pQuotaCharges;
POBJECT_TYPE pType;
PQUOTA_BLOCK pQuotaBlock;
POBJECT_CREATE_INFO pCreateInfo;
PWORD pwName;
PWORD pwType;
}
W2K_OBJECT, *PW2K_OBJECT, **PPW2K_OBJECT;
#define W2K_OBJECT_ sizeof (W2K_OBJECT)
// -----------------------------------------------------------------
typedef struct _W2K_OBJECT_FRAME
{
OBJECT_QUOTA_CHARGES QuotaCharges;
OBJECT_HANDLE_DB HandleDB;
OBJECT_NAME Name;
OBJECT_CREATOR_INFO CreatorInfo;
OBJECT_HEADER Header;
W2K_OBJECT Object;
OBJECT_TYPE Type;
QUOTA_BLOCK QuotaBlock;
OBJECT_CREATE_INFO CreateInfo;
WORD Buffer [];
}
W2K_OBJECT_FRAME, *PW2K_OBJECT_FRAME, **PPW2K_OBJECT_FRAME;
#define W2K_OBJECT_FRAME_ sizeof (W2K_OBJECT_FRAME)
#define W2K_OBJECT_FRAME__(_n) (W2K_OBJECT_FRAME_ + ((_n) * WORD_))
列表7-21. 对象的克隆结构
我不想深入讲解w2kObjectOpen()及其子结构的细节。出于说明的目的,列表7-22给出的函数就已足够了。w2kObjectHeader()用于创建一个实际对象的OBJECT_HEADER的副本,w2kObjectCreateInfo()和w2kObjectName()则用于复制对象表头中的OBJECT_CREATOR_INFO和OBJECT_NAME结构(如果这些结构存在的话)。再次强调,w2kSpyClone()完成了大部分的主要工作。更多的信息,请参考本书光盘中的w2k_call.c源代码。
#define BACK(_p,_d) ((PVOID) ( ( (PBYTE) (_p) ) - (_d) ) )
// -----------------------------------------------------------------
POBJECT_HEADER WINAPI
w2kObjectHeader (POBJECT pObject)
{
DWORD dOffset = OBJECT_HEADER_;
POBJECT_HEADER pHeader = NULL;
if (pObject != NULL)
{
pHeader = w2kSpyClone (BACK (pObject, dOffset),
dOffset);
}
return pHeader;
}
// -----------------------------------------------------------------
POBJECT_CREATOR_INFO WINAPI
w2kObjectCreatorInfo (POBJECT_HEADER pHeader,
POBJECT pObject)
{
DWORD dOffset;
POBJECT_CREATOR_INFO pCreatorInfo = NULL;
if ((pHeader != NULL) && (pObject != NULL) &&
(pHeader->ObjectFlags & OB_FLAG_CREATOR_INFO))
{
dOffset = OBJECT_CREATOR_INFO_ + OBJECT_HEADER_;
pCreatorInfo = w2kSpyClone (BACK (pObject, dOffset),
OBJECT_CREATOR_INFO_);
}
return pCreatorInfo;
}
// -----------------------------------------------------------------
POBJECT_NAME WINAPI
w2kObjectName (POBJECT_HEADER pHeader,
POBJECT pObject)
{
DWORD dOffset;
POBJECT_NAME pName = NULL;
if ((pHeader != NULL) && (pObject != NULL) &&
(dOffset = pHeader->NameOffset))
{
dOffset += OBJECT_HEADER_;
pName = w2kSpyClone (BACK (pObject, dOffset),
OBJECT_NAME_);
}
return pName;
}
列表7-22. 用于对象克隆的辅助函数
在整个复制过程的最后,w2kDirectoryOpen()接受一个指向实时的OBJECT_DIRECTORY的节点,并返回一个副本,该副本中包含一个W2K_OBJECT指针,该指针指向的就是原始的目录对象的对象体。对象浏览器针对每一层目录都调用该函数。列表7-23给出了经过修改后的浏览器代码,将其核心部分直接暴露出来。w2k_obj.c中的原始代码包含很多容易让人分散注意力的代码。最高层的函数是DisplayObjects()。该函数需要__ObpRootDirectoryObject()为其提供一个root对象的指针,随后它将显示传入对象的类型和名称,如果传入的对象是OBJECT_DIRECTORY,它会再次调用自身(递归调用)。DisplayObject()会为每个嵌套层增加三个空格。我将列表7-23中的函数加入到了w2k_obj.c中,不过,尽管该函数可以工作,但最好不要调用它。
VOID WINAPI _DisplayObject (PW2K_OBJECT pObject,
DWORD dLevel)
{
POBJECT_DIRECTORY pDir;
POBJECT_DIRECTORY_ENTRY pEntry;
DWORD i;
for (i = 0; i < dLevel; i++) printf (L" ");
_printf (L"%+.-16s%s\r\n", pObject->pwType, pObject->pwName);
if ((!lstrcmp (pObject->pwType, L"Directory")) &&
((pDir = w2kDirectoryOpen (pObject->pObject)) != NULL))
{
for (i = 0; i < OBJECT_HASH_TABLE_SIZE; i++)
{
for (pEntry = pDir->HashTable [i];
pEntry != NULL;
pEntry = pEntry->NextEntry)
{
_DisplayObject (pEntry->Object, dLevel+1);
}
}
w2kDirectoryClose (pDir);
}
return;
}
// -----------------------------------------------------------------
VOID WINAPI _DisplayObjects (VOID)
{
PW2K_OBJECT pObject;
if ((pObject = w2kObjectOpen (__ObpRootDirectoryObject ()))
!= NULL)
{
_DisplayObject (pObject, 0);
w2kObjectClose (pObject);
}
return;
}
列表7-23. 一个非常简单的对象浏览器
示例7-2给出的对象目录的特征就是通过列表7-23中的代码获取到的。例如,\BaseNamedObjects子结构包含一个命名对象,该对象通常被多个进程共享,并可根据对象名来打开。\ObjectTypes子结构包含系统支持的所有27种OBJECT_TYPE类型对象(参考列表7-9),表7-4也列出了这些对象。
示例7-2. 对象目录的一个摘要
w2k_obj.exe是一个拥有全部功能的对象浏览器,它不仅可以以多种格式显示对象目录树,而且还允许显示对象的一些附加特性并且支持按照对象类型进行过滤。示例7-3展示w2k_obj.exe提供的多个命令行选项。
// w2k_obj.exe
// SBS Windows 2000 Object Browser V1.00
// 08-27-2000 Sven B. Schreiber
// sbs@orgon.com
Usage: w2k_obj [+-atf] [<type>] [<#>|-1] [/root] [/types]
+a -a : show/hide object addresses (default: -a)
+t -t : show/hide object type names (default: -t)
+f -f : show/hide object flags (default: -f)
<type> : show <type> objects only (default: *)
<#> : show <#> directory levels (default: -1)
-1 : show all directory levels
/root : show ObpRootDirectoryObject tree
/types : show ObpTypeDirectoryObject tree
Example: w2k_obj +atf *port 2 /root
This command displays all Port and WaitablePort objects,
starting in the root and scanning two directory levels.
Each line includes address, type, and flag information.
示例7-3. w2k_obj.exe的命令行帮助
在示例7-4中,我演示了在命令行帮助中提到的命令:w2k_obj +atf *port 2 /root。该命令将仅输出Port和WaitablePort对象,因为指定了类型过滤表达式:*port,此外还将显示对象体的地址、类型名和每项的标志。输出被限制为两个子目录层(命令行中参数2的作用)。
示例7-4. 命令:w2k_obj +atf *port 2 /root的输出结果
注意,即使与提供的类型名称模版(type name pattern)不匹配目录对象也将包含在相同的链表中,否则,将无法在目录树中找到与匹配对象相关联的节点。在第一列出现的>符号是为了在附近加的目录对象中将匹配对象类型区分出来。
我们将走向哪里?
有关Windows 2000内部的东西还有很多值得一说。但一本长度适当的书是无法容纳如此多的内容的,因此,我们必须在某个地方结束它。本书的七章内容读起来一定很费劲,或许还有些恐怖。但如果你现在能以不同的眼光看待Windows 2000的话,那么我的目的就达到了。如果你是一个程序员或调试工具开发人员,那么本书中的编程技巧和接口技术将帮助你向你的产品中加入你的竞争对手还无法提供的功能。如果你开发针对Windows 2000的其它类型的程序,那么通过理解本书讲解的系统内部机理,你将编写出更高效、更能利用操作系统特性的代码。我还希望本书能激励更多开发人员的钻研精神,从而进一步揭开笼罩在Windows 2000内核上的神秘面纱。
我从来不认为将操作系统当作一个黑盒子是一种好的编程范例,并且我现在仍然这样想。