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 Machine Specific Register (MSR) 0xC0000102 which the kernel initialises with the address of the processor’s KPCR.

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 file 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 definitions 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 while 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. Though a few members have gone in or out of use, including one that was first unused and then redefined for reuse, no members that aren’t labelled reserved or spare have ever shifted.

Layout

In the tables that follow, C-language definitions are reconstructed from type definitions 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)

The symbol-file type definitions are first available for Windows 2003 SP3. For earlier versions, members after Number are therefore not known with certainty: some notes on 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
0x00
NT_TIB NtTib;
3.51 to 5.1
union {
    NT_TIB NtTib;
    struct {
        /*  slightly changing members, see below  */
    };
};
5.2 and higher
0x1C
KPCR *SelfPcr;
 
0x20
KPRCB *Prcb;
 
0x24
KIRQL Irql;
 
0x28
ULONG IRR;
 
0x2C
ULONG IrrActive;
 
0x30
ULONG IDR;
 
0x34
ULONG Reserved2;
3.51 to 5.0
PVOID KdVersionBlock;
5.1 and higher
0x38
KIDTENTRY *IDT;
 
0x3C
KGDTENTRY *GDT;
 
0x40
KTSS *TSS;
 
0x44
USHORT MajorVersion;
 
0x46
USHORT MinorVersion;
 
0x48
KAFFINITY SetMember;
 
0x4C
ULONG StallScaleFactor;
 
0x50
UCHAR DebugActive;
3.51 to 5.1
UCHAR SpareUnused;
5.2 and higher
0x51
UCHAR Number;
 
0x52
UCHAR VdmAlert;
3.51 to 5.0
UCHAR Spare0;
5.1 and higher
0x53
UCHAR Reserved [1];
3.51 to 5.0
UCHAR SecondLevelCacheAssociativity;
5.1 and higher
0x54
ULONG KernelReserved [0x10];
3.51 to 4.0
ULONG KernelReserved [0x0F];
5.0 only
ULONG VdmAlert;
5.1 and higher
0x58
ULONG KernelReserved [0x0E];
5.1 and higher
0x90
ULONG SecondLevelCacheSize;
5.0 and higher
0x94
ULONG HalReserved [0x10];
 
0xD4
ULONG InterruptMode;
 
0xD8
UCHAR Spare1;
 
0xDC
ULONG KernelReserved2 [0x11];
 
0x0120
KPRCB PrcbData;
 

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

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.

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

No use is known of the InterruptMode, in any version, or of Spare1 before it became spare (if it ever was in use). Whether they are defined for versions before 5.0 (or, strictly speaking, from before the build of version 5.0 for Windows 2000 SP3) is not known. Something like KernelReserved2 will have been defined in those versions, 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 and higher
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

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. That Prcb is intentionally 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.

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