KTHREAD (3.51 to 5.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. For a good run of the early Windows versions, however, the KTHREAD is very stable. 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 in a separate table for the KTHREAD in early builds of version 5.2.

Layout

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 begins with a DISPATCHER_HEADER in which the Type is 6, i.e., ThreadObject in the KOBJECTS enumeration. In these early versions, only the Type and Size distinguish the 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

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.

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

Before version 4.0, the KTHREAD had yet another built-in wait block. The one whose 0-based index is 3 are in these ancient versions dedicated to synchronising a client and a server through an event-pair object. This type of kernel object is simple enough in general, though it 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 reduction of the WaitBlock array from five KWAIT_BLOCK structures to four in 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
0xD9 (4.0);
0xDB (5.0);
0xDF
UCHAR Pad [3];
4.0 only    
UCHAR Pad [1];
5.0 only    
UCHAR InitialNode;
5.1 only    
0xDC (4.0 to 5.0);
0xE0
PVOID ServiceTable;
4.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 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    
ULONG 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;
5.0 to 5.1   next at 0x014C
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;
last member in 5.0;
last member in 5.1
next at 0x0117