第七章 Windows 2000的对象管理
翻译:Kendiv( fcczj@263.net )
更新:Sunday, May 22, 2005
声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。
进程和线程对象
或许最吸引人同时也是最复杂的Windows 2000对象就是进程、线程对象了。这些对象通常是软件开发人员必须处理的顶层对象。一个内核模式组件总是运行在某个线程的上下文中,而一个线程也总是属于某个进程。因此,从本质上来看,进城和线程对象都是类型对象,在调试过程中它们被研究的频率最高。Windows 2000内核调试器解决了这一问题,它提供了两个命令:!processfields和!threadfields(译注,最新版的内核调试器已不在支持这两个命令,对应的新命令为:dt nt!_EPROCESS和dt nt!_ETHREAD),这两个命令由kdextx86.dll导出。这两个命令可针对EPROCESS和ETHREAD结构,分别输出由名称/偏移量构成的简单列表(参考第一章的示例1-1和示例1-2)。这两个结构体都没有文档化,这两个命令是目前唯一的官方信息来源。
译注:
在Windows XP Professional + SP2下,HANDLE_TABLE结构发生了很大的变化,如下所示:
kd> dt nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x02c ExtraInfoPages : Int4B
+0x030 FirstFree : Uint4B
+0x034 LastFree : Uint4B
+0x038 NextHandleNeedingPool : Uint4B
+0x03c HandleCount : Int4B
+0x040 Flags : Uint4B
+0x040 StrictFIFO : Pos 0, 1 Bit
可以预见本章所给的w2k_obj.exe在Windows XP下将无法工作。
很不幸,!processfields命令的输出(参见第一章中的示例1-1)中最开始的是一个名为Pcb的成员,该成员指向一个0x6C字节大小的子结构(这是因为下一个成员ExitStatus的偏移量为0x6c)。Pcb是一个KPROCESS结构,该结构没有任何文档记载。这种排列非常有趣:显然,一个进程可由嵌入在庞大的可执行对象中的一个很小的内核对象来描述。这种嵌套式的结构也出现在线程对象中。从调试器的!threadfields命令(参见第一章中的示例1-2)的输出可以看到在ETHREAD结构的开始位置存在一个大小为0x1B0字节的Tcb成员。这就是KTHREAD结构,它代表可执行对象中的另一个内核对象。
译注:
本书中所给的EPROCESS和ETHREAD结构的定义,在Windows XP+SP2中已发生了很大变化。
尽管内核调试器提供的有关进程和线程对象的符号化信息非常有帮助,但没有提供足够的信息来说明这些结构体成员的类型。而且,Pcb和Tcb成员的不透明性进一步加大了掌握这些对象的本质的难度。在内核调试器生成的反汇编列表中,你会发现有很多指令涉及到这些不透明的数据结构。它们使用的偏移量对于我们来说毫无价值,它无法提供任何有关这些数据的名字和类型的信息。因此,我收集了各方面的资料再加上我自己的研究结果,以推断出这些对象的具体类型。列表7-11和列表7-12给出部分研究结果,这两个列表分别给出了KPROCESS和KTHREAD结构的定义。在这两个结构体开始处的DISPATCHER_HEADER结构明确表示了这两个结构体都属于Dispatcher对象。这意味着可使用KeWaitForSingleObject()和KeWaitForMultipleObjects()来等待它们。当线程结束执行时,线程对象才会变为“有信号”状态,一个进程仅当其所有线程都终止时才会进入“有信号”状态。这对于Win32程序员来说没什么新鲜的。不过,现在你终于明白了为什么等待一个进程和线程对象是可行的了。
typedef struct _KPROCESS
{
/*000*/ DISPATCHER_HEADER Header; // DO_TYPE_PROCESS (0x1B)
/*010*/ LIST_ENTRY ProfileListHead;
/*018*/ DWORD DirectoryTableBase;
/*01C*/ DWORD PageTableBase;
/*020*/ KGDTENTRY LdtDescriptor;
/*028*/ KIDTENTRY Int21Descriptor;
/*030*/ WORD IopmOffset;
/*032*/ BYTE Iopl;
/*033*/ BOOLEAN VdmFlag;
/*034*/ DWORD ActiveProcessors;
/*038*/ DWORD KernelTime; // ticks
/*03C*/ DWORD UserTime; // ticks
/*040*/ LIST_ENTRY ReadyListHead;
/*048*/ LIST_ENTRY SwapListEntry;
/*050*/ LIST_ENTRY ThreadListHead; // KTHREAD.ThreadListEntry
/*058*/ PVOID ProcessLock;
/*05C*/ KAFFINITY Affinity;
/*060*/ WORD StackCount;
/*062*/ BYTE BasePriority;
/*063*/ BYTE ThreadQuantum;
/*064*/ BOOLEAN AutoAlignment;
/*065*/ BYTE State;
/*066*/ BYTE ThreadSeed;
/*067*/ BOOLEAN DisableBoost;
/*068*/ DWORD d068;
/*06C*/ }
KPROCESS,
* PKPROCESS,
**PPKPROCESS;
#define KPROCESS_ sizeof (KPROCESS)
列表7-11. KPROCESS结构
typedef struct _KTHREAD
{
/*000*/ DISPATCHER_HEADER Header; // DO_TYPE_THREAD (0x6C)
/*010*/ LIST_ENTRY MutantListHead;
/*018*/ PVOID InitialStack;
/*01C*/ PVOID StackLimit;
/*020*/ struct _TEB *Teb;
/*024*/ PVOID TlsArray;
/*028*/ PVOID KernelStack;
/*02C*/ BOOLEAN DebugActive;
/*02D*/ BYTE State; // THREAD_STATE_*
/*02E*/ BOOLEAN Alerted;
/*02F*/ BYTE
bReserved01;
/*030*/ BYTE Iopl;
/*031*/ BYTE NpxState;
/*032*/ BYTE Saturation;
/*033*/ BYTE Priority;
/*034*/ KAPC_STATE ApcState;
/*04C*/ DWORD ContextSwitches;
/*050*/ DWORD WaitStatus;
/*054*/ BYTE WaitIrql;
/*055*/ BYTE WaitMode;
/*056*/ BYTE WaitNext;
/*057*/ BYTE WaitReason;
/*058*/ PLIST_ENTRY WaitBlockList;
/*05C*/ LIST_ENTRY WaitListEntry;
/*064*/ DWORD WaitTime;
/*068*/ BYTE BasePriority;
/*069*/ BYTE DecrementCount;
/*06A*/ BYTE PriorityDecrement;
/*06B*/ BYTE Quantum;
/*06C*/ KWAIT_BLOCK WaitBlock [4];
/*0CC*/ DWORD LegoData;
/*0D0*/ DWORD KernelApcDisable;
/*0D4*/ KAFFINITY UserAffinity;
/*0D8*/ BOOLEAN SystemAffinityActive;
/*0D9*/ BYTE Pad [3];
/*0DC*/ PSERVICE_DESCRIPTOR_TABLE pServiceDescriptorTable;
/*0E0*/ PVOID Queue;
/*0E4*/ PVOID ApcQueueLock;
/*0E8*/ KTIMER Timer;
/*110*/ LIST_ENTRY QueueListEntry;
/*118*/ KAFFINITY Affinity;
/*11C*/ BOOLEAN Preempted;
/*11D*/ BOOLEAN ProcessReadyQueue;
/*11E*/ BOOLEAN KernelStackResident;
/*11F*/ BYTE NextProcessor;
/*120*/ PVOID CallbackStack;
/*124*/ struct _WIN32_THREAD *Win32Thread;
/*128*/ PVOID TrapFrame;
/*12C*/ PKAPC_STATE ApcStatePointer;
/*130*/ PVOID p130;
/*134*/ BOOLEAN EnableStackSwap;
/*135*/ BOOLEAN LargeStack;
/*136*/ BYTE ResourceIndex;
/*137*/ KPROCESSOR_MODE PreviousMode;
/*138*/ DWORD KernelTime; // ticks
/*13C*/ DWORD UserTime; // ticks
/*140*/ KAPC_STATE SavedApcState;
/*157*/ BYTE
bReserved02;
/*158*/ BOOLEAN Alertable;
/*159*/ BYTE ApcStateIndex;
/*15A*/ BOOLEAN ApcQueueable;
/*15B*/ BOOLEAN AutoAlignment;
/*15C*/ PVOID StackBase;
/*160*/ KAPC SuspendApc;
/*190*/ KSEMAPHORE SuspendSemaphore;
/*1A4*/ LIST_ENTRY ThreadListEntry; // see KPROCESS
/*1AC*/ BYTE FreezeCount;
/*1AD*/ BYTE SuspendCount;
/*1AE*/ BYTE IdealProcessor;
/*1AF*/ BOOLEAN DisableBoost;
/*1B0*/ }
KTHREAD,
* PKTHREAD,
**PPKTHREAD;
#define KTHREAD_ sizeof (KTHREAD)
列表7-12. KTHREAD结构
KPROCESS结构通过自己的ThreadListHead成员来组织它下属的线程对象,对于由KTHREAD对象构成的双向链表来说,ThreadListHead既是该链表的起点也是该链表的终点(呵呵,又是一个环形链表)。每个线程也有一个类似的结构来保存各自的对象,那就是它们的ThreadListEntry成员,线程自己的链表是由一个个LIST_ENTRY结构组成。要得到这一个个LIST_ENTRY所在的对象的基地址,则必须从LIST_ENTRY结构的地址值中减去其在所属结构中的偏移量才能得到,这是因为LIST_ENTRY结构的Flink和Blink成员总是指向链表中的下一个LIST_ENTRY结构,而不是拥有该节点结构的对象。这种特性可以很容易的将同一个对象加入到多个链表中而不会产生冲突。
在列表7-11、列表7-12以及下面即将给出的列表中,你会发现一个特殊的成员,该成员的名字由一个小写字符和三个十六进制数组成。这些成员的确切含义我目前还不清楚。开始的第一个字符反映出了该成员可能的类型(比如,d表示DWORD,p表示PVOID),随后的数字表示该成员相对于结构基地址的偏移量。
列表7-13和列表7-14分别给出了EPROCESS和ETHREAD可执行对象。这些结构包含一些还未确定的成员,希望在本书的鼓励下,有人能把它们确定下来。不过,最重要和最常用的成员都已确定下来,至少我们知道丢失了那些信息。
typedef struct _EPROCESS
{
/*000*/ KPROCESS Pcb;
/*06C*/ NTSTATUS ExitStatus;
/*070*/ KEVENT LockEvent;
/*080*/ DWORD LockCount;
/*084*/ DWORD d084;
/*088*/ LARGE_INTEGER CreateTime;
/*090*/ LARGE_INTEGER ExitTime;
/*098*/ PVOID LockOwner;
/*09C*/ DWORD UniqueProcessId;
/*0A0*/ LIST_ENTRY ActiveProcessLinks;
/*0A8*/ DWORD QuotaPeakPoolUsage [2]; // NP, P
/*0B0*/ DWORD QuotaPoolUsage [2]; // NP, P
/*0B8*/ DWORD PagefileUsage;
/*0BC*/ DWORD CommitCharge;
/*0C0*/ DWORD PeakPagefileUsage;
/*0C4*/ DWORD PeakVirtualSize;
/*0C8*/ LARGE_INTEGER VirtualSize;
/*0D0*/ MMSUPPORT Vm;
/*100*/ DWORD d100;
/*104*/ DWORD d104;
/*108*/ DWORD d108;
/*10C*/ DWORD d10C;
/*110*/ DWORD d110;
/*114*/ DWORD d114;
/*118*/ DWORD d118;
/*11C*/ DWORD d11C;
/*120*/ PVOID DebugPort;
/*124*/ PVOID ExceptionPort;
/*128*/ PHANDLE_TABLE ObjectTable;
/*12C*/ PVOID Token;
/*130*/ FAST_MUTEX WorkingSetLock;
/*150*/ DWORD WorkingSetPage;
/*154*/ BOOLEAN ProcessOutswapEnabled;
/*155*/ BOOLEAN ProcessOutswapped;
/*156*/ BOOLEAN AddressSpaceInitialized;
/*157*/ BOOLEAN AddressSpaceDeleted;
/*158*/ FAST_MUTEX AddressCreationLock;
/*178*/ KSPIN_LOCK HyperSpaceLock;
/*17C*/ DWORD ForkInProgress;
/*180*/ WORD VmOperation;
/*182*/ BOOLEAN ForkWasSuccessful;
/*183*/ BYTE MmAgressiveWsTrimMask;
/*184*/ DWORD VmOperationEvent;
/*188*/ HARDWARE_PTE PageDirectoryPte;
/*18C*/ DWORD LastFaultCount;
/*190*/ DWORD ModifiedPageCount;
/*194*/ PVOID VadRoot;
/*198*/ PVOID VadHint;
/*19C*/ PVOID CloneRoot;
/*1A0*/ DWORD NumberOfPrivatePages;
/*1A4*/ DWORD NumberOfLockedPages;
/*1A8*/ WORD NextPageColor;
/*1AA*/ BOOLEAN ExitProcessCalled;
/*1AB*/ BOOLEAN CreateProcessReported;
/*1AC*/ HANDLE SectionHandle;
/*1B0*/ struct _PEB *Peb;
/*1B4*/ PVOID SectionBaseAddress;
/*1B8*/ PQUOTA_BLOCK QuotaBlock;
/*1BC*/ NTSTATUS LastThreadExitStatus;
/*1C0*/ DWORD WorkingSetWatch;
/*1C4*/ HANDLE Win32WindowStation;
/*1C8*/ DWORD InheritedFromUniqueProcessId;
/*1CC*/ ACCESS_MASK GrantedAccess;
/*1D0*/ DWORD DefaultHardErrorProcessing; // HEM_*
/*1D4*/ DWORD LdtInformation;
/*1D8*/ PVOID VadFreeHint;
/*1DC*/ DWORD VdmObjects;
/*1E0*/ PVOID DeviceMap; // 0x24 bytes
/*1E4*/ DWORD SessionId;
/*1E8*/ DWORD d1E8;
/*1EC*/ DWORD d1EC;
/*1F0*/ DWORD d1F0;
/*1F4*/ DWORD d1F4;
/*1F8*/ DWORD d1F8;
/*1FC*/ BYTE ImageFileName [16];
/*20C*/ DWORD VmTrimFaultValue;
/*210*/ BYTE SetTimerResolution;
/*211*/ BYTE PriorityClass;
/*212*/ union
{
struct
{
/*212*/ BYTE SubSystemMinorVersion;
/*213*/ BYTE SubSystemMajorVersion;
};
struct
{
/*212*/ WORD SubSystemVersion;
};
};
/*214*/ struct _WIN32_PROCESS *Win32Process;
/*218*/ DWORD d218;
/*21C*/ DWORD d21C;
/*220*/ DWORD d220;
/*224*/ DWORD d224;
/*228*/ DWORD d228;
/*22C*/ DWORD d22C;
/*230*/ PVOID Wow64;
/*234*/ DWORD d234;
/*238*/ IO_COUNTERS IoCounters;
/*268*/ DWORD d268;
/*26C*/ DWORD d26C;
/*270*/ DWORD d270;
/*274*/ DWORD d274;
/*278*/ DWORD d278;
/*27C*/ DWORD d27C;
/*280*/ DWORD d280;
/*284*/ DWORD d284;
/*288*/ }
EPROCESS,
* PEPROCESS,
**PPEPROCESS;
#define EPROCESS_ sizeof (EPROCESS)
列表7-13. EPROCESS结构
typedef struct _ETHREAD
{
/*000*/ KTHREAD Tcb;
/*1B0*/ LARGE_INTEGER CreateTime;
/*1B8*/ union
{
/*1B8*/ LARGE_INTEGER ExitTime;
/*1B8*/ LIST_ENTRY LpcReplyChain;
};
/*1C0*/ union
{
/*1C0*/ NTSTATUS ExitStatus;
/*1C0*/ DWORD OfsChain;
};
/*1C4*/ LIST_ENTRY PostBlockList;
/*1CC*/ LIST_ENTRY TerminationPortList;
/*1D4*/ PVOID ActiveTimerListLock;
/*1D8*/ LIST_ENTRY ActiveTimerListHead;
/*1E0*/ CLIENT_ID Cid;
/*1E8*/ KSEMAPHORE LpcReplySemaphore;
/*1FC*/ DWORD LpcReplyMessage;
/*200*/ DWORD LpcReplyMessageId;
/*204*/ DWORD PerformanceCountLow;
/*208*/ DWORD ImpersonationInfo;
/*20C*/ LIST_ENTRY IrpList;
/*214*/ PVOID TopLevelIrp;
/*218*/ PVOID DeviceToVerify;
/*21C*/ DWORD ReadClusterSize;
/*220*/ BOOLEAN ForwardClusterOnly;
/*221*/ BOOLEAN DisablePageFaultClustering;
/*222*/ BOOLEAN DeadThread;
/*223*/ BOOLEAN Reserved;
/*224*/ BOOL HasTerminated;
/*228*/ ACCESS_MASK GrantedAccess;
/*22C*/ PEPROCESS ThreadsProcess;
/*230*/ PVOID StartAddress;
/*234*/ union
{
/*234*/ PVOID Win32StartAddress;
/*234*/ DWORD LpcReceivedMessageId;
};
/*238*/ BOOLEAN LpcExitThreadCalled;
/*239*/ BOOLEAN HardErrorsAreDisabled;
/*23A*/ BOOLEAN LpcReceivedMsgIdValid;
/*23B*/ BOOLEAN ActiveImpersonationInfo;
/*23C*/ DWORD PerformanceCountHigh;
/*240*/ DWORD d240;
/*244*/ DWORD d244;
/*248*/ }
ETHREAD,
* PETHREAD,
**PPETHREAD;
#define ETHREAD_ sizeof (ETHREAD)
列表7-14. ETHREAD结构
除!processfields和!threadfields命令列出的成员之外,在EPROCESS和ETHREAD结构中实际上还包含一些附加成员。有两种主要的方式可发现有关未文档化成员的详细信息。其一是:观察系统函数是如何访问这些对象成员的;其二是:检查对象是如何被创建并被初始化的。第二种方法可以获得对象的实际大小。基本的对象创建函数是ntoskrnl.exe导出的ObCreateObject()函数,该函数为对象表头和对象体分配内存,并初始化常见的对象参数。不过,ObCreateObject()对它创建的对象是什么类型却丝毫不知,因此,调用者必须给定对象体所需内存的确切字节数。因此,找出对象实际大小这一问题就转化为针对此类对象所调用的ObCreateObject()。进程对象是由Native API函数NtCreateProcess()创建的,而NtCreateProcess()又调用PspCreateProcess()来完成实际工作。在PspCreateProcess()函数中,可找到调用ObCreateObject()的地址,在这里你会发现所需的对象体的大小为0x288字节(即648字节)。这就是为什么在列表7-13中会包含几个没有确切名称的成员的原因,就是为了让对象大小达到0x288。ETHREAD结构也是如此:NtCreateThread()函数调用PspCreateThread(),后者在转而调用ObCreateObject()以获取大小为0x248字节的对象。
当前正在运行的进程使用EPROCESS结构中的ActiveProcessLinks成员互相链接起来构成了一个链表。该链表的表头保存在全局变量PsActiveProcessHead中,与该链表相关的同步对象是类型为FAST_MUTEX的PspActiveProcessMutex。很不幸,ntoskrnl.exe并未导出PsActiveProcessHead变量,但它导出了一个名为PsInitialSystemProcess的变量,该变量实际上是一个指向进程ID为8的系统进程的EPROCESS结构。通过该结构的ActiveProcessLinks成员的Blink指针就可找到PsActiveProcessHead。图7-3给出了进程和线程链接构成的结构图。图7-3只是一个简化图,它给出的进程链表中仅包含两项。在实际情况下,这个链表会非常的长。为了使该图简洁明了,我仅给出了一个进程的线程链表,并假设该进程只有两个线程。
从列表7-12和列表7-13可看出,在内核和执行体之上还存在一个进程和线程对象,它们分别指向位于EPROCESS和KTHREAD中的WIN32_PROCESS和WIN32_THREAD结构。这些未文档化的结构构成了Win32子系统中的进程和线程对象。尽管这些结构体的某些成员的含义非常明显,但它们仍包含很多用意不明的成员。这或许是将来要探索的领域了。
图7-3. 进程和线程对象链表
线程和进程的上下文(Context)
当系统执行代码时,执行将总是在该进程的某个线程的上下文中进行。在很多情况下,系统必须从当前上下文中寻找与线程或进程相关的信息。因此,系统总是将当前线程的指针保存在一个内核处理器控制块(Kernel’s Processor Control Block,KPRCB)中。该结构定义于ntddk.h中,列表7-15给出了该结构。
typedef struct _KPRCB // processor control block
{
/*000*/ WORD MinorVersion;
/*002*/ WORD MajorVersion;
/*004*/ struct _KTHREAD *CurrentThread;
/*008*/ struct _KTHREAD *NextThread;
/*00C*/ struct _KTHREAD *IdleThread;
/*010*/ CHAR Number;
/*011*/ CHAR Reserved;
/*012*/ WORD BuildType;
/*014*/ KAFFINITY SetMember;
/*018*/ struct _RESTART_BLOCK *RestartBlock;
/*01C*/ }
KPRCB,
* PKPRCB,
**PPKPRCB;
#define KPRCB_ sizeof (KPRCB)
列表7-15. 内核处理器控制块(KPRCB)
在线性地址0xFFDFF120处可找到KPRCB结构,指向该结构的指针存放在KPCR结构(Kernel’s Processor Control Region)的Prcb成员中。KPCR结构的定义可在Ntddk.h中找到,该结构位于线性地址0xFFDFF000处。就像在第四章解释的那样,内核模块通过FS寄存器可以很容易的访问该结构体。从地址:FS:0处读取等价于从线性地址DS:0xFFDFF000处读取。系统将最基本的CPU信息保存在地址0xFFDFF13C处(紧随KPRCB结构之后)的CONTEXT结构(见列表7-17)中。
typedef struct _KPCR // processor control region
{
/*000*/ NT_TIB NtTib;
/*01C*/ struct _KPCR *SelfPcr;
/*020*/ PKPRCB Prcb;
/*024*/ KIRQL Irql;
/*028*/ DWORD IRR;
/*02C*/ DWORD IrrActive;
/*030*/ DWORD IDR;
/*034*/ DWORD Reserved2;
/*038*/ struct _KIDTENTRY *IDT;
/*03C*/ struct _KGDTENTRY *GDT;
/*040*/ struct _KTSS *TSS;
/*044*/ WORD MajorVersion;
/*046*/ WORD MinorVersion;
/*048*/ KAFFINITY SetMember;
/*04C*/ DWORD StallScaleFactor;
/*050*/ BYTE DebugActive;
/*051*/ BYTE Number;
/*054*/ }
KPCR,
* PKPCR,
**PPKPCR;
#define KPCR_ sizeof (KPCR)
列表7-16. 内核处理器控制区域(KPCR)
#define SIZE_OF_80387_REGISTERS 80
typedef struct _FLOATING_SAVE_AREA
{
/*000*/ DWORD ControlWord;
/*004*/ DWORD StatusWord;
/*008*/ DWORD TagWord;
/*00C*/ DWORD ErrorOffset;
/*010*/ DWORD ErrorSelector;
/*014*/ DWORD DataOffset;
/*018*/ DWORD DataSelector;
/*01C*/ BYTE RegisterArea [SIZE_OF_80387_REGISTERS];
/*06C*/ DWORD Cr0NpxState;
/*070*/ }
FLOATING_SAVE_AREA,
* PFLOATING_SAVE_AREA,
**PPFLOATING_SAVE_AREA;
#define FLOATING_SAVE_AREA_ sizeof (FLOATING_SAVE_AREA)
#define MAXIMUM_SUPPORTED_EXTENSION 512
typedef struct _CONTEXT
{
/*000*/ DWORD ContextFlags;
/*004*/ DWORD Dr0;
/*008*/ DWORD Dr1;
/*00C*/ DWORD Dr2;
/*010*/ DWORD Dr3;
/*014*/ DWORD Dr6;
/*018*/ DWORD Dr7;
/*01C*/ FLOATING_SAVE_AREA FloatSave;
/*08C*/ DWORD SegGs;
/*090*/ DWORD SegFs;
/*094*/ DWORD SegEs;
/*098*/ DWORD SegDs;
/*09C*/ DWORD Edi;
/*0A0*/ DWORD Esi;
/*0A4*/ DWORD Ebx;
/*0A8*/ DWORD Edx;
/*0AC*/ DWORD Ecx;
/*0B0*/ DWORD Eax;
/*0B4*/ DWORD Ebp;
/*0B8*/ DWORD Eip;
/*0BC*/ DWORD SegCs;
/*0C0*/ DWORD EFlags;
/*0C4*/ DWORD Esp;
/*0C8*/ DWORD SegSs;
/*0CC*/ BYTE ExtendedRegisters [MAXIMUM_SUPPORTED_EXTENSION];
/*2CC*/ }
CONTEXT,
* PCONTEXT,
**PPCONTEXT;
#define CONTEXT_ sizeof (CONTEXT)
列表7-17 CPU的CONTEXT和FLOATING_SAVE_AREA结构
对照列表7-15,可看出KPRCB结构包含三个指向KTHREAD结构的指针,其偏移量分别为:0x004、0x008和0x00C:
1. CurrentThread指向当前正在执行的线程的KTHREAD对象。内核代码经常访问该成员。
2. NextThread指向在下一次上下文切换后将要运行的线程的KTHREAD对象。
3. IdleThread指向空闲线程的KTHREAD对象,当没有线程准备好去执行时,该线程将执行后台任务。系统为每个已安装CPU提供了一个专用的空闲线程。在单处理器系统中,唯一的空闲线程对象被称作POBootThread,并且它是PsIdleProcess对象的线程链表中仅有的一个线程。
由于ETHREAD结构中的第一个成员是KTHREAD,而KTHREAD指针又总是指向ETHREAD。所以KTHREAD和ETHREAD之间是可以相互进行类型转化的。KPROCESS和EPROCESS也是如此。
由于Windows 2000内核将线性地址0xFFDFF000映射到了CPU的FS的内核模式段的0x00000000,所以系统总是能在地址:FS:0x0、FS:0x120和FS:13C处找到当前的KPCR、KPRCB和CONTEXT结构。当你在调试器中反编译内核代码时,你会发现系统经常从FS:0x124处取出一个指针,很明显,该指针指向的是当前的线程对象。示例7-1给出了内核调试器命令:u PsGetCurrentProcessId的执行结果,该命令将使内核调试器从PsGetCurrentProcessId所处的地址开始反编译10行代码。可看出,PsGetCurrentProcessId()函数只是简单的取出当前线程的KTHREAD/ETHREAD结构,然后返回结构中偏移量为0x1E0的数值,此处恰是CLIENT_ID类型的cid成员(属于ETHREAD结构)的UniqueProcessID(参见列表7-14)。PsGetCurrentThreadId()与之类似,不同之处是它在偏移量0x1E4处取出UniqueThreadID。在第二章的列表2-8中有CLIENT_ID结构的定义。
kd> u PsGetCurrentProcessId
ntoskrnl!PsGetCurrentProcessId:
8052ba52 64a124010000 mov eax,fs:[00000124]
8052ba58 8b80e0010000 mov eax,[eax+0x1e0]
8052ba5e c3 ret
8052ba5f cc int 3
ntoskrnl!PsGetCurrentThreadId:
8052ba60 64a124010000 mov eax,fs:[00000124]
8052ba66 8b80e4010000 mov eax,[eax+0x1e4]
8052ba6c c3 ret
8052ba6d cc int 3
示例7-1. 获取进程和线程ID
有时,系统需要当前线程所属进程对象的指针。可通过当前KTHREAD结构中的ApcState子结构的Process成员来获取该指针。