Geoff Chappell - Software Analyst
The KTHREAD structure is the Kernel Core’s portion of the ETHREAD structure. The latter is the thread object as exposed through the Object Manager. The KTHREAD is the core of it.
The KTHREAD structure is plainly internal to the kernel and its layout varies greatly between Windows versions and even between builds. Indeed, it is the most highly variable of all significant kernel-mode structures—so much so that tracking its history looks to be imposisble on one page and is therefore spread over several:
For a good run of the early Windows versions, however, the KTHREAD is very stable. Through versions 3.51, 4.0, 5.0 and 5.1, the size hardly changes: 0x01B0 bytes until the first growth, to 0x01C0 bytes, for version 5.1. Within the structure, there is some reordering—for instance, KernelStack and Teb get swapped for version 4.0 (plausibly as a side-effect of inserting TlsArray)—but not enough to confound presentation. In the layout table below, a handful of members each make two appearances because of reordering. These duplications are indicated in the Remarks column.
The progression to version 5.2 rearranges the structure on another scale. It does happen, of course, that even large sequences are kept together. More common, however, are such examples as the single-byte members Iopl, NpxState, Saturation and Priority, which are consecutive in versions 3.51 to 5.1 but are scattered throughout the structure—to offsets 0x01B9, 0x2D, 0x010D, 0x5B, respectively—for the first build of version 5.2. Extending the layout to version 5.2 can only produce a hopeless jumble. Instead, for each member that survives to version 5.2 the Future column points the way to that member’s appearance on the separate page for early builds of version 5.2.
Offsets, types and names in the tables that follow, are from Microsoft’s symbol files for the kernel starting with Windows 2000 SP3. Since symbol files for earlier versions do not contain type information for the KTHREAD, Microsoft’s names and types are something of a guess from inspection of how the kernel in those versions uses the KTHREAD. Where use of a member corresponds closely with that of a version for which type information is available in Microsoft’s symbol files, it seems reasonable to suppose continuity. Some use, however, has no correspondence, the code having changed too much. Even where the use hasn’t changed so much, tracking down the correspondence exhaustively would be difficult, if not impossible, even with source code.
It is well known that the KTHREAD is a kernel object that can be waited on until it gets signalled, as happens when the thread ends its execution. In the DISPATCHER_HEADER at the beginning of a KTHREAD, the Type is ThreadObject (6) in the KOBJECTS enumeration. In these early versions, only the Type and Size distinguish this Header from that of any other dispatcher object.
Offset (x86) | Definition | Versions | Remarks | Past and Future |
---|---|---|---|---|
0x00 |
DISPATCHER_HEADER Header; |
3.51 to 5.1 | previously at 0x00; next at 0x00 |
|
0x10 |
LIST_ENTRY MutantListHead; |
3.10 to 5.1 | previously at 0x10; next at 0x10 |
|
0x18 |
PVOID InitialStack; |
3.51 to 5.1 | previously at 0x0168; next at 0x18 |
|
0x1C |
PVOID StackLimit; |
3.51 to 5.1 | next at 0x1C | |
0x20 (3.51) |
PVOID KernelStack; |
3.51 only | next at 0x28 | previously at 0x016C |
0x24 (3.51); 0x20 |
PVOID Teb; |
3.51 to 5.1 | previously at 0x0170; next at 0x30 |
|
0x24 |
PVOID TlsArray; |
4.0 to 5.1 | next at 0x01A4 | |
0x28 |
PVOID KernelStack; |
4.0 to 5.1 | previously at 0x20 | next at 0x20 |
0x28 (3.51); 0x2C |
BOOLEAN DebugActive; |
3.51 to 5.1 | previously at 0x0199; next at 0x03 in Header |
|
0x29 (3.51); 0x2D |
UCHAR State; |
3.51 to 5.1 | previously at 0x01A2; next at 0x2C |
|
0x2A (3.51); 0x2E |
BOOLEAN Alerted [MaximumMode]; |
3.51 to 5.1 | previously at 0x0195; next at 0x5E |
|
0x2C (3.51); 0x30 |
UCHAR Iopl; |
3.51 to 5.1 | previously at 0x01AC; next at 0x01B9 |
|
0x2D (3.51); 0x31 |
UCHAR NpxState; |
3.51 to 5.1 | previously at 0x01AD; next at 0x2D |
|
0x2E (3.51); 0x32 |
CHAR Saturation; |
3.51 to 5.1 | next at 0x010D | |
0x2F (3.51); 0x33 |
CHAR Priority; |
3.51 to 5.1 | previously at 0x01A1; next at 0x5B |
|
0x30 (3.51); 0x34 |
KAPC_STATE ApcState; |
3.51 to 5.1 | previously at 0x0130; next at 0x34 |
|
0x48 (3.51); 0x4C |
ULONG ContextSwitches; |
3.51 to 5.1 | previously at 0x0174; next at 0x28 |
|
0x50 |
UCHAR IdleSwapBlock; |
5.1 only | ||
0x51 |
UCHAR Spare0 [3]; |
5.1 only | ||
0x4C (3.51); 0x50 (4.0 to 5.0); 0x54 |
LONG WaitStatus; |
3.51 to 5.1 | previously at 0x0190; next at 0x50 |
|
0x50 (3.51); 0x54 (4.0 to 5.0); 0x58 |
KIRQL WaitIrql; |
3.51 to 5.1 | previously at 0x01A5; next at 0x2E |
|
0x51 (3.51); 0x55 (4.0 to 5.0); 0x59 |
KPROCESSOR_MODE WaitMode; |
3.51 to 5.1 | previously at 0x01A6; next at 0x2F |
|
0x52 (3.51); 0x56 (4.0 to 5.0); 0x5A |
BOOLEAN WaitNext; |
3.51 to 5.1 | previously at 0x019D; next at 0x59 |
|
0x53 (3.51); 0x57 (4.0 to 5.0); 0x5B |
UCHAR WaitReason; |
3.51 to 5.1 | previously at 0x01A7; next at 0x5A |
|
0x54 (3.51); 0x58 (4.0 to 5.0); 0x5C |
KWAIT_BLOCK *WaitBlockList; |
3.51 to 5.1 | previously at 0x018C; next at 0x54 |
|
0x58 (3.51); 0x5C (4.0 to 5.0); 0x60 |
LIST_ENTRY WaitListEntry; |
3.51 to 5.0 | previously at 0x28 | |
union { LIST_ENTRY WaitListEntry; SINGLE_LIST_ENTRY SwapListEntry; }; |
5.1 only | next at 0x60 | ||
0x60 (3.51); 0x64 (4.0 to 5.0); 0x68 |
ULONG WaitTime; |
3.51 to 5.1 | previously at 0x0180; next at 0x6C |
|
0x64 (3.51); 0x68 (4.0 to 5.0); 0x6C |
CHAR BasePriority; |
3.51 to 5.1 | previously at 0x01A9; next at 0x0110 |
|
0x65 (3.51); 0x69 (4.0 to 5.0); 0x6D |
UCHAR DecrementCount; |
3.51 to 5.1 | previously at 0x019F | |
0x66 (3.51); 0x6A (4.0 to 5.0); 0x6E |
CHAR PriorityDecrement; |
3.51 to 5.1 | previously at 0x01AA; next at 0x0112 |
|
0x67 (3.51); 0x6B (4.0 to 5.0); 0x6F |
CHAR Quantum; |
3.51 to 5.1 | previously at 0x01AB; next at 0x0113 |
|
0x68 (3.51); 0x6C (4.0 to 5.0); 0x70 |
KWAIT_BLOCK WaitBlock [5]; |
3.51 only | previously at 0xA4 | |
KWAIT_BLOCK WaitBlock [4]; |
4.0 to 5.1 | next at 0xA0 |
When version 4.0 brought most of the windowing functionality into the kernel-mode WIN32K.SYS from such user-mode modules as WINSRV.DLL in the CSRSS server process, it had much less need for fast server-client synchronisation. This had been supported by the fourth KWAIT_BLOCK in the WaitBlock array. Its removal for version 4.0 seems to have been treated as an opportunity to insert (mostly) new members.
Offset (x86) | Definition | Versions | Remarks | Future |
---|---|---|---|---|
0xCC (4.0 to 5.0); 0xD0 |
PVOID LegoData; |
4.0 to 5.1 | next at 0x01A8 | |
0xD0 (4.0 to 5.0); 0xD4 |
ULONG KernelApcDisable; |
4.0 to 5.1 | previously as UCHAR at 0x0134 | next at 0x70 |
0xD4 (4.0 to 5.0); 0xD8 |
KAFFINITY UserAffinity; |
4.0 to 5.1 | next at 0x0118 | |
0xD8 (4.0 to 5.0); 0xDC |
BOOLEAN SystemAffinityActive; |
4.0 to 5.1 | next at 0x0114 | |
0xD9 (5.0); 0xDD |
UCHAR PowerState; |
5.0 to 5.1 | next at 0x01B5 | |
0xDA (5.0); 0xDE |
KIRQL NpxIrql; |
5.0 to 5.1 | next at 0x01B6 | |
0xDF |
UCHAR InitialNode; |
5.1 only | ||
0xD9 (4.0); 0xDB (5.0) |
UCHAR Pad [7]; |
4.0 only | ||
UCHAR Pad [1]; |
5.0 only | |||
0xDC (5.0); 0xE0 |
PVOID ServiceTable; |
5.0 to 5.1 | previously at 0x0124 | next at 0x0124 |
Note that the preceding insertions for version 4.0 don’t fully reclaim the deleted KWAIT_BLOCK. It’s almost as if an effort was made to keep Queue at offset 0xE0, not that it stayed there long.
Offset (x86) | Definition | Versions | Remarks | Past and Future |
---|---|---|---|---|
0xE0 (3.51 to 5.0); 0xE4 |
KQUEUE *Queue; |
3.51 to 5.1 | next at 0x68 | |
0xE4 (3.51 to 5.0); 0xE8 |
apparently unused 4 bytes | 3.51 only | ||
KSPIN_LOCK ApcQueueLock; |
4.0 to 5.1 | next at 0x4C | ||
0xE8 (3.51 to 5.0); 0xF0 |
KTIMER Timer; |
3.51 to 5.1 | previously at 0x38; next at 0x78 |
|
0x0110 (3.51 to 5.0); 0x0118 |
LIST_ENTRY QueueListEntry; |
3.51 to 5.1 | next at 0x0100 | |
0x0120 |
ULONG SoftAffinity; |
5.1 only | ||
0x0118 (3.51 to 5.0); 0x0124 |
KAFFINITY Affinity; |
3.51 to 5.1 | previously at 0x0188; next at 0x0120 |
|
0x011C (3.51 to 5.0); 0x0128 |
BOOLEAN Preempted; |
3.51 to 5.1 | previously at 0x019A; next at 0x010A |
|
0x011D (3.51 to 5.0); 0x0129 |
BOOLEAN ProcessReadyQueue; |
3.51 to 5.1 | previously at 0x019B; next at 0x010B |
|
0x011E (3.51 to 5.0); 0x012A |
BOOLEAN KernelStackResident; |
3.51 to 5.1 | previously at 0x019C; next at 0x010C |
|
0x011F (3.51 to 5.0); 0x012B |
UCHAR NextProcessor; |
3.51 to 5.1 | previously at 0x01A0; next at 0x010F |
|
0x0120 (3.51 to 5.0); 0x012C |
PVOID CallbackStack; |
3.51 to 5.1 | next at 0x0148 | |
0x0124 (3.51 to 5.0); 0x0130 |
PVOID ServiceTable; |
3.51 only | next at 0xDC | |
PVOID Win32Thread; |
4.0 to 5.1 | next at 0x014C |
That ServiceTable changes to Win32Thread for version 4.0 is more natural than first seems. The one pointer serves both purposes. What Win32Thread points to, if anything, is a THREADINFO. This is what WIN32K keeps for a thread that has become a GUI thread. As first introduced, it begins with the thread’s service table.
Offset (x86) | Definition | Versions | Remarks | Future |
---|---|---|---|---|
0x0128 (3.51 to 5.0); 0x0134 |
KTRAP_FRAME *TrapFrame; |
3.51 to 5.1 | next at 0x0150 | |
0x012C (3.51 to 5.0); 0x0138 |
KAPC_STATE *ApcStatePointer [2]; |
3.51 to 5.1 | previously at 0x0160; next at 0x0128 |
|
0x0134 (3.51) |
UCHAR KernelApcDisable; |
3.51 only | next as ULONG at 0xD0 | previously at 0x01AE |
0x0134 (5.0); 0x0140 |
KPROCESSOR_MODE PreviousMode; |
5.0 to 5.1 | previously at 0x0137 | next at 0x0115 |
0x0134 (4.0); 0x0135 (5.0); 0x0141 |
BOOLEAN EnableStackSwap; |
4.0 to 5.1 | next at 0x5C | |
0x0135 (3.51 to 4.0); 0x0136 (5.0); 0x0142 |
BOOLEAN LargeStack; |
3.51 to 5.1 | next at 0x01B4 | |
0x0136 (3.51 to 4.0); 0x0137 (5.0); 0x0143 |
apparently unused byte | 3.51 only | ||
UCHAR ResourceIndex; |
4.0 to 5.1 | next at 0x0115 | ||
0x0137 (3.51 to 4.0) |
KPROCESSOR_MODE PreviousMode; |
3.10 to 4.0 | next at 0x0134 | previously at 0x01A8 |
0x0138 (3.51 to 5.0); 0x0144 |
ULONG KernelTime; |
3.51 to 5.1 | previously at 0x30; next at 0x0154 |
|
0x013C (3.51 to 5.0); 0x0148 |
ULONG UserTime; |
3.51 to 5.1 | previously at 0x34; next at 0x0158 |
|
0x0140 (3.51 to 5.0); 0x014C |
KAPC_STATE SavedApcState; |
3.51 to 5.1 | previously at 0x0148; next at 0x0130 |
|
0x0158 (3.51 to 5.0); 0x0164 |
BOOLEAN Alertable; |
3.51 to 5.1 | previously at 0x0194; next at 0x58 |
|
0x0159 (3.51 to 5.0); 0x0165 |
UCHAR ApcStateIndex; |
3.51 to 5.1 | previously at 0x019E; next at 0x0108 |
|
0x015A (3.51 to 5.0); 0x0166 |
BOOLEAN ApcQueueable; |
3.51 to 5.1 | previously at 0x0197; next at 0x0109 |
|
0x015B (3.51 to 5.0); 0x0167 |
BOOLEAN AutoAlignment; |
3.51 to 5.1 | previously at 0x0198; next at 0x01B8 |
|
0x015C (3.51 to 5.0); 0x0168 |
PVOID StackBase; |
3.51 to 5.1 | next at 0x015C | |
0x0160 (3.51 to 5.0); 0x016C |
KAPC SuspendApc; |
3.51 to 5.1 | previously at 0x60; next at 0x0160 |
|
0x0190 (3.51 to 5.0); 0x019C |
KSEMAPHORE SuspendSemaphore; |
3.51 to 5.1 | previously at 0x90; next at 0x0190 |
|
0x01A4 (3.51 to 5.0); 0x01B0 |
LIST_ENTRY ThreadListEntry; |
3.51 to 5.1 | previously at 0x20; next at 0x01AC |
|
0x01AC (3.51 to 5.0); 0x01B8 |
CHAR FreezeCount; |
3.51 to 5.1 | previously at 0x01A3; next at 0x01BA |
|
0x01AD (3.51 to 5.0); 0x01B9 |
CHAR SuspendCount; |
3.51 to 5.1 | last member in 3.51 | previously at 0x01A4; next at 0x01BB |
0x01AE (4.0 to 5.0); 0x01BA |
UCHAR IdealProcessor; |
4.0 to 5.1 | next at 0x010E | |
0x01AF (4.0 to 5.0); 0x01BB |
BOOLEAN DisableBoost; |
4.0 to 5.1 | last member in 4.0 to 5.1 | next at 0x0117 |