Geoff Chappell - Software Analyst
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. 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.
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 |