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.


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.


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

Offset (x86) Definition Versions Remarks Future
3.10 to 3.50   next at 0x00
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)
3.10 only    
ULONG KernelTime;
3.50 only   next at 0x0138
0x40 (3.10);
0x34 (3.50)
3.10 only    
ULONG UserTime;
3.50 only   next at 0x013C
0x48 (3.10);
0x38 (3.50)
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

A KWAIT_BLOCK is needed for each dispatcher object that a thread waits on. That callers of KeWaitForSingleObject aren’t asked for one is because the KTHREAD has its own. That callers of KeWaitForMultipleObjects typically don’t have to provide an array of wait blocks for the multiple objects is because the KTHREAD has not just one but several. The first three in the WaitBlock array have this purpose. Starting with the NTDDK.H from the Device Driver Kit (DDK) for Windows NT 4.0, this number of what a comment calls “Builtin usable wait blocks” is defined as THREAD_WAIT_OBJECTS. The comment is remarkably succinct: those three are not all of the built-in wait blocks, just the ones that are usable by callers who do not provide their own.

Before version 4.0, the KTHREAD had yet another built-in wait block. In these ancient versions, the KWAIT_BLOCK whose 0-based index is 3 is dedicated to synchronising a client and a server through an event-pair object. This type of kernel object seems never to have been formally documented. Two user-mode threads—you might call them client and server—create or open a pair of synchronisation events as one object by calling the NTDLL functions NtCreateEventPair and NtOpenEventPair. The two events—call them low and high—each represent one thread’s work. When a thread completes work for the other, it signals its event and waits on the other’s. They each do this as one call to the kernel, passing one handle to the NTDLL functions NtSetLowWaitHighEventPair and NtSetHighWaitLowEventPair. In version 5.0 and higher, once this operation gets to the kernel and the handles are resolved to objects, the kernel actually does just call KeSetEvent and KeWaitForSingleObject. Earlier versions, however, look for efficiency from the certainty that setting the event is just the first operation in a pair. They even give each thread a built-in event pair—though in the ETHREAD not the KTHREAD—that a client and server can operate through the NTDLL functions NtSetLowWaitHighThread and NtSetHighWaitLowThread without the overhead of interpreting a handle. The original Windows versions apparently regarded this as so important that these functions get to the kernel through their own interrupt numbers (0x2B and 0x2C), sparing the overhead of having the kernel look up its service table. Though this special attention given to synchronising with event pairs is arguably nothing but dim prehistory now, one formal vestige remains to this day in NTSTATUS.H where comments for STATUS_NO_EVENT_PAIR talk of a “thread specific client/server event pair object”.

The last built-in KWAIT_BLOCK is dedicated to the thread’s own Timer, for use as an implied addition to the objects that are being waited on when a timeout is specified.

Offset (x86) Definition Versions Remarks Future
0x0140 (3.10);
0x0130 (3.50)
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

The InitialStack is the address immediately above the 8KB that these versions allow for the thread’s kernel-mode 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.

Offset (x86) Definition Versions Remarks Future
0x0180 (3.10);
0x0170 (3.50)
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)
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)
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)
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)
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)
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)
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