KTHREAD (3.10 to 3.50)

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 varies greatly between Windows versions and even between builds. Changes from version 3.10 to 3.50 were very few. Almost all are explained as reductions of members’ widths, e.g., from a 32-bit integer to a one-byte boolean, possibly then motivating relocation, e.g., to collect those booleans for better alignment. Where members have been rearranged such that some need to be listed twice in the Layout table that follows, the Remarks column points the way. As soon as version 3.51, however, the changes become too great for conveying any sense of continuity. For a quick sense of this, look down the Future column in the Layout below, and notice how often two neighbouring members are next at opposite ends of the structure.

Layout

Types and names in the table that follows are from Microsoft’s symbol files for the kernel starting with Windows 2000 SP3. How these apply to the much earlier versions presented here is something of a guess based on cross-version comparison of the kernel’s code for using the KTHREAD. Where use of a member by these early versions 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. Take everything here against the background that the historical detail attempted here is plausibly long lost even at Microsoft (or if not lost, then at least long forgotten).

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 in the KOBJECTS enumeration. Even this changes from 5 to 6 in progressing from version 3.10 to 3.50. Still, in these early versions, only the Type and Size distinguish the Header from that of any other dispatcher object.

Offset (x86) Definition Versions Remarks Future
0x00
DISPATCHER_HEADER Header;
3.10 to 3.50   next at 0x00
0x10
LIST_ENTRY MutantListHead;
3.10 to 3.50   next at 0x10
0x18 unknown LIST_ENTRY 3.10 to 3.50    
0x20 (3.10) unknown LIST_ENTRY 3.10 only    
0x28 (3.10);
0x20 (3.50)
LIST_ENTRY ThreadListEntry;
3.10 to 3.50   next at 0x01A4
0x30 (3.10):
0x28 (3.50)
LIST_ENTRY WaitListEntry;
3.10 to 3.50   next at 0x58
0x38 (3.10);
0x30 (3.50)
LARGE_INTEGER KernelTime;
3.10 only    
ULONG KernelTime;
3.50 only   next at 0x0138
0x40 (3.10);
0x34 (3.50)
LARGE_INTEGER UserTime;
3.10 only    
ULONG UserTime;
3.50 only   next at 0x013C
0x48 (3.10);
0x38 (3.50)
KTIMER Timer;
3.10 to 3.50   next at 0xE8
0x70 (3.10);
0x60 (3.50)
KAPC SuspendApc;
3.10 to 3.50   next at 0x0160
0xA0 (3.10);
0x90 (3.50)
KSEMAPHORE SuspendSemaphore;
3.10 to 3.50   next at 0x0190
0xB4 (3.10);
0xA4 (3.50)
KWAIT_BLOCK WaitBlock [5];
3.10 to 3.50   next at 0x68
0x0140 (3.10);
0x0130 (3.50)
KAPC_STATE ApcState;
3.10 ro 3.50   next at 0x30
0x0158 (3.10);
0x0148 (3.50)
KAPC_STATE SavedApcState;
3.10 to 3.50   next at 0x0140
0x0170 (3.10);
0x0160 (3.50)
KAPC_STATE *ApcStatePointer [2];
3.10 to 3.50   next at 0x012C
0x0178 (3.10);
0x0168 (3.50)
PVOID InitialStack;
3.10 to 3.50   next at 0x18
0x017C (3.10);
0x016C (3.50)
PVOID KernelStack;
3.10 to 3.50   next at 0x20
0x0180 (3.10);
0x0170 (3.50)
PVOID Teb;
3.10 to 3.50   next at 0x24
0x0184 (3.10);
0x0174 (3.50)
ULONG ContextSwitches;
3.10 to 3.50   next at 0x48
0x0188 (3.10) unknown dword 3.10 only    
0x018C (3.10)
LONG Quantum;
3.10 only next as CHAR at 0x01AB  
0x0190 (3.10)
ULONG Iopl;
3.10 only next as UCHAR at 0x01AC  
0x0194 (3.10)
ULONG KernelApcDisable;
3.10 only next as UCHAR at 0x01AE  
0x0198 (3.10) unaccounted 0x0C bytes 3.10 only    
0x01A4 (3.10) unknown dword 3.10 only    
0x01A8 (3.10);
0x0178 (3.50)
unaccounted eight bytes 3.10 only    
0x01B0 (3.10);
0x0180 (3.50)
ULONG WaitTime;
3.10 to 3.50   next at 0x64
0x0184 (3.50) unaccounted four bytes 3.50 only    
0x01B4 (3.10);
0x0188 (3.50)
KAFFINITY Affinity;
3.10 to 3.50   next at 0x0118
0x01B8 (3.10);
0x018C (3.50)
KWAIT_BLOCK *WaitBlockList;
3.10 to 3.50   next at 0x54
0x01BC (3.10);
0x0190 (3.50)
LONG WaitStatus;
3.10 to 3.50   next at 0x4C
0x01C0 (3.10);
0x0194 (3.50)
BOOLEAN Alertable;
3.10 to 3.50   next at 0x0158
0x01C1 (3.10);
0x0195 (3.50)
BOOLEAN Alerted [MaximumMode];
3.10 to 3.50   next at 0x2A
0x01C3 (3.10);
0x0197 (3.50)
BOOLEAN ApcQueueable;
3.10 to 3.50   next at 0x015A
0x01C4 (3.10);
0x0198 (3.50)
BOOLEAN AutoAlignment;
3.10 to 3.50   next at 0x015B
0x01C5 (3.10);
0x0199 (3.50)
BOOLEAN DebugActive;
3.10 to 3.50   next at 0x28
0x01C6 (3.10);
0x019A (3.50)
BOOLEAN Preempted;
3.10 to 3.50   next at 0x011C
0x01C7 (3.10);
0x019B (3.50)
BOOLEAN ProcessReadyQueue;
3.10 to 3.50   next at 0x011D
0x01C8 (3.10);
0x019C (3.50)
BOOLEAN KernelStackResident;
3.10 to 3.50   next at 0x011E
0x01C9 (3.10);
0x019D (3.50)
BOOLEAN WaitNext;
3.10 to 3.50   next at 0x52
0x01CA (3.10);
0x019E (3.50)
UCHAR ApcStateIndex;
3.10 to 3.50   next at 0x0159
0x01CB (3.10);
0x019F (3.50)
UCHAR DecrementCount;
3.10 to 3.50   next at 0x65
0x01CC (3.10);
0x01A0 (3.50)
UCHAR NextProcessor;
3.10 to 3.50   next at 0x011F
0x01CD (3.10);
0x01A1 (3.50)
CHAR Priority;
3.10 to 3.50   next at 0x2F
0x01CE (3.10);
0x01A2 (3.50)
UCHAR State;
3.10 to 3.50   next at 0x29
0x01CF (3.10);
0x01A3 (3.50)
CHAR FreezeCount;
3.10 to 3.50   next at 0x01AC
0x01D0 (3.10);
0x01A4 (3.50)
CHAR SuspendCount;
3.10 to 3.50   next at 0x01AD
0x01D1 (3.10);
0x01A5 (3.50)
KIRQL WaitIrql;
3.10 to 3.50   next at 0x50
0x01D2 (3.10);
0x01A6 (3.50)
KPROCESSOR_MODE WaitMode;
3.10 to 3.50   next at 0x51
0x01D3 (3.10);
0x01A7 (3.50)
UCHAR WaitReason;
3.10 to 3.50   next at 0x53
0x01D4 (3.10);
0x01A8 (3.50)
KPROCESSOR_MODE PreviousMode;
3.10 to 3.50   next at 0x0134
0x01D5 (3.10);
0x01A9 (3.50)
CHAR BasePriority;
3.10 to 3.50 next at 0x64
0x01D6 (3.10);
0x01AA (3.50)
CHAR PriorityDecrement;
3.10 to 3.50   next at 0x66
0x01AB (3.50)
CHAR Quantum;
3.50 only previously as LONG at 0x018C next at 0x67
0x01AC (3.50)
UCHAR Iopl;
3.50 only previously as ULONG at 0x0190 next at 0x2C
0x01D7 (3.10);
0x01AD (3.50)
UCHAR NpxState;
3.10 to 3.50 last member in 3.10 next at 0x2D
0x01AE (3.50)
UCHAR KernelApcDisable;
3.50 only previously as ULONG at 0x0194;
last member in 3.50
next at 0xD0

See that two KTHREAD members are stack pointers. The InitialStack is the address immediately above the 8KB that these versions allow for the stack. It is however not where esp points for the thread’s very first push. The top of the stack is given over to a FLOATING_SAVE_AREA. The pushing and popping of actual execution all takes place beneath that.

When a processor is switched from one thread to another, the KernelStack marks where esp had got for the outgoing thread and will need to be restored when the internal routine ContextSwap next switches a processor to the thread.