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. 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:

Changes from version 3.10 to 3.50 were very few. 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 tables that follow, 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 columns, especially at the sequence of byte-sized members near to the end, 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. All known versions of Windows have the KTHREAD start with a DISPATCHER_HEADER whose Type is ThreadObject from the KOBJECTS enumeration. In these early versions, only the Type and Size distinguish this Header from that of any other dispatcher object. The Type changes from 5 to 6 in progressing from version 3.10 to 3.50. The Size is consistent with the KTHREAD being 0x01D8 and 0x01B0 bytes in versions 3.10 and 3.50, respectively.

Offset (x86) Definition Versions 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 (3.10)
LIST_ENTRY MutexListHead;
3.10 only  

The MutantListHead persists as the second KTHREAD member through all the structure’s early rearrangements up to and including version 5.2, before version 6.0 moved it to nearly the end. By contrast, the MutexListHead is unique to version 3.10. The name MutexListHead is a guess, proposed on the pattern of MutantListHead whose name is known from symbol files for later versions, but it is a guess that has the support of showing in the output of the !thread command as implemented by I386KD from the DDK for Windows NT 3.10.

When a thread waits successfully (including trivially) on a mutant or mutex object, it acquires the object exclusively. No other thread can acquire it until the owning thread releases it. All such objects that the thread currently owns have the thread stamped into them as their OwnerThread member and are linked into the thread’s MutantListHead or MutexListHead, as appropriate, through their MutantListEntry or MutexListEntry members, respecively. 

That there are two lists in version 3.10 is because in this version the KMUTEX and KMUTANT are different objects. Indeed, the mutex is different enough from all other dispatcher objects that it has its own exported function, KeWaitForMutexObject, which is in this version separate from KeWaitForSingleObject. How exactly the KMUTEX and KMUTANT differed in version 3.10 is not without implications for later versions, and may be taken up elsewhere as its own subject. Here, it’s of historical interest only: as soon as version 3.50 the KMUTEX and KMUTANT become one and the same.

Offset (x86) Definition Versions Future
0x20 (3.10);
0x18 (3.50)
unknown LIST_ENTRY 3.10 to 3.50  
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

In contast to the first two LIST_ENTRY structures in the KTHREAD, which are both list heads that objects are linked into, the next three list entries link the thread into other lists.

The ThreadListEntry is arguably the best known and most permanent. It links the thread with the other threads of the same process. The head of this list is the ThreadListHead in the EPROCESS. The thread is linked into this list at the end of initialising the thread object and it then stays in this list until the thread is terminated (specifically until all threads that wait for the thread’s termination are signalled).

The ThreadListEntry is also the most permanent in that there is absolutely no doubt about its existence through the whole history of Windows. For the other two of these LIST_ENTRY members, the correspondence with later versions is not yet established with certainty.

Offset (x86) Definition Versions Future
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

The KernelTime and UserTime are retrievable from user mode through the NtQueryInformationThread and ZwQueryInformationThread functions when given the information class ThreadTimes (1). They then show as the same-named members of a KERNEL_USER_TIMES structure that the caller provides for output. Version 3.10 keeps them as 64-bit times. Later versions keep them as 32-bit tick counts.

Whatever their size, and despite being soon moved near to the end of the KTHREAD, the KernelTime and UserTime are kept together until version 5.2 SP1 squeezes them into unused space in the SuspendApc.

Offset (x86) Definition Versions Future
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

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 for callers who do not provide their own. All versions have at least one more built-in wait block that is not usable for waiting on a caller’s provision of multiple objects. These early versions have two more.

Before version 4.0, 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 (though the obvious name KEVENT_PAIR is known, along with names and offsets of members, from the output of early debugger extensions). 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 one thread completes work for the other, it signals its own of the events 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 for one event and KeWaitForSingleObject for the other. 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), thus shaving away even the small 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 when a wait for multiple objects has a timeout. The Timer is implied as an additional object to wait for whenever a non-zero timeout is specified, whether the wait is for a single object or for mutliple objects. The wait for a single object is simpler, of course: the first built-in KWAIT_BLOCK is used for the object and the second for the timer. In a wait for multiple objects without a caller-supplied KWAIT_BLOCK array, the second (and third) KWAIT_BLOCK in the built-in array may be needed for the objects the caller specifies. All versions therefore need one more built-in KWAIT_BLOCK for the built-in Timer.

Offset (x86) Definition Versions Future
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

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 how far esp had come down for the outgoing thread and thus also where it will need to be restored to when the internal routine ContextSwap next switches a processor to the thread.

Offset (x86) Definition Versions Future
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)
ULONG MutexLevel;
3.10 only  

The name MutexLevel is taken from the output of the !thread command as implemented by I386KD from the DDK for Windows NT 3.10. It is the highest Level of any KMUTEX that the thread currently owns. The point to the level is to defend against deadlocks by enforcing an order of acquisition, specifically only ever to higher levels. It is a serious error—with its own bug check code, MUTEX_LEVEL_NUMBER_VIOLATION (0x0D)—if a thread that already owns a mutex tries to acquire another whose level is not higher.

Offset (x86) Definition Versions Remarks
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

The preceding three are the only KTHREAD members that version 3.50 shifts out of sequence. Each is narrowed to eight bits and moved to the end of the structure.

Offset (x86) Definition Versions
0x0198 (3.10) unaccounted 0x0C bytes 3.10 only
0x01A4 (3.10) unknown pointer 3.10 only
0x01A8 (3.10);
0x0178 (3.50)
unaccounted eight bytes 3.10 to 3.50

Version 3.10 has a hard-coded maximum number of threads that can exist concurrently. This arises from keeping a table of pointers to threads. The table is a set of variables in the kernel’s own data: a count of entries; an array of 0x0100 possible pointers to table segments; a pointer to the first free entry; an initial segment; and supporting synchronisation. Each segment is an array of 0x0100 pointers. These are the table’s entries. An entry’s interpretation depends on its low bit. If clear, the entry holds the address of a thread. If set, the entry is free. What it holds, disregarding its set low bit, is the address of the next free entry.

In an initialised thread, the pointer at offset 0x01A4 is the address of the thread’s entry in the thread table. At the thread’s termination, this pointer allows that that the thread’s entry in the thread table is quickly found and freed. Whatever was Microsoft’s name for this pointer might never be known: the thread table is gone as soon as version 3.50.

Offset (x86) Definition Versions Future
0x01B0 (3.10);
0x0180 (3.50)
ULONG WaitTime;
3.10 to 3.50 next at 0x60
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)
NTSTATUS 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

The single-byte State member takes its values from the undocumented KTHREAD_STATE enumeration—or is known to in much later versions. What could have been known with certainty in 1993 is that the !thread command as implemented by the I386KD debugger from the DDK for Windows NT 3.10 presents the following as possible values for State:

When State is 5, the WaitReason tells something of why. It takes its values from the documented KWAIT_REASON enumeration.

Offset (x86) Definition Versions Remarks Future
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   next at 0x2D
0x01AE (3.50)
UCHAR KernelApcDisable;
3.50 only previously as ULONG at 0x0194 next at 0xD0