CURRENT WORK ITEM - PREVIEW ONLY

TEB

The Thread Environment Block is a thread’s user-mode representation. It has the highest-level knowledge of a thread in kernel mode and the lowest-level in user mode. If a thread has no user-mode footprint, it has no TEB. If only in principle, if anything about a thread can be properly managed in user mode without needing a transition to kernel mode, it goes in the TEB.

Access

Code executing in user mode can easily find the TEB for the current thread because the fs or gs register, for 32-bit and 64-bit code respectively, holds a selector for a segment whose base address is that of the TEB. By reading just the NtTib.Self member with respect to the segment register, the user-mode code gets a linear address for convenient access to all the rest of the TEB. The 32-bit NTDLL even exports a function, named NtCurrentTeb and declared in WINNT.H, which does exactly this. For 64-bit Windows, NtCurrentTeb is a macro.

It is similarly easy for kernel-mode code to find the TEB for the current thread (if it has one). In kernel mode, the fs and gs registers, again for 32-bit and 64-bit code respectively, select a segment whose base address is that of a KPCR. Though this structure represents the current processor, not the current thread, it too has an NtTib member. Though it might be thought, aided by comments in NTDDK.H, that the Self member in NtTib would point back to NtTib, i.e., to the NT_TIB at the beginning of the processor’s KPCR, the NT_TIB it points to is in fact the one at the beginning of the TEB for the processor’s current thread.

That said, although at every switch of a processor to another thread the kernel updates the KPCR so that its NtTib.Self points to the incoming thread’s TEB, and notwithstanding what the kernel itself often does, finding the current TEB this way is surely not what Microsoft prefers in general. Each KTHREAD, which is the kernel’s representation of a thread, has a Teb member which, of course, holds the address of the thread’s TEB (again, if it has one). Since version 5.1, the kernel exports a function, named PsGetThreadTeb, that does this lookup for an arbitrary thread. Although a PsGetCurrentThreadTeb has to wait for the build of version 5.2 from Windows Server 2003 SP1, it too gets the TEB from the Teb in the current thread’s KTHREAD (from which it also checks that the thread is not a system thread and is not presently attached to the address space of another process).

Documentation Status

In an ideal world, the TEB might be opaque, or even unknown, outside the kernel, NTDLL and perhaps a handful of other modules that are obviously at the very heart of Windows. The fs and gs registers are well-known as addressing an NT_TIB in user mode. This structure is defined in NTDDK.H and WINNT.H. A comment in NTDDK.H notes that the NT_TIB “appears as the first part of the TEB for all threads which have a user mode component” and WINNT.H either declares as a function or defines as a macro something named NtCurrentTeb which produces the address of the current thread’s TEB.

This could be the end of it, yet the TEB is not entirely opaque. Various high-level modules supplied with Windows over the years have used a few members of the TEB, and this eventually had to be disclosed. A new header, named WINTERNL.H, for previously internal APIs was added to the Software Development Kit (SDK) apparently in 2002, and remains to this day. It originally presented a modified TEB that has just the TlsSlots, ReservedForOle and TlsExpansionSlots members, plus padding that gets these members to the same offsets as in the true structure. It seems unlikely that Microsoft will change the TEB in any way that moves these members.

The three members defined in WINTERNL.H may be the only ones that have yet been disclosed for regulatory compliance, but others are known to modules even as far out as Internet Explorer (such that the use surely ought to have compelled disclosure). For instance, it takes mere seconds to establish that IEFRAME knows of the ProcessEnvironmentBlock member. That it got missed is plausibly because of an overlooked macro. If it is an oversight, it’s put right for WINTRNL.H in the SDK for Windows 8.

Other high-level knowledge, though not subject to disclosure, is clearly deliberate. For instance, the 32-bit SHELL32 and SHLWAPI know the special meaning that GdiBatchCount, almost at the end of the structure, has for 32-bit code running on 64-bit Windows. At the lower levels of the Win32 subsystem, very many modules written by Microsoft know of this or that in the TEB.

Layout

With such widespread dependencies, it should not surprise that the TEB is highly stable across Windows versions. It must almost certainly stay so for many versions yet. With very few exceptions, the TEB has varied only by extension rather than redefinition. The following changes of size are known:

Version Size (x86) Size (x64)
3.51 0x0F28  
4.0 0x0F88  
5.0 0x0FA4  
early 5.1 (before Windows XP SP2) 0x0FB4  
late 5.1 (Windows XP SP2 and higher) 0x0FB8  
early 5.2 (before Windows Server 2003 SP1) 0x0FB8  
late 5.2 (Windows Server 2003 SP1 and higher) 0x0FBC 0x17D8
6.0 0x0FF8 0x1828
6.1 0x0FE4 0x1818
6.2 to 6.3 0x0FE8 0x1820
10.0 0x1000 0x1838

These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for the kernel and NTDLL starting with Windows XP, but are something of a guess for earlier versions since the symbol files for these do not contain type information for the TEB. What’s known of Microsoft’s names and types for earlier versions is instead inferred from what use NTOSKRNL and NTDLL are seen to make of the TEB. Exhaustively tracking down all such use would be difficult, if not impossible, even with source code.

Original

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
NT_TIB NtTib;
3.51 and higher
0x1C 0x38
PVOID EnvironmentPointer;
3.51 and higher
0x20 0x40
CLIENT_ID ClientId;
3.51 and higher
0x28 0x50
PVOID ActiveRpcHandle;
3.51 and higher
0x2C 0x58
PVOID ThreadLocalStoragePointer;
3.51 and higher
0x30 0x60
PEB *ProcessEnvironmentBlock;
3.51 and higher
0x34 0x68
ULONG LastErrorValue;
3.51 and higher
0x38 0x6C
ULONG CountOfOwnedCriticalSections;
3.51 and higher
0x3C 0x70
PVOID CsrClientThread;
3.51 and higher
0x40 0x78
PVOID Win32ThreadInfo;
3.51 and higher
0x44 0x80
ULONG User32Reserved [0x1A];
3.51 and higher
0xAC 0xE8
ULONG UserReserved [5];
3.51 and higher
0xC0 0x0100
PVOID WOW32Reserved;
3.51 and higher
0xC4 0x0108
ULONG CurrentLocale;
3.51 and higher
0xC8 0x010C
ULONG FpSoftwareStatusRegister;
3.51 and higher

A relatively large reservation, for system use according to the name, gets partly reassigned for Windows 10 but only to change what it’s reserved for.

Offset (x86) Offset (x64) Definition Versions
0xCC 0x0110
PVOID SystemReserved1 [0x36];
3.51 and higher
PVOID ReservedForDebuggerInstrumentation [0x10];
10.0 and higher
0x010C 0x0190
PVOID SystemReserved1 [0x26];
10.0 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x01A4 0x02C0
LONG ExceptionCode;
3.51 and higher
  0x02C4
UCHAR Padding0 [4];
6.3 and higher

When activation contexts were introduced for Windows XP, the TEB had a whole ACTIVATION_CONTEXT_STACK carved from previously spare bytes.

Offset (x86) Definition Versions
0x01A8 (3.51 to early 5.2)
UCHAR SpareBytes1 [0x2C];
3.51 to 5.0
ACTIVATION_CONTEXT_STACK ActivationContextStack;
5.1 to early 5.2
0x01BC (3.51 to early 5.2)
UCHAR SpareBytes1 [0x18];
3.51 to early 5.2

In the Windows versions that have both x86 and x64 builds, the TEB has just a pointer to an ACTIVATION_CONTEXT_STACK. At first, this just returned almost all the bytes of the ACTIVATION_CONTEXT_STACK to being spare. Except that Windows Vista defined a member at the end, these spare bytes remained spare until Windows 10 put some to use for the instrumentation callback (that can be set through the ProcessInstrumentationCallback case of NtSetInformationProcess).

Offset (x86) Definition Versions
0x01A8
ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer;
late 5.2 and higher
0x01AC
UCHAR SpareBytes1 [0x28];
late 5.2 only
UCHAR SpareBytes1 [0x24];
6.0 only
UCHAR SpareBytes [0x24];
6.1 to 6.3
ULONG_PTR InstrumentationCallbackSp;
10.0 and higher
0x01B0
ULONG_PTR InstrumentationCallbackPreviousPc;
10.0 and higher
0x01B4
ULONG_PTR InstrumentationCallbackPreviousSp;
10.0 and higher
0x01B8
BOOLEAN InstrumentationCallbackDisabled;
10.0 and higher
0x01B9
UCHAR SpareBytes [0x17];
10.0 and higher
0x01D0
ULONG TxFsContext;
6.0 and higher

It may be just coincidental that the 64-bit TEB places the GdiTebBatch member (see below) exactly right for a whole ACTIVATION_CONTEXT_STACK. However, all known x64 builds have just a pointer to the ACTIVATION_CONTEXT_STACK and then spare bytes that get reduced and renamed until all that’s left of them is padding caused by an alignment requirement. Note that the members that are added for Windows 10 have a slightly different order for the different processors. The x86 builds keep all four members together, again carving them from the beginning of previously spare bytes, and leaving some still spare. Space is tighter in the x64 builds, such that the single-byte InstrumentationCallbackDisabled only fits by squeezing into the alignment requirement after the older TxFsContext.

Offset (x64) Definition Versions
0x02C8
ACTIVATION_CONTEXT_STACK *ActivationContextStackPointer;
late 5.2 and higher
0x02D0
UCHAR SpareBytes1 [0x1C];
late 5.2 only
UCHAR SpareBytes1 [0x18];
6.0 only
UCHAR SpareBytes [0x18];
6.1 to 6.3
ULONG_PTR InstrumentationCallbackSp;
10.0 and higher
0x02D8
ULONG_PTR InstrumentationCallbackPreviousPc;
10.0 and higher
0x02E0
ULONG_PTR InstrumentationCallbackPreviousSp;
10.0 and higher
0x02E8
ULONG TxFsContext;
6.0 and higher
0x02EC
UCHAR Padding1 [4];
6.3 and higher
BOOLEAN InstrumentationCallbackDisabled;
10.0 and higher
0x02ED
UCHAR Padding1 [3];
6.3 and higher

The remainder of the original TEB is stable. Note the GDI_TEB_BATCH structure which has always been a very large contributor to the size of the TEB.

Offset (x86) Offset (x64) Definition Versions
0x01D4 0x02F0
GDI_TEB_BATCH GdiTebBatch;
3.51 and higher
0x06B4 0x07D8
CLIENT_ID RealClientId;
3.51 and higher
0x06BC 0x07E8
PVOID GdiCachedProcessHandle;
3.51 and higher
0x06C0 0x07F0
ULONG GdiClientPID;
3.51 and higher
0x06C4 0x07F4
ULONG GdiClientTID;
3.51 and higher
0x06C8 0x07F8
PVOID GdiThreadLocalInfo;
3.51 and higher
0x06CC 0x0800
ULONG_PTR Win32ClientInfo [0x3E];
3.51 and higher
0x07C4 0x09F0
PVOID glDispatchTable [0xE9];
3.51 and higher
0x0B68 0x1138
ULONG_PTR glReserved1 [0x1D];
3.51 and higher
0x0BDC 0x1220
PVOID glReserved2;
3.51 and higher
0x0BE0 0x1228
PVOID glSectionInfo;
3.51 and higher
0x0BE4 0x1230
PVOID glSection;
3.51 and higher
0x0BE8 0x1238
PVOID glTable;
3.51 and higher
0x0BEC 0x1240
PVOID glCurrentRC;
3.51 and higher
0x0BF0 0x1248
PVOID glContext;
3.51 and higher
0x0BF4 0x1250
ULONG LastStatusValue;
3.51 and higher
0x0BF8 0x1258
UNICODE_STRING StaticUnicodeString;
3.51 and higher
0x0C00 0x1268
WCHAR StaticUnicodeBuffer [0x0105];
3.51 and higher
  0x1472
UCHAR Padding3 [6];
6.3 and higher
0x0E0C 0x1478
PVOID DeallocationStack;
3.51 and higher
0x0E10 0x1480
PVOID TlsSlots [0x40];
3.51 and higher
0x0F10 0x1680
LIST_ENTRY TlsLinks;
3.51 and higher
0x0F18 0x1690
PVOID Vdm;
3.51 and higher
0x0F1C 0x1698
PVOID ReservedForNtRpc;
3.51 and higher
0x0F20 0x16A0
PVOID DbgSsReserved [2];
3.51 and higher

Appended for Windows NT 4.0

Offset (x86) Offset (x64) Definition Versions
0x0F28 0x16B0
ULONG HardErrorsAreDisabled;
4.0 to 5.1
ULONG HardErrorMode;
5.2 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x0F2C  
PVOID Instrumentation [0x10];
4.0 to early 5.2
0x16B8
PVOID Instrumentation [0x0E];
late 5.2 only
PVOID Instrumentation [0x09];
6.0 and higher (x86)
PVOID Instrumentation [0x0B];
6.0 and higher (x64)
0x0F50 0x1710
GUID ActivityId;
6.0 and higher
0x0F64 (late 5.2);
0x0F60
0x1728 (late 5.2);
0x1720
PVOID SubProcessTag;
late 5.2 and higher
0x0F64 0x1728
PVOID EtwLocalData;
6.0 to 6.1
PVOID PerflibData;
6.2 and higher
0x0F68 0x1730
PVOID EtwTraceData;
late 5.2 and higher

Yes, there is a plan to write something here.

Offset (x86) Offset (x64) Definition Versions
0x0F6C 0x1738
PVOID WinSockData;
4.0 and higher
0x0F70 0x1740
ULONG GdiBatchCount;
4.0 and higher
0x0F74 0x1744
BOOLEAN InDbgPrint;
4.0 to 5.2
BOOLEAN SpareBool0;
6.0 only
union {
    PROCESSOR_NUMBER CurrentIdealProcessor;
    ULONG IdealProcessorValue;
    struct {
        UCHAR ReservedPad0;
        UCHAR ReservedPad1;
        UCHAR ReservedPad2;
        UCHAR IdealProcessor;
    };
};
6.1 and higher
0x0F75 0x1745
BOOLEAN FreeStackOnTermination;
4.0 to 5.2
BOOLEAN SpareBool1;
6.0 only
0x0F76 0x1746
BOOLEAN HasFiberData;
4.0 to 5.2
BOOLEAN SpareBool2;
6.0 only
0x0F77 0x1747
UCHAR IdealProcessor;
4.0 to 6.0
0x0F78 0x1748
ULONG Spare3;
4.0 to early 5.2
ULONG GuaranteedStackBytes;
late 5.2 and higher
  0x174C
UCHAR Padding5 [4];
6.3 and higher
0x0F7C 0x1750
PVOID ReservedForPerf;
4.0 and higher
0x0F80 0x1758
PVOID ReservedForOle;
4.0 and higher
0x0F84 0x1760
ULONG WaitingOnLoaderLock;
4.0 and higher
  0x1764
UCHAR Padding6 [4];
6.3 and higher

Appended for Windows 2000

Offset (x86) Offset (x64) Definition Versions
0x0F88  
struct _Wx86ThreadState {
    ULONG *CallBx86Eip;
    PVOID DeallocationCpu;
    UCHAR UseKnownWx86Dll;
    CHAR OleStubInvoked;
} Wx86Thread;
5.0 to early 5.2
0x1768
ULONG_PTR SparePointer1;
late 5.2 only
PVOID SavedPriorityState;
6.0 and higher
0x0F8C 0x1770
ULONG_PTR SoftPatchPtr1;
late 5.2 to 6.1
ULONG_PTR ReservedForCodeCoverage;
6.2 and higher
0x0F90 0x1778
ULONG_PTR SoftPatchPtr2;
late 5.2 only
PVOID ThreadPoolData;
6.0 and higher
0x0F94 0x1780
PVOID *TlsExpansionSlots;
5.0 and higher
  0x1788
PVOID DeallocationBStore;
late 5.2 and higher
  0x1790
PVOID BStoreLimit;
late 5.2 and higher
0x0F98 0x1798
ULONG ImpersonationLocale;
5.0 to 6.0
ULONG MuiGeneration;
6.1 and higher
0x0F9C 0x179C
ULONG IsImpersonating;
5.0 and higher
0x0FA0 0x17A0
PVOID NlsCache;
5.0 and higher

Appended for Windows XP

Offset (x86) Offset (x64) Definition Versions
0x0FA4 0x17A8
PVOID pShimData;
5.1 and higher
0x0FA8 0x17B0
ULONG HeapVirtualAffinity;
5.1 to 6.1
USHORT HeapVirtualAffinity;
6.2 and higher
0x0FAA 0x17B2
USHORT LowFragHeapDataSlot;
6.2 and higher
  0x17B4
UCHAR Padding7 [4];
6.3 and higher
0x0FAC 0x17B8
PVOID CurrentTransactionHandle;
5.1 and higher
0x0FB0 0x17C0
TEB_ACTIVE_FRAME *ActiveFrame;
5.1 and higher
0x0FB4 0x17C8
PVOID FlsData;
5.2 and higher
0x0FB4 (late 5.1);
0x0FB8 (late 5.2)
0x17D0 (late 5.2)
BOOLEAN SafeThunkCall;
late 5.1 and late 5.2
0x0FB5 (late 5.1);
0x0FB9 (late 5.2)
0x17D1 (late 5.2)
BOOLEAN BooleanSpare [3];
late 5.1 and late 5.2

Appended for Windows Vista

Offset (x86) Offset (x64) Definition Versions
0x0FB8 0x17D0
PVOID PreferredLanguages;
6.0 and higher
0x0FBC 0x17D8
PVOID UserPrefLanguages;
6.0 and higher
0x0FC0 0x17E0
PVOID MergedPrefLanguages;
6.0 and higher
0x0FC4 0x17E8
ULONG MuiImpersonation;
6.0 and higher
0x0FC8 0x17EC
union {
    USHORT volatile CrossTebFlags;
    struct {
        USHORT SpareCrossTebBits : 16;
    };
};
6.0 and higher
0x0FCA 0x17EE
union {
    USHORT SameTebFlags;
    struct {
        /*  bit fields, see below  */
    };
};
6.0 and higher
0x0FCC 0x17F0
PVOID TxnScopeEnterCallback;
6.0 and higher
0x0FD0 0x17F8
PVOID TxnScopeExitCallback;
6.0 and higher
0x0FD4 0x1800
PVOID TxnScopeContext;
6.0 and higher
0x0FD8 0x1808
ULONG LockCount;
6.0 and higher

The last few members were all discarded after Windows Vista, such that the TEB shrinks.

Offset (x86) Offset (x64) Definition Versions
0x0FDC (6.0) 0x180C
ULONG ProcessRundown;
6.0 only
0x0FE0 (6.0) 0x1810
ULONGLONG LastSwitchTime;
6.0 only
0x0FE8 (6.0) 0x1820
ULONGLONG TotalSwitchOutTime;
6.0 only
0x0FF0 (6.0) 0x1828
LARGE_INTEGER WaitReasonBitMap;
6.0 only

Reworked for Windows 7

Offset (x86) Offset (x64) Definition Versions
0x0FDC 0x180C
ULONG SpareUlong0;
6.1 to 6.3
LONG WowTebOffset;
10.0 and higher
0x0FE0 0x1810
PVOID ResourceRetValue;
6.1 and higher

Appended for Windows 8

Offset (x86) Offset (x64) Definition Versions
0x0FE4 0x1818
PVOID ReservedForWdf;
6.2 and higher

Appended for Windows 10

Offset (x86) Offset (x64) Definition Versions
0x0FE8 0x1820
ULONGLONG ReservedForCrt;
10.0 and higher
0x0FF0 0x1828
GUID EffectiveContainerId;
10.0 and higher

Bit Fields

Windows Vista introduced two sets of bit fields. Each is overlaid by a USHORT for simultaneous access to the bits.

Cross-TEB Flags

The CrossTebFlags have been provided for but never defined: all bits are spare.

Same-TEB Flags

Except that Dbg and Rtl prefixes were removed for Windows 7, the SameTebFlags have changed only by addition:

Mask Definition Versions
0x0001
USHORT DbgSafeThunkCall : 1;
6.0 only
USHORT SafeThunkCall : 1;
6.1 and higher
0x0002
USHORT DbgInDebugPrint : 1;
6.0 only
USHORT InDebugPrint : 1;
6.1 and higher
0x0004
USHORT DbgHasFiberData : 1;
6.0 only
USHORT HasFiberData : 1;
6.1 and higher
0x0008
USHORT DbgSkipThreadAttach : 1;
6.0 only
USHORT SkipThreadAttach : 1;
6.1 and higher
0x0010
USHORT DbgWerInShipAssertCode : 1;
6.0 only
USHORT WerInShipAssertCode : 1;
6.1 and higher
0x0020
USHORT DbgRanProcessInit : 1;
6.0 only
USHORT RanProcessInit : 1;
6.1 and higher
0x0040
USHORT DbgClonedThread : 1;
6.0 only
USHORT ClonedThread : 1;
6.1 and higher
0x0080
USHORT DbgSuppressDebugMsg : 1;
6.0 only
USHORT SuppressDebugMsg : 1;
6.1 and higher
0x0100
USHORT RtlDisableUserStackWalk : 1;
late 6.0 only
USHORT DisableUserStackWalk : 1;
6.1 and higher
0x0200
USHORT RtlExceptionAttached : 1;
late 6.0 and higher
0x0400
USHORT InitialThread : 1;
6.1 and higher
0x0800
USHORT SessionAware : 1;
6.2 and higher
0x1000
USHORT LoadOwner : 1;
10.0 and higher
0x2000
USHORT LoaderWorker : 1;
10.0 and higher
 
USHORT SpareSameTebBits : 8;
early 6.0 only
USHORT SpareSameTebBits : 6;
late 6.0 and higher
USHORT SpareSameTebBits : 5;
6.1 only
USHORT SpareSameTebBits : 4;
6.2 to 6.3
USHORT SpareSameTebBits : 2;
10,0 and higher