KPCR

The name KPCR stands for (Kernel) Processor Control Region. The kernel keeps a KPCR for each logical processor. The KPCR for the boot processor is in space provided by the loader or is in the kernel’s .data section, but the KPCR for each additional processor is at the start of a large-scale per-processor state that the kernel builds in one memory allocation. In version 6.0, for instance:

The precise arrangement of these items is plainly meant to be the kernel’s own business. Explicit use of them, wherever they are for any processor, doesn’t look to be intended for any software other than the kernel and the HAL. They are listed here just for the general background of knowing what sorts of large-scale structures Windows keeps for each processor and to elaborate the architectural point that the KPCR is the means through which the kernel manages them.

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 or gs register, in 32-bit and 64-bit Windows respectively, to address that processor’s KPCR. In 32-bit Windows, entering kernel mode gets fs loaded with a selector (0x0030) for a segment whose base address, as determined from the processor’s GDT which the kernel initialised long before, is that of the processor’s KPCR. Entering kernel mode in 64-bit Windows gets the base address for gs loaded via the swapgs instruction from the processor’s Model Specific Register (MSR) 0xC0000102 which the kernel initialises with the address of the processor’s KPCR. (Intel’s label for this MSR is IA32_KERNEL_GS_BASE.)

The KPCR conveniently holds its own address in the SelfPcr or Self member, in 32-bit and 64-bit Windows respectively, so that reading just this one member using a segment register override makes the whole KPCR accessible without overrides. Beware, though, that this 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. As an aside, 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.0, space for the initial KPCR, for the boot processor, is allocated by the loader at the fixed address 0xFFDFF000, perhaps so that the kernel can have the memory available for initialising its use of the boot processor before initialising its own memory manager but without needing to carry the relatively large space in its file image. The NTDDK.H from the Device Driver Kit (DDK) for Windows XP defines this address as KIP0PCRADDRESS. If the loader ends up running the 64-bit kernel, the fixed address is unmapped and the 64-bit kernel instead has space in its .data section for the initial KPCR. The 32-bit kernel, however, keeps the initial KPCR at the fixed address it gets from the loader—and the single-processor builds, for which the initial KPCR is their one and only, use the fixed address in preference to the fs register.

Documentation Status

Not even the role of the segment registers in accessing the KPCR is formally documented, but the KPCR is made at least semi-official by definitions in header files from the DDK or Windows Driver Kit (WDK). From all the way back to the DDK for Windows NT3.51, the NTDDK.H header defines a KPCR for each of the supported architectures and presents either inline functions or macros which access the KPCR via the fs or gs register using compiler intrinsics or inline assembly language.

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. Type information in symbol files for the kernel confirm that the definition in NTDDK.H is not what Microsoft itself uses when building the kernel. Definitions in driver kits from before Windows Vista stop at Number (which is as far as needed for the inline function KeGetCurrentProcessorNumber). All stop short of the embedded KPRCB.

The comment 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. The ACPI.SYS driver also transgresses the comment: it reads the CurrentThread member from the KPRCB by knowing what offset to use with the segment override.

Variability

Still, whatever was or is the intention, e.g., 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 start of the KPCR is highly stable across Windows versions while the KPRCB is highly changeable.

Indeed, the KPCR is so stable that although the structure provides for MajorVersion and MinorVersion numbers, they have not needed changing: they are both 1 in all known builds of both 32-bit and 64-bit Windows. 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.

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. The KPCR varies enough between 32-bit and 64-bit Windows that the layouts are better presented separately.

32-Bit Windows (i386)

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 (and no members are known with certainty for the versions for which even a DDK is not available). Some notes on inferences and suppositions arising from this point follow the table.

Disregard the embedded KPRCB, and the KPCR is 0x0120 bytes in all known builds of 32-bit Windows.

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 Reserved [3];
3.10 only  
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  
0x53
UCHAR Reserved [1];
3.50 to 5.0  
UCHAR SecondLevelCacheAssociativity;
5.1 and higher  
0x54
ULONG KernelReserved [0x10];
3.10 to 4.0  
ULONG KernelReserved [0x0F];
5.0 only  
ULONG VdmAlert;
5.1 and higher previously UCHAR at 0x52
0x58
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. 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. Whether it is defined for versions before 5.0 (or, strictly speaking, from before the build of version 5.0 for Windows 2000 SP3) is not known.

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). That Microsoft named it DpcRoutineActive is mere supposition, but it is 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 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 set the PrcbData at the reliable offset of 0x0120.

NT_TIB

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 was already different. 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.

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!

64-Bit Windows (amd64)

Disregard the embedded KPRCB, and the KPCR is 0x0180 bytes in all known builds of 64-bit Windows. Disregard the reuse of one member of the unnamed structure that overlays the NT_TIB at the beginning, and the 64-bit KPCR is completely stable.

Offset Definition
0x00
union {
    NT_TIB NtTib;
    struct {
        /*  slightly changing members, see below  */
    };
};
0x38
KIDTENTRY64 *IdtBase;
0x40
ULONG64 Unused [2];
0x50
KIRQL Irql;
0x51
UCHAR SecondLevelCacheAssociativity;
0x52
UCHAR ObsoleteNumber;
0x53
UCHAR Fill0;
0x54
ULONG Unused0 [3];
0x60
USHORT MajorVersion;
0x62
USHORT MinorVersion;
0x64
ULONG StallScaleFactor;
0x68
PVOID Unused1 [3];
0x80
ULONG KernelReserved [0x0F];
0xBC
ULONG SecondLevelCacheSize;
0xC0
ULONG HalReserved [0x10];
0x0100
ULONG Unused2;
0x0108
PVOID KdVersionBlock;
0x0110
PVOID Unused3;
0x0118
ULONG PcrAlign1 [0x18];
0x0180
KPRCB Prcb;

It seems at least plausible that the unused space ahead of KernelReserved exists to place the latter and thus also HalReserved at 64-byte cache-line boundaries. The different sizing of KernelReserved and HalReserved seems to have been inherited from the 32-bit implementation’s creation of SecondLevelCacheSize at the end of a previously larger KernelReserved.

Curiously, PcrAlign1 does not by itself align the Prcb that follows. That Prcb is meant to be cache-aligned is certain: cache alignment is plainly a recurring concern within the KPRCB and is obviously simpler to arrange if the KPRCB is itself cache aligned (which it isn’t for 32-bit Windows).

NT_TIB

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 Remarks
0x00
KGDTENTRY64 *GdtBase;
   
0x08
KTSS64 *TssBase;
   
0x10
PVOID PerfGlobalGroupMask;
late 5.2 only  
ULONG64 UserRsp;
6.0 and higher previously at 0x20 in KPRCB
0x18
KPCR *Self;
   
0x20
KPRCB *CurrentPrcb;
   
0x28
KSPIN_LOCK_QUEUE *LockArray;
   
0x30
PVOID Used_Self;
   

See that the 64-bit kernel-mode NT_TIB is from the outset nothing like as defined for user mode—except for having the same size and for ending with some sort of pointer to itself. Again, what Used_Self actually points to is not the NT_TIB in the KPCR but the one that begins the TEB.