KPCR (i386)

The name KPCR stands for (Kernel) Processor Control Region. The kernel keeps a KPCR (formally a _KPCR) for each logical processor. The KPCR is highly specific to the processor architecture. This page concerns itself only with the KPCR in 32-bit Windows for the processor architecture that’s variously named i386 or x86. The x64 KPCR is presented separately.

Access

Kernel-mode code can easily find the KPCR for whichever processor it’s executing on, because when the processor last entered ring 0, however it got there, the kernel will have loaded the fs register with a GDT selector (0x0030) for a segment whose base address is that of the processor’s KPCR. Microsoft’s assembly-language header KS386.INC gives the symbolic name KGDT_R0_PCR for this selector.

The KPCR conveniently holds its own address in the SelfPcr member so that reading just this one member using a segment register override makes the whole KPCR accessible without overrides. This is so fundamental that all versions of the 32-bit Windows kernel have it as an internal routine, typically inlined, which is coded very much like:

FORCEINLINE
KPCR *KeGetPcr (VOID)
{
    return (KPCR *) __readfsdword (FIELD_OFFSET (KPCR, SelfPcr));
}

Beware, though, that what this gets is the address of the KPCR for the processor that the thread was running on at the time. It remains the address of the current KPCR only while the thread can ensure it is not switched to another processor. I suspect that more than a few things go very slightly wrong in kernel-mode Windows because this point is insufficiently respected.

Before version 6.2, space for the boot processor’s KPCR is allocated by the loader at the fixed address 0xFFDFF000. The loader also prepares the initial GDT so that the kernel starts its execution with this space already addressable through the 0x0030 selector. This preparation allows that the kernel can initialise its use of the boot processor before it has initialised a memory manager, yet not carry the relatively large space in its own data. Before version 6.0, the kernel accepts the fixed address from the loader, builds the initial KPCR there, and keeps it there. Indeed, the single-processor builds, for which the initial KPCR is the one and only, use the fixed address in preference to the fs register. Aside from being of some practical use when debugging without symbols, this fixed address for the boot processor’s KPCR was architectural enough to be defined symbolically: from the Device Driver Kit (DDK) for Windows XP up to and including the Windows Driver Kit (WDK) for Windows 8, either or both of NTDDK.H and WDM.H name the fixed address 0xFFDFF000 symbolically as KIP0PCRADDRESS.

Documentation Status

Not even the role of the segment register in accessing the KPCR is formally documented, but the KPCR is made at least semi-official by C-language definitions that Microsoft has published in NTDDK.H from every DDK or WDK since at least as far back as Windows NT 3.51.

That said, Microsoft has always been a little less open about the KPCR for the x86 processor than for others. Though the NTDDK.H for Windows NT 3.1 has no C-language definition of the x86 KPCR, it does for two other processor architectures. Though every NTDDK.H since at least Windows NT 3.51 presents KeGetPcr (see above) as either a macro or inline routine for at least one processor architecture, none has ever defined it for the x86. Of course, this routine’s existence for the x86 does have public disclosure from the very beginning in symbol files that Microsft publishes for debugging support. For Windows NT 4.0, we’re even told it’s at lines 43 to 46 in a source file named i386pcr.asm.

In no published DDK or WDK is the C-language definition of the KPCR complete—not just for the x86 but apparently for all processors. A comment speaks of an “architecturally defined section of the PCR” which “may be directly addressed by vendor/platform specific HAL code and will not change from version to version of NT”. A ready inference is that this defined section is just part of an undisclosed whole. How far this architecturally defined section extends is unclear. Definitions in driver kits from before Windows Vista stop at Number (which is as far as needed for the inline function KeGetCurrentProcessorNumber). Definitions since have stretched to HalReserved—in NTDDK.H, but the definitions in NTOSP.H from early editions of the WDK for Windows 10 also stop at Number. It’s not as if any published definitions have a comment to mark the architecturally defined section’s end. All stop short of the embedded KPRCB.

The comment about the section’s start is anyway best taken as stating some intention which is not strictly observed in practice. Certainly, the “architecturally defined section” is not all that the HAL has access to. It may be all that the HAL accesses from code written in C—and, indeed, the symbol files for the HAL have the NTDDK.H definition of the KPCR—but parts of the HAL that are written in assembly language access KPRCB members very nearly at the end, e.g., HighCycleTime, by knowing their offsets from the start of the KPCR.

Variability

Whatever was or is the intention—perhaps that the KPCR before the KPRCB is per-processor information that the kernel shares but the KPRCB itself is (more) private to the kernel—one practical consequence is that the KPCR before the KPRCB is highly stable across Windows versions while the KPRCB is highly changeable.

Indeed, except for changes within the embedded KPRCB, the x86 KPCR is so stable that although the structure provides for MajorVersion and MinorVersion numbers, they have never changed—they are both 1 in all versions—and arguably have never needed changing. No member that isn’t labelled reserved or spare has ever shifted because of some other member’s insertion or removal, though one has shifted because of its own redefinition.

The changing size of the whole KPCR structure, as shown in the table below, is due entirely to the changing size of the KPRCB at the structure’s end. All versions have this embedded KPCR at offset 0x0120. It can be convenient, if not formally correct, to think of the KPCR as just these first 0x0120 bytes.

Version Size
3.10 0x03B8
3.50 0x0468
3.51 0x0480
4.0 0x0678
5.0 0x0B10
5.1 0x0D70
early 5.2 (before SP1) 0x0EF0
late 5.2 0x0FE0
early 6.0 (before SP1) 0x20B8
late 6.0 0x2128
6.1 0x3748
6.2 0x4280
6.3 0x4628
10.0 to 1703 0x4A20
1709 0x4A60
1803 to 2004 0x6020

Layout

In the tables that follow, C-language definitions are reconstructed from type information in symbol files that Microsoft publishes for the kernel and from definitions in the NTDDK.H files from development kits for driver programming. Symbol-file type information for the 32-bit KPCR is first available for Windows 2003 SP3. For earlier versions, members after Number are therefore not known with certainty. Some notes on inferences and suppositions arising from this point follow the table.

Offset Definition Versions Remarks
0x00
NT_TIB NtTib;
3.10 to 5.1  
union {
    NT_TIB NtTib;
    struct {
        /*  slightly changing members, see below  */
    };
};
5.2 and higher  
0x1C
KPCR *SelfPcr;
all  
0x20
KPRCB *Prcb;
all  
0x24
KIRQL Irql;
all  
0x28
ULONG IRR;
all  
0x2C
ULONG IrrActive;
all  
0x30
ULONG IDR;
all  
0x34
ULONG Reserved2;
3.10 to 5.0  
PVOID KdVersionBlock;
5.1 and higher  
0x38
KIDTENTRY *IDT;
all  
0x3C
KGDTENTRY *GDT;
all  
0x40
KTSS *TSS;
all  
0x44
USHORT MajorVersion;
all  
0x46
USHORT MinorVersion;
all  
0x48
KAFFINITY SetMember;
all  
0x4C
ULONG StallScaleFactor;
all  
0x50
UCHAR DebugActive;
3.10 to 5.1  
UCHAR SpareUnused;
5.2 and higher  
0x51
UCHAR Number;
3.50 and higher  
0x52
UCHAR VdmAlert;
3.50 to 5.0 next as ULONG at 0x54
UCHAR Spare0;
5.1 and higher  
0x51 (3.10);
0x53
UCHAR Reserved [3];
3.10 only  
UCHAR Reserved [1];
3.50 to 5.0  
UCHAR SecondLevelCacheAssociativity;
5.1 and higher  
0x54
ULONG VdmAlert;
5.1 and higher previously UCHAR at 0x52
0x54 (3.10 to 5.0);
0x58
ULONG KernelReserved [0x10];
3.10 to 4.0  
ULONG KernelReserved [0x0F];
5.0 only  
ULONG KernelReserved [0x0E];
5.1 and higher  
0x90
ULONG SecondLevelCacheSize;
5.0 and higher  
0x94
ULONG HalReserved [0x10];
all  
0xD4
ULONG InterruptMode;
all  
0xD8
BOOLEAN DpcRoutineActive;
3.10 to 3.50 next as ULONG at 0x030C in KPRCB
BOOLEAN Spare1;
3.51 and higher  
0xDC
ULONG KernelReserved2 [0x11];
all  
0x0120
KPRCB PrcbData;
all  

At any given moment, the 8-bit Irql member is the processor’s current IRQL. It is what the long-documented HAL function KeGetCurrentIrql looks up. As suggested by the comment “do not use 3 bytes after this as HALs assume they are zero” from the NTDDK.H in the DDK for Windows Server 2003, the HAL sometimes sets 32 bits for this member, e.g., in KeTryToAcquireQueuedSpinLock up to and including version 6.1, and even as late as version 10.0 when restoring the Irql after handling a Machine Check exception (interrupt 0x12).

What KdVersionBlock actually points to is an internal kernel variable that is also named KdVersionBlock. This variable is a DBGKD_GET_VERSION64 structure, which is defined in the WDK header file WDBGEXTS.H. This structure’s reason for existence is presumably to provide the means for a kernel-mode debugger to know more detail about the kernel it’s working with—even when debugging without the correct symbol file. Among other things, the structure has pointers to numerous kernel variables that are otherwise internal to the kernel, i.e., are not exported. Exposing the KdVersionBlock variable via the KPCR means that all those otherwise internal variables are easily and reliably accessible to all kernel-mode software (including kernel-mode malware).

No use is known of Number or VdmAlert before version 3.50. It is here supposed that space for these was taken from the front of a previously larger Reserved array.

The SecondLevelCacheAssociativity and SecondLevelCacheSize are determined when initialising the kernel’s use of the processor, but only if the CPU vendor string is one of the following:

For CPUs from other vendors the size and associativity are zero. The kernel is not known to have any Second-Level (L2) Cache Support before version 5.0, which anyway does not bother about associativity. The layout above supposes as the most plausible history that the kernel and HAL reservations were originally the same size and that version 5.0 took SecondLevelCacheSize from the end of the previously larger KernelReserved but version 5.1 took VdmAlert from the start of it, thus not only shrinking it but shifting it.

The HAL certainly does use its HalReserved area, but the kernel knows nothing of what’s inside and type information in symbol files for the HAL seems not to cover it.

No use is known of the InterruptMode in any version. That it was defined from the start is known from the !pcr command as implemented by the I386KD debugger from the Windows NT 3.1 DDK.

The byte that symbol files define as Spare1 was not always spare. Before version 3.51, it tracks whether the processor is currently executing a Deferred Procedure Call (DPC). Microsoft’s name for it is not known, but DpcRoutineActive is at least plausible since it’s the name that turns up in symbol files for where this member subsequently appears in the KPRCB. As with Irql there seems to be some disagreement about this member’s size. The kernel mostly reads DpcRoutineActive as a BOOLEAN, but always sets it as a ULONG, though only ever to 0 or 1. Though the exported function KeIsExecutingDpc—which Microsoft got round to documenting in 2018 as if new for the 1803 release of Windows 10—returns all 32 bits, DpcRoutineActive is treated above as having been formally a one-byte type for consistency with the known type of Spare1.

Something like KernelReserved2 will have been defined all along, if only to place the PrcbData at the reliable offset of 0x0120.

NT_TIB Overlay

Starting with Windows Server 2003, the NT_TIB at the beginning of the KPCR is given in union with an unnamed structure whose members change a little between versions:

Offset Definition Versions
0x00
EXCEPTION_REGISTRATION_RECORD *Used_ExceptionList;
5.2 and higher
0x04
PVOID Used_StackBase;
5.2 and higher
0x08
PVOID PerfGlobalGroupMask;
5.2 only
PVOID Spare2;
6.0 to 6.2
ULONG MxCsr;
6.3 and higher
0x0C
PVOID TssCopy;
5.2 and higher
0x10
ULONG ContextSwitches;
5.2 and higher
0x14
KAFFINITY SetMemberCopy;
5.2 and higher
0x18
PVOID Used_Self;
5.2 and higher

That version 5.2 defines these as alternatives to what NTDDK.H and WINNT.H present for the NT_TIB itself is plausibly not just because version 5.2 changed the use but was also to formalise that the kernel’s use of these members had been different all along. The NT_TIB does not exist only in the KPCR for each processor. It also begins the TEB for each thread. This is a structure that is created by the kernel for the thread’s user-mode representation. Those headers, even NTDDK.H which is ostensibly for kernel-mode programming, describe the user-mode NT_TIB. The kernel-mode NT_TIB has always been a little different.

Before this union was defined for version 5.2, the StackBase and StackLimit at offsets 0x04 and 0x08 in the NT_TIB are respectively the upper and lower bounds on the esp register for the current thread’s kernel-mode execution in its current circumstances. The name Used_StackBase perhaps warns that this kernel-mode StackBase is interpreted a little differently from the user-mode StackBase in the NT_TIB that begins the TEB. The user-mode StackBase is the page-aligned end of memory that is allocated to the thread’s user-mode stack. The kernel-mode StackBase is not the page-aligned end of the memory that is allocated to the thread’s kernel-mode stack. The top of this memory is instead given over to an area for saving floating-point state and StackBase addresses that. The stack, as a region to push to and pop from, is underneath.

This upper bound on what part of the stack allocation actually is available for use as a stack can change—even without using the relatively modern provisions for switching to a new stack in new memory. Among the contortions of “calling” user mode is that the kernel can be re-entered. Its execution then should be well-separated from its execution before. At each such call to user mode, however far esp has yet got down the current thread’s kernel-mode stack becomes a new top of stack with a new chain for exception handling and a new area for saving floating-point state—which StackBase is changed to point to. If you can read the preceding without realising that the kernel’s simulation of calling user mode risks exhausting the kernel-mode stack (or worse), then kernel-mode programming is not yet for you.

In version 5.2, the StackLimit instead holds the address of a PERFINFO_GROUPMASK. This is an array of bits for what types of event are enabled in NT Kernel Logger sessions. The intention was presumably to allow the tracing of different selections of events on different processors. Whatever the plan, it didn’t survive even to version 6.0. Neither was the old use brought back. Not until version 6.3 is the space put to new use, specifically to hold the value that is to be loaded into the processor’s mxcsr on each entry to kernel mode.

The name of the Used_Self member is perhaps another warning against naive expectation. The Self member of the NT_TIB does not point to the NtTib in the KPCR but instead to the NtTib at the beginning of the current thread’s TEB. That it does so means that the TEB and related structures are not just accessible in both user mode and kernel mode but are accessible by exactly the same (library) code. Early versions made extensive use of this convenience, even to allocate from and free to the user-mode process heap. There is thankfully ever less of this as concern for the kernel’s security has become more important: it’s one thing for the kernel to place into user-mode read-write memory any amount of data to help with user-mode management of the thread, but quite another to depend on what it reads there!