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:
Though the KTHREAD had been relatively stable through versions 3.51 to 5.1, the original Windows Server 2003 brought a substantial rearrangement. This perhaps anticipated 64-bit Windows, but when x64 builds actually didi appear for the first service pack the structure was rearranged almost as much again.
Distinctively new for the versions that have both x86 and x64 builds is the packing of small members into spare fields in other members. Some such reused fields are explicitly spare, as with several members of the KAPC structure. WDM.H even defines macros to ease the reference to these fields by their offsets. Other reuse is available only because of alignment padding, as with the last byte of the KAPC_STATE for 32-bit Windows. There is more opportunity for this reuse in the 64-bit builds, whose wider pointers tend to create alignment padding as a side-effect. To take advantage of this to get a smaller KTHREAD for the late builds of version 5.2, numerous members are reordered from the early builds of version 5.2, and a few are ordered differently in the 32-bit and 64-bit builds. To track this reordering continuously with even the immediately preceding build is just not feasible.
In the following table of sizes, different builds of the same version are distinguished as early, late and very late. These descriptions are then used throughout this article as a shorthand.
Version | Size (x86) | Size (x64) |
---|---|---|
late 5.2 (SP1) | 0x01B8 | 0x0320 |
very late 5.2 (SP2) | 0x01B8 | 0x0308 |
early 6.0 (before SP1); late 6.0 (SP1 and higher) |
0x01E0 | 0x0330 |
6.1 | 0x0200 | 0x0360 |
These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for the kernel.
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.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x00 | 0x00 |
DISPATCHER_HEADER Header; |
5.2 to 6.1 | previously at 0x00; next at 0x00 |
|
0x10 (5.2) | 0x18 (5.2) |
LIST_ENTRY MutantListHead; |
5.2 only | next at 0x01CC and 0x02E0 | previously at 0x10 |
0x10 | 0x18 |
ULONGLONG volatile CycleTime; |
6.0 to 6.1 | next at 0x30 and 0x48 | |
0x18 |
ULONG volatile HighCycleTime; |
6.0 to 6.1 | next at 0x38 | ||
0x20 | 0x20 |
ULONGLONG QuantumTarget; |
6.0 to 6.1 | next at 0x18 and 0x20 | |
0x18 (5.2); 0x28 |
0x28 |
PVOID InitialStack; |
5.2 to 6.1 | previously at 0x18; next at 0x20 and 0x28 |
|
0x1C (5.2); 0x2C |
0x30 |
PVOID StackLimit; |
5.2 only | previously at 0x1C | |
PVOID volatile StackLimit; |
6.0 to 6.1 | next at 0x24 and 0x30 | |||
0x20 (5.2); 0x30 |
0x38 |
PVOID KernelStack; |
5.2 to 6.1 | previously at 0x20; next at 0x48 and 0x58 |
|
0x24 (5.2); 0x34 |
0x40 |
KSPIN_LOCK ThreadLock; |
5.2 to 6.1 | previously at 0x24; next at 0x2C and 0x40 |
|
0x38 | 0x48 |
KWAIT_STATUS_REGISTER WaitRegister; |
6.1 only | next at 0x54 and 0x70 | |
0x39 | 0x49 |
BOOLEAN volatile Running; |
6.1 only | next at 0x55 and 0x71 | |
0x3A | 0x4A |
BOOLEAN Alerted [2]; |
6.1 only | previously at 0x6E and 0x96 | next at 0x56 and 0x72 |
0x3C | 0x4C |
union { struct { /* bit fields, follow link */ }; LONG MiscFlags; }; |
6.1 only | previously at 0x3C and 0x4C | next at 0x58 and 0x74 |
0x28 (5.2); 0x38 (6.0); 0x40 |
0x48 (late 5.2 to 6.0); 0x50 |
union { KAPC_STATE ApcState; /* overlay, see below */ }; |
5.2 to 6.1 | previously without union at 0x34; next at 0x70 and 0x98 |
Overlaying the ApcState is the structure
struct { UCHAR ApcStateFill [KAPC_STATE_ACTUAL_LENGTH]; /* 5 bytes, see below */ };
With this construction, the 64-bit builds fit five bytes into the 0x30-byte ApcState. In 32-bit builds, only the first fits the 0x18-byte ApcState: the rest just follow as if they had been declared outside the union. Disregard the construction, and the members that are packed with the ApcState are:
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x3F (5.2); 0x4F (6.0); 0x57 |
0x73 (5.2 to 6.0); 0x7B |
BOOLEAN ApcQueueable; |
5.2 only | next as bit in ThreadFlags | previously at 0x0109 |
CHAR Priority; |
6.0 to 6.1 | previously at 0x5B and 0x93 | next at 0x87 and 0xC3 | ||
0x40 (5.2); 0x50 (6.0); 0x58 |
0x74 (5.2 to 6.0); 0x7C |
UCHAR volatile NextProcessor; |
5.2 only | previously as UCHAR at 0x010F | |
USHORT volatile NextProcessor; |
6.0 only | ||||
ULONG volatile NextProcessor; |
6.1 only | next at 0x0148 and 0x0218 | |||
0x41 (5.2); 0x52 (6.0); 0x5C |
0x75 (5.2); 0x76 (6.0); 0x80 |
UCHAR volatile DeferredProcessor; |
5.2 only | previously at 0x01BE | |
USHORT volatile DeferredProcessor; |
6.0 only | ||||
ULONG volatile DeferredProcessor; |
6.1 only | next at 0x014C and 0x021C | |||
0x42 (5.2) | 0x76 (5.2) |
UCHAR AdjustReason; |
5.2 only | next at 0x0134 and 0x01E4 | previously at 0x01BF |
0x43 (5.2) | 0x77 (5.2) |
CHAR AdjustIncrement; |
5.2 only | next at 0x0135 and 0x01E5 | previously at 0x01C0 |
For all the contrivance, the packing had to change as soon as version 6.0. First, one of the single-byte members was just a boolean, such that it could add even less to the KTHREAD if converted to a bit field. Second, the two single-byte members NextProcessor and DeferredProcessor were widened to 16 bits. For them to stay in the ApcState, two that hadn’t changed had to be moved out. The widening to 32 bits for version 6.1 pushed DeferredProcessor out of the reclaimed space (and it may be that Microsoft’s C-language definition doesn’t leave DeferrredProcessor in the ApcState union.)
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x44 (5.2); 0x54 (6.0); 0x60 |
0x78 (5.2 to 6.0); 0x88 |
KSPIN_LOCK ApcQueueLock; |
5.2 to 6.1 | previously at 0x4C | |
0x48 (5.2); 0x58 (6.0); 0x64 |
ULONG ContextSwitches; |
5.2 to 6.1 | for x64, see below | previously at 0x28; next at 0x8C |
|
0x4C (5.2); 0x5C (6.0); 0x68 |
UCHAR volatile State; |
5.2 to 6.1 | for x64, see below | previously at 0x2C; next at 0x90 |
|
0x4D (5.2); 0x5D (6.0); 0x69 |
UCHAR NpxState; |
5.2 to 6.1 | for x64, see below | previously at 0x2D; next as CHAR at 0x91 |
|
0x4E (5.2); 0x5E (6.0); 0x6A |
KIRQL WaitIrql; |
5.2 to 6.1 | for x64, see below | previously at 0x2E; next at 0x92 |
|
0x4F (l5.2); 0x5F (6.0); 0x6B |
KPROCESSOR_MODE WaitMode; |
5.2 to 6.1 | for x64, see below | previously at 0x2F; next at 0x93 |
|
0x50 (5.2); 0x60 (6.0); 0x6C |
0x80 (5.2 to 6.0); 0x90 |
LONG_PTR WaitStatus; |
5.2 to 6.1 | previously at 0x50; next at 0x94 and 0xC8 |
|
0x54 (5.2); 0x64 (6.0); 0x70 |
0x88 (5.2 to 6.0); 0x98 |
union { KWAIT_BLOCK *WaitBlockList; KGATE *GateObject; }; |
5.2 to 6.0 | previously without union at 0x54 | |
KWAIT_BLOCK *WaitBlockList; |
6.1 only | next at 0x98 and 0xD0 | |||
0x58 (5.2) | 0x90 (5.2) |
BOOLEAN Alertable; |
5.2 only | next as bit in MiscFlags | previously at 0x58 |
0x59 (5.2) | 0x91 (5.2) |
BOOLEAN WaitNext; |
5.2 only | next as bit in MiscFlags | previously at 0x5A |
0x68 (6.0) | 0x90 (6.0) |
union { struct { /* bit fields, see below */ }; LONG MiscFlags; }; |
6.0 only | next at 0x3C and 0x4C | |
0x5A (5.2); 0x6C (6.0) |
0x92 (5.2); 0x94 (6.0) |
UCHAR WaitReason; |
5.2 to 6.0 | next at 0x0187 and 0x026B | previously at 0x5A |
0x5B (5.2) | 0x93 (5.2) |
CHAR Priority; |
5.2 only | next at 0x4F and 0x73 | previously at 0x5B |
0x5C (5.2) | 0x94 (5.2) |
BOOLEAN EnableStackSwap; |
5.2 only | next as bit in ThreadFlags | previously at 0x5C |
0x5D (5.2); 0x6D (6.0) |
0x95 (5.2 to 6.0) |
UCHAR volatile SwapBusy; |
5.2 to 6.0 | previously at 0x5D | |
0x5E (5.2); 0x6E (6.0) |
0x96 (5.2 to 6.0) |
UCHAR Alerted [2]; |
5.2 to 6.0 | next at 0x3A and 0x4A | previously at 0x5E |
0x60 (5.2); 0x70 (6.0); 0x74 |
0x98 (5.2 to 6.0); 0xA0 |
union { LIST_ENTRY WaitListEntry; SINGLE_LIST_ENTRY SwapListEntry; }; |
5.2 to 6.1 | previously at 0x60; next at 0x9C and 0xD8 |
|
0x68 (5.2); 0x78 (6.0); 0x7C |
0xA8 (5.2 to 6.0); 0xB0 |
KQUEUE *Queue; |
5.2 to 6.0 | previously at 0x68 |
|
KQUEUE * volatile Queue; |
6.1 only | next at 0xA4 and 0xE8 | |||
0x6C (5.2); 0x7C (6.0); 0x80 |
ULONG WaitTime; |
5.2 to 6.1 | for x64, see below | previously at 0x6C; next at 0x0138 |
|
0x70 (5.2); 0x80 (6.0); 0x84 |
union { struct { SHORT KernelApcDisable; SHORT SpecialApcDisable; }; ULONG CombinedApcDisable; }; |
5.2 to 6.1 | for x64, see below | previously at 0x70; next at 0x013C |
|
0x74 (5.2); 0x84 (6.0); 0x88 |
0xB0 (5.2 to 6.0); 0xB8 |
PVOID Teb; |
5.2 to 6.1 | previously at 0x30; next at 0xA8 and 0xF0 |
|
0x78 (5.2); 0x88 (6.0); 0x90 |
0xB8 (5.2 to 6.0); 0xC0 |
union { KTIMER Timer; /* overlay, see below */ }; |
5.2 to 6.1 | previously without union at 0x78; next without union at 0xB8 and 0x0100 |
Overlaying the long-standing Timer is the structure
struct { UCHAR TimerFill [KTIMER_ACTUAL_LENGTH]; /* 4 bytes, see below */ };
The 64-bit KTIMER originally had four bytes of unused space at its end. This was a snug fit for a newly defined set of bit flags, which seem to have been kept with the Timer even after a reworking of the KTIMER for version 6.1 put the alignment space to use. Though only the one member ever was squeezed in, it changed slightly while Microsoft’s programmers seem to have been uncertain about what needs to be volatile:
Offset (x86) | Offset (x64) | Definition | Versions | History |
---|---|---|---|---|
0xA0 (5.2); 0xB0 (6.0); 0xB8 |
0xF4 (5.2 to 6.0); 0x0100 |
union { struct { /* bit fields, follow link */ }; LONG ThreadFlags; }; |
5.2 only | |
union { struct { /* bit fields, follow link */ }; LONG volatile ThreadFlags; }; |
6.0 to 6.1 | next at 0x5C and 0x78 |
Because the change within the KTIMER for version 6.1 has the side-effect that the ThreadFlags are no longer inside the Timer, the 64-bit builds were left with four bytes of spare space for the 8-byte alignment of the WaitBlock. The 32-bit builds always had these four bytes left by alignment, but perhaps nobody cared. Once noticed, the alignment space got used for the ServiceTable, which had anyway been discontinued for 64-bit builds.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0xBC (6.1) |
PVOID ServiceTable; |
6.1 only | previously at 0x012C | next at 0x3C | |
0x0104 (6.1) |
ULONG Spare0; |
6.1 only |
The KWAIT_BLOCK structure has a spare byte in x86 builds and another four in x64 builds. With the KTHREAD having four of these structures as its WaitBlock array, packing members into this space was evidently irresistable, even if it’s spectacularly messy. Then, for version 6.1, it seems to have been decided that the single bytes weren’t worth picking up in either build, but the 64-bit builds reclaim yet more from realising that the particular use that’s made of the last KWAIT_BLOCK does not involve its Object member.
Offset (x86) | Offset (x64) | Definition | Versions | History |
---|---|---|---|---|
0xA8 (5.2); 0xB8 (6.0); 0xC0 |
0xF8 (5.2 to 6.0); 0x0108 |
union { KWAIT_BLOCK WaitBlock [4]; /* overlay, see below */ }; |
5.2 to 6.0 | previously without union at 0xA0 and 0x0140 |
KWAIT_BLOCK WaitBlock [4]; |
6.1 only | next as union at 0xE0 | ||
union { KWAIT_BLOCK WaitBlock [4]; /* overlay, see below */ }; |
6.1 only | next as changed union at 0x0140 |
Overlaying the long-standing WaitBlock are a succession of structures that pad to the spare or resuable member:
#if NTDDI_VERSION < NTDDI_WIN7 // SpareByte not used in 6.1 struct { UCHAR WaitBlockFill0 [FIELD_OFFSET (KWAIT_BLOCK, SpareByte)]; /* reclaimed byte, see below */ }; struct { UCHAR WaitBlockFill1 [FIELD_OFFSET (KWAIT_BLOCK, SpareByte) + sizeof (KWAIT_BLOCK)]; /* reclaimed byte, see below */ }; struct { UCHAR WaitBlockFill2 [FIELD_OFFSET (KWAIT_BLOCK, SpareByte) + 2 * sizeof (KWAIT_BLOCK)]; /* reclaimed byte, see below */ }; struct { UCHAR WaitBlockFill3 [FIELD_OFFSET (KWAIT_BLOCK, SpareByte) + 3 * sizeof (KWAIT_BLOCK)]; /* reclaimed byte, see below */ }; #endif #ifdef _AMD64_ // SpareLong exists only for x64 struct { UCHAR WaitBlockFill4 [FIELD_OFFSET (KWAIT_BLOCK, SpareLong)]; /* reclaimed 4 bytes, see below */ }; struct { UCHAR WaitBlockFill5 [FIELD_OFFSET (KWAIT_BLOCK, SpareLong) + sizeof (KWAIT_BLOCK)]; /* reclaimed 4 bytes, see below */ }; struct { UCHAR WaitBlockFill6 [FIELD_OFFSET (KWAIT_BLOCK, SpareLong) + 2 * sizeof (KWAIT_BLOCK)]; /* reclaimed 4 bytes, see below */ }; # if NTDDI_VERSION < NTDDI_WIN7 struct { UCHAR WaitBlockFill7 [FIELD_OFFSET (KWAIT_BLOCK, SpareLong) + 3 * sizeof (KWAIT_BLOCK)]; /* reclaimed 4 bytes, see below */ }; # else // last Object reclaimed in 6.1 struct { UCHAR WaitBlockFill7 [FIELD_OFFSET (KWAIT_BLOCK, Object) + 3 * sizeof (KWAIT_BLOCK)]; /* Reclaimed 16 bytes, see below */ }; struct { UCHAR WaitBlockFill8 [FIELD_OFFSET (KWAIT_BLOCK, SpareLong) + 3 * sizeof (KWAIT_BLOCK)]; /* reclaimed 4 bytes, see below */ }; # endif #endif
Put aside the scaffolding and here are the members that get squeezed in with the WaitBlock:
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0xBF (5.2); 0xCF (6.0) |
0x0123 (5.2 to 6.0) |
BOOLEAN SystemAffinityActive; |
5.2 only | next as bit in MiscFlags | |
UCHAR IdealProcessor; |
6.0 only | previously at 0x011D and 0x01E5; next as ULONG at 0x0160 and 0x0228 |
|||
0x0124 (5.2 to 6.0); 0x0134 |
ULONG ContextSwitches; |
5.2 to 6.1 | for x86, see above | next at 0x0154 | |
0xD7 (5.2); 0xE7 (6.0) |
0x0153 (5.2 to 6.0) |
KPROCESSOR_MODE PreviousMode; |
5.2 to 6.0 | next at 0x013A and 0x01F6 | previously at 0x0115 |
0x0154 (5.2 to 6.0); 0x0164 |
UCHAR volatile State; |
5.2 to 6.1 | for x86, see above | next at 0x0184 | |
0x0155 (5.2 to 6.0); 0x0165 |
UCHAR NpxState; |
5.2 to 6.1 | for x86, see above | next as CHAR at 0x0185 | |
0x0156 (5.2 to 6.0); 0x0166 |
KIRQL WaitIrql; |
5.2 to 6.1 | for x86, see above | next at 0x0186 | |
0x0157 (5.2 to 6.0); 0x0167 |
KPROCESSOR_MODE WaitMode; |
5.2 to 6.1 | for x86, see above | next at 0x0187 | |
0xEF (5.2); 0xFF (6.0) |
0x0183 (5.2 to 6.0) |
UCHAR ResourceIndex; |
5.2 to 6.0 | next at 0x0195 and 0x0281 | previously at 0x0116 |
0x0184 (5.2 to 6.0); 0x0194 |
ULONG WaitTime; |
5.2 to 6.1 | for x86, see above | next at 0x01B4 | |
0x01B0 (6.1) |
PVOID TebMappedLowVa; |
6.1 only | next at 0x0200 | ||
0x01B8 (6.1) |
UMS_CONTROL_BLOCK *Ucb; |
6.1 only | next at 0x01F0 | ||
0x0107 (5.2); 0x0117 (6.0) |
0x01B3 (5.2 to 6.0) |
BOOLEAN LargeStack; |
5.2 to 6.0 | next at 0x01C3 and 0x02D3 | previously at 0x01B4 |
0x01B4 (5.2 to 6.0); 0x01C4 |
union { struct { SHORT KernelApcDisable; SHORT SpecialApcDisable; }; ULONG CombinedApcDisable; }; |
5.2 to 6.1 | for x86, see above | next at 0x01E4 |
Yes, there is a plan to write something here.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x0108 (5.2); 0x0118 (6.0); 0x0120 |
0x01B8 (5.2 to 6.0); 0x01C8 |
LIST_ENTRY QueueListEntry; |
5.2 to 6.1 | previously at 0x0100; next at 0x0140 and 0x0208 |
|
0x0110 (5.2); 0x0120 (6.0); 0x0128 |
0x01C8 (5.2 to 6.0); 0x01D8 |
KTRAP_FRAME *TrapFrame; |
5.2 to 6.1 | previously at 0x0150; next at 0x6C and 0x90 |
|
0x0124 (6.0); 0x012C |
0x01D0 (6.0); 0x01E0 |
PVOID FirstArgument; |
6.0 to 6.1 | next at 0x68 and 0x88 | |
0x0114 (5.2); 0x0128 (6.0); 0x0130 |
0x01D0 (5.2); 0x01D8 (6.0); 0x01E8 |
PVOID CallbackStack; |
5.2 only | previously at 0x0148 | |
union { PVOID CallbackStack; ULONG_PTR CallbackDepth; }; |
6.0 to 6.1 | next at 0x0130 and 0x01E8 | |||
0x0118 (5.2); 0x012C (6.0) |
0x01D8 (late 5.2) |
PVOID ServiceTable; |
5.2 to 6.0 | next at 0xBC | previously at 0x0124 |
0x01E0 (late 5.2) |
ULONG KernelLimit; |
late 5.2 only | |||
0x011C (5.2); 0x0130 (6.0); 0x0134 |
0x01E4 (late 5.2); 0x01D8 (v. late 5.2); 0x01E0 (6.0); 0x01F0 |
UCHAR ApcStateIndex; |
5.2 to 6.1 | previously at 0x0108; next at 0x016A and 0x024A |
|
0x011D (5.2) | 0x01E5 (late 5.2); 0x01D9 (v. late 5.2) |
UCHAR IdealProcessor; |
5.2 only | next at 0xCF and 0x0123 | previously at 0x010E |
0x011E (5.2) | 0x01E6 (late 5.2); 0x01DA (v. late 5.2) |
BOOLEAN Preempted; |
5.2 only | next at 0x0133 and 0x01E3 | previously at 0x010A |
0x011F (5.2) | 0x01E7 (late 5.2); 0x01DB (v. late 5.2) |
BOOLEAN ProcessReadyQueue; |
5.2 only | next as bit in MiscFlags | previously at 0x010B |
0x01E8 (late 5.2) |
PVOID Win32kTable; |
late 5.2 only | |||
0x01F0 (late 5.2) |
ULONG Win32kLimit; |
late 5.2 only | |||
0x0120 (5.2) | 0x01F4 (late 5.2); 0x01DC (v. late 5.2) |
BOOLEAN KernelStackResident; |
5.2 only | next as bit in MiscFlags | previously at 0x010C |
0x0121 (5.2); 0x0131 (6.0); 0x0135 |
0x01F5 (late 5.2); 0x01DD (v. late 5.2); 0x01E1 (6.0); 0x01F1 |
CHAR BasePriority; |
5.2 to 6.1 | previously at 0x0110; next at 0x015B and 0x0233 |
|
0x0122 (5.2); 0x0132 (6.0); 0x0136 |
0x01F6 (late 5.2); 0x01DE (v. late 5.2); 0x01E2 (6.0); 0x01F2 |
CHAR PriorityDecrement; |
5.2 to 6.0 | previously at 0x0112 | |
union { CHAR PriorityDecrement; struct { UCHAR ForegroundBoost : 4; UCHAR UnusualBoost : 4; }; }; |
6.1 only | next at 0x015C (x86); next at 0x0234 (x64) |
|||
0x0133 (6.0); 0x0137 |
0x01E3 (6.0); 0x01F3 |
BOOLEAN Preempted; |
6.0 to 6.1 | previously at 0x011E and 0x01E6 | next at 0x015D and 0x0235 |
0x0134 (6.0); 0x0138 |
0x01E4 (6.0); 0x01F4 |
UCHAR AdjustReason; |
6.0 to 6.1 | previously at 0x42 and 0x76 | next at 0x015E and 0x0236 |
0x0135 (6.0); 0x0139 |
0x01E5 (6.0); 0x01F5 |
CHAR AdjustIncrement; |
6.0 to 6.1 | previously at 0x43 and 0x77 | next at 0x015F and 0x0237 |
0x0136 (6.0); 0x013A |
0x01E6 (6.0); 0x01F6 |
UCHAR Spare01; |
6.0 only | ||
KPROCESSOR_MODE PreviousMode; |
6.1 only | previously at 0xE7 and 0x0153 | next at 0x015A and 0x0232 | ||
0x0123 (5.2); 0x0137 (6.0); 0x013B |
0x01F7 (late 5.2); 0x01DF (v. late 5.2); 0x01E7 (6.0); 0x01F7 |
CHAR Saturation; |
5.2 to 6.1 | previously at 0x010D; next at 0x018D and 0x0285 |
|
0x0138 (6.0); 0x013C |
0x01E8 (6.0); 0x01F8 |
ULONG SystemCallNumber; |
6.0 to 6.1 | next at 0x64 and 0x80 | |
0x013C (6.0); 0x0140 |
0x01EC (6.0); 0x01FC |
ULONG Spare02; |
early 6.0 only | ||
ULONG FreezeCount; |
late 6.0 to 6.1 | previously as UCHAR at 0x016B and 0x0243 | next as bit in ThreadFlags | ||
0x0124 (5.2); 0x0140 (6.0); 0x0144 |
0x01F8 (late 5.2); 0x01E0 (v. late 5.2); 0x01F0 (6.0); 0x0200 |
KAFFINITY UserAffinity; |
5.2 to 6.0 | previously at 0x0118 | |
GROUP_AFFINITY volatile UserAffinity; |
6.1 only | next as union at 0x0154 and 0x0228 | |||
0x0128 (5.2); 0x0144 (6.0); 0x0150 |
0x0200 (late 5.2); 0x01E8 (v. late 5.2); 0x01F8 (6.0); 0x0210 |
KPROCESS *Process; |
5.2 to 6.1 | previously at 0x011C; next at 0x0150 and 0x0220 |
|
0x012C (5.2); 0x0148 (6.0); 0x0154 |
0x0208 (late 5.2); 0x01F0 (v. late 5.2); 0x0200 (6.0); 0x0218 |
KAFFINITY Affinity; |
5.2 only | previously at 0x0120 | |
KAFFINITY volatile Affinity; |
6.0 only | ||||
GROUP_AFFINITY volatile Affinity; |
6.1 only | next as union at 0x0160 and 0x0238 | |||
0x0160 | 0x0228 |
ULONG IdealProcessor; |
6.1 only | previously as UCHAR at 0xCF and 0x0123 | next at 0x0168 and 0x0244 |
0x0164 | 0x022C |
ULONG UserIdealProcessor; |
6.1 only | previously as UCHAR at 0x016D and 0x0245 | next at 0x88 and 0xC4 |
0x0130 (5.2); 0x014C (6.0); 0x0168 |
0x0210 (late 5.2); 0x01F8 (v. late 5.2); 0x0208 (6.0); 0x0230 |
KAPC_STATE *ApcStatePointer [2]; |
5.2 to 6.1 | previously at 0x0128; next at 0x016C and 0x0248 |
The KTHREAD has two KAPC_STATE structures. The second is used when the thread attaches to another process’s address space. It too has a spare byte in 32-bit builds and a spare five in 64-bit builds:
Offset (x86) | Offset (x64) | Definition | Versions | History |
---|---|---|---|---|
0x0138 (5.2); 0x0154 (6.0); 0x0170 |
0x0220 (late 5.2); 0x0208 (v. late 5.2); 0x0218 (6.0); 0x0240 |
union { KAPC_STATE SavedApcState; /* overlay, see below */ }; |
5.2 to 6.1 | previously without union at 0x0130; next at 0x0174 and 0x0258 |
Overlaying the long-standing SavedApcState is the structure
struct { UCHAR SavedApcStateFill [KAPC_STATE_ACTUAL_LENGTH]; /* 5 bytes, see below */ };
With this construction, the 64-bit builds fit five bytes into the 0x30-byte SavedApcState. In 32-bit builds, only the first fits the 0x18-byte SavedApcState: the rest just follow as if they had been declared outside the union. Disregard the construction, and the members that are packed with the SavedApcState are:
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x014F (5.2); 0x016B (6.0); 0x0187 |
0x024B (late 5.2); 0x0233 (v. late 5.2); 0x0243 (6.0); 0x026B |
CHAR FreezeCount; |
5.2 to early 6.0 | next as ULONG at 0x013C and 0x01EC | previously at 0x01BA |
UCHAR Spare02; |
late 6.0 only | ||||
UCHAR WaitReason; |
6.1 only | previously at 0x6C and 0x94 | next at 0x018B and 0x0283 | ||
0x0150 (5.2); 0x016C (6.0); 0x0188 |
0x024C (late 5.2); 0x0234 (v. late 5.2); 0x0244 (6.0); 0x026C |
CHAR SuspendCount; |
5.2 to 6.1 | previously at 0x01BB; next at 0x018C and 0x0284 |
|
0x0151 (5.2); 0x016D (6.0); 0x0189 |
0x024D (late 5.2); 0x0235 (v. late 5.2); 0x0245 (6.0); 0x026D |
UCHAR UserIdealProcessor; |
5.2 to 6.0 | next as ULONG 0x0164 and 0x022C | previously at 0x01BD |
CHAR Spare1; |
6.1 only | ||||
0x0152 (5.2); 0x016E (6.0) |
0x024E (late 5.2); 0x0236 (v. late 5.2); 0x0246 (6.0) |
BOOLEAN CalloutActive; |
5.2 only | next as bit in ThreadFlags | |
BOOLEAN Spare03; |
6.0 only | ||||
0x0153 (5.2); 0x016F (6.0); 0x018A |
UCHAR Iopl; |
5.2 to early 6.0 | previously at 0x01B9 | ||
UCHAR OtherPlatformFill; |
late 6.0 to 6.1 | ||||
0x024F (late 5.2); 0x0237 (v. late 5.2); 0x0247 (6.0); 0x026E |
BOOLEAN CodePatchInProgress; |
5.2 to 6.1 | next as bit in MiscFlags |
Yes, there is a plan to write something here.
Offset (x86) | Offset (x64) | Definition | Versions | History |
---|---|---|---|---|
0x0154 (5.2); 0x0170 (6.0); 0x018C |
0x0250 (late 5.2); 0x0238 (v. late 5.2); 0x0248 (6.0); 0x0270 |
PVOID Win32Thread; |
5.2 to 6.1 | previously at 0x014C; next as PVOID volatile at 0x0124 and 0x01C8 |
0x0158 (5.2); 0x0174 (6.0); 0x0190 |
0x0258 (late 5.2); 0x0240 (v. late 5.2); 0x0250 (6.0); 0x0278 |
PVOID StackBase; |
5.2 to 6.1 | previously at 0x015C; next at 0x28 and 0x38 |
0x015C (5.2); 0x0178 (6.0); 0x0194 |
0x0260 (late 5.2); 0x0248 (v. late 5.2); 0x0258 (6.0); 0x0280 |
union { KAPC SuspendApc; /* overlay, see below */ }; |
5.2 to 6.1 | previously without union at 0x0160 |
Overlaying the long-standing SuspendApc are structures that each pad to a spare or resuable member or to alignment space:
struct { UCHAR SuspendApcFill0 [KAPC_OFFSET_TO_SPARE_BYTE0]; /* reclaimed byte, see below */ }; struct { UCHAR SuspendApcFill1 [KAPC_OFFSET_TO_SPARE_BYTE1]; /* reclaimed byte, see below */ }; struct { UCHAR SuspendApcFill2 [KAPC_OFFSET_TO_SPARE_LONG]; /* reclaimed four bytes, see below */ }; struct { UCHAR SuspendApcFill3 [KAPC_OFFSET_TO_SYSTEMARGUMENT1]; /* reclaimed 4 or 8 bytes, see below */ }; struct { UCHAR SuspendApcFill4 [KAPC_OFFSET_TO_SYSTEMARGUMENT2]; /* reclaimed 4 or 8 bytes, see below */ }; struct { UCHAR SuspendApcFill5 [KAPC_ACTUAL_LENGTH]; /* reclaimed 1 or 5 bytes, see below) */ ;
The KAPC is especially productive of space for members since there are not just the bytes here and there that are left by alignment but also the members that hold the callback routine’s arguments which the KTHREAD knows it doesn’t use. Here are the members that are squeezed in to the SuspendApc:
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x015D (5.2); 0x0179 (6.0); 0x0195 |
0x0261 (late 5.2); 0x0249 (v. late 5.2); 0x0259 (6.0); 0x0281 |
CHAR Quantum; |
5.2 only | previously at 0x0113 | |
CHAR Spare04; |
6.0 only | ||||
UCHAR ResourceIndex; |
6.1 only | previously at 0xFF and 0x0183 | next at 0x0191 and 0x0289 | ||
0x015F (5.2); 0x017B (6.0); 0x0197 |
0x0263 (late 5.2); 0x024B (v. late 5.2); 0x025B (6.0); 0x0283 |
UCHAR QuantumReset; |
5.2 to 6.1 | next at 0x0193 and 0x028B | |
0x0160 (5.2); 0x017C (6.0); 0x0198 |
0x0264 (late 5.2); 0x024C (v. late 5.2); 0x025C (6.0); 0x0284 |
ULONG KernelTime; |
5.2 to 6.1 | previously at 0x0154; next at 0x0194 and 0x028C |
|
0x0180 (5.2); 0x019C (6.0); 0x01B8 |
0x02A0 (late 5.2); 0x0288 (v. late 5.2); 0x0298 (6.0); 0x02C0 |
PVOID TlsArray; |
5.2 only | previously at 0x01A4 | |
PVOID WaitPrcb; |
6.0 to 6.1 | next at 0x01B4 and 0x02C8 | |||
0x0184 (5.2); 0x01A0 (6.0); 0x01BC |
0x02A8 (late 5.2); 0x0290 (v. late 5.2); 0x02A0 (6.0); 0x02C8 |
PVOID LegoData; |
5.2 to 6.1 | previously at 0x01A8; next at 0x01B8 and 0x02D0 |
|
0x018B (5.2); 0x01A7 (6.0); 0x01C3 |
0x02B3 (late 5.2); 0x029B (v. late 5.2); 0x02AB (6.0); 0x02D3 |
UCHAR PowerState; |
5.2 to 6.0 | previously at 0x01B5 | |
BOOLEAN LargeStack; |
6.1 only | previously at 0x0117 and 0x01B3 | |||
0x018C (5.2); 0x01A8 (6.0); 0x01C4 |
0x02B4 (late 5.2); 0x029C (v. late 5.2); 0x02AC (6.0); 0x02D4 |
ULONG UserTime; |
5.2 to 6.1 | previously at 0x0158; next at 0x01C0 and 0x2DC |
|
0x0190 (5.2); 0x01AC (6.0); 0x01C8 |
0x02B8 (late 5.2); 0x02A0 (v. late 5.2); 0x02B0 (6.0); 0x02D8 |
union { KSEMAPHORE SuspendSemaphore; /* overlay, see below */ }; |
5.2 to 6.1 | previously without union at 0x0190 |
Overlaying the long-standing SuspendSemaphore is the structure
struct { UCHAR SuspendSemaphorefill [KSEMAPHORE_ACTUAL_LENGTH]; /* 4 bytes, see below */ };
The 64-bit KSEMAPHORE has four bytes of unused space left by its 8-byte alignment. Reclaimed from the SuspendSemaphore:
Offset (x86) | Offset (x64) | Definition | Versions | History |
---|---|---|---|---|
0x01A4 (5.2); 0x01C0 (6.0); 0x01DC |
0x02D4 (late 5.2); 0x02BC (v. late 5.2); 0x02CC (6.0); 0x02D4 |
ULONG SListFaultCount; |
5.2 to 6.1 | next as USHORT at 0x018E and 0x0286 |
That’s the end of the contrivances over packing members into space that’s left unused in other members.
Offset (x86) | Offset (x64) | Definition | Versions | Remarks | History |
---|---|---|---|---|---|
0x01A8 (5.2); 0x01C4 (6.0); 0x01E0 |
0x02D8 (late 5.2); 0x02C0 (v. late 5.2); 0x02D0 (6.0); 0x02F8 |
LIST_ENTRY ThreadListEntry; |
5.2 to 6.1 | previously at 0x01AC; next at 0x01D4 and 0x02F8 |
|
0x01CC (6.0); 0x01E8 |
0x02E0 (6.0); 0x0308 |
LIST_ENTRY MutantListHead; |
6.0 to 6.1 | previously at 0x10 and 0x18 | next at 0x01DC and 0x0308 |
0x01B0 (5.2); 0x01D4 (6.0); 0x01F0 |
0x02E8 (late 5.2); 0x02D0 (v. late 5.2); 0x02F0 (6.0); 0x0318 |
PVOID SListFaultAddress; |
5.2 to 6.1 | last member in 5.2 (x86) | next at 0x10 and 0x18 |
0x02F0 (late 5.2); 0x02D8 (v. late 5.2); 0x02F8 (6.0); 0x0320 |
LONGLONG ReadOperationCount; |
5.2 to 6.1 | next at 0x0318 | ||
0x02F8 (late 5.2); 0x02E0 (v. late 5.2); 0x0300 (6.0); 0x0328 |
LONGLONG WriteOperationCount; |
5.2 to 6.1 | next at 0x0320 | ||
0x0300 (late 5.2); 0x02E8 (v. late 5.2); 0x0308 (6.0); 0x0330 |
LONGLONG OtherOperationCount; |
5.2 to 6.1 | next at 0x0328 | ||
0x0308 (late 5.2); 0x02F0 (v. late 5.2); 0x0310 (6.0); 0x0338 |
LONGLONG ReadTransferCount; |
5.2 to 6.1 | next at 0x0330 | ||
0x0310 (late 5.2); 0x02F8 (v. late 5.2); 0x0318 (6.0); 0x0340 |
LONGLONG WriteTransferCount; |
5.2 to 6.1 | next at 0x0338 | ||
0x0318 (late 5.2); 0x0300 (v. late 5.2); 0x0320 (6.0); 0x0348 |
LONGLONG OtherTransferCount; |
5.2 to 6.1 | last member in 5.2 (x64) |
next at 0x0340 | |
0x01D8 (6.0) | 0x0328 (6.0) |
PVOID volatile MdlForLockedTeb; |
6.0 only | last member in 6.0 | |
0x01F4 | 0x0350 |
KTHREAD_COUNTERS *ThreadCounters; |
6.1 only | next at 0xF4 and 0x0168 | |
0x01F8 | 0x0358 |
XSTATE_SAVE *XStateSave; |
6.1 only | last member in 6.1 | next at 0x010C and 0x0198 |