KTHREAD (Late 5.2 To 6.1)

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.

Variability

The KTHREAD structure is plainly internal to the kernel and its layout changes greatly between Windows versions and even between builds. The KTHREAD in the original Windows Server 2003 was already a substantial rearrangement of the KTHREAD for versions 3.51 to 5.1. This perhaps anticipated 64-bit Windows, but when x64 builds appeared 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 (Windows Server 2003 SP1) 0x01B8 0x0320
very late 5.2 (Windows Server 2003 SP2) 0x01B8 0x0308
early 6.0 (before Windows Vista SP1);
late 6.0 (Windows Vista 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.

Layout

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 x86 only 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.1   previously at 0x68;
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 x86 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 x64 only next at 0x0200
  0x01B8 (6.1)
UMS_CONTROL_BLOCK *Ucb;
6.1 only x64 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 x86 only in v. late 5.2 and higher;
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 x86 only 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 x64 only 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 late 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 x64 only next at 0x0318 and 0x05A0
  0x02F8 (late 5.2);
0x02E0 (v. late 5.2);
0x0300 (6.0);
0x0328
LONGLONG WriteOperationCount;
5.2 to 6.1 x64 only next at 0x0320 and 0x05A8
  0x0300 (late 5.2);
0x02E8 (v. late 5.2);
0x0308 (6.0);
0x0330
LONGLONG OtherOperationCount;
5.2 to 6.1 x64 only next at 0x0328 and 0x05B0
  0x0308 (late 5.2);
0x02F0 (v. late 5.2);
0x0310 (6.0);
0x0338
LONGLONG ReadTransferCount;
5.2 to 6.1 x64 only next at 0x0330 and 0x05B8
  0x0310 (late 5.2);
0x02F8 (v. late 5.2);
0x0318 (6.0);
0x0340
LONGLONG WriteTransferCount;
5.2 to 6.1 x64 only next at 0x0338 and 0x05C0
  0x0318 (late 5.2);
0x0300 (v. late 5.2);
0x0320 (6.0);
0x0348
LONGLONG OtherTransferCount;
5.2 to 6.1 last member in late 5.2 (x64);
next at 0x0340 and 0x05C8
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