KPRCB (i386)

The name KPRCB stands for (Kernel) Processor Control Block. The kernel keeps one KPRCB (formally a _KPRCB) for each logical processor as the PrcbData member of the same processor’s KPCR. The KPRCB holds most of what the kernel needs ready access to while managing a processor and while managing resources that are themselves managed more simply (and quickly) per processor. Neither of these structures is formally documented. Both are highly specific to the processor architecture. This page concerns itself only with the KPRCB in 32-bit Windows for the processor architecture that’s variously named i386 or x86. The x64 KPRCB is presented separately.


Kernel-mode code can easily find the KPRCB for whichever processor it’s executing on, by finding the current KPCR first. The latter is well-known to be accessible through the fs register. Its Prcb member points to the KPRCB without depending on it to be embedded in the KPCR. Given a C-language definition of the KPCR, getting the current processor’s KPRCB can be conveniently wrapped into one inline function:

KPRCB *KeGetCurrentPrcb (VOID)
    return (KPRCB *) __readfsdword (FIELD_OFFSET (KPCR, Prcb));

which, less some dressing, is mostly how Microsoft’s own programmers have been doing it, as confirmed by the NTOSP.H that Microsoft published (possibly by oversight) in early editions of the Windows Driver Kit (WDK) for Windows 10. Go back far enough, to version 5.0 and earlier, and the kernel has this as a self-standing routine that is named in public symbol files and is apparently written in assembly language. For Windows NT 4.0, we’re even told its two instructions are at lines 61 and 64 in a source file named i386pcr.asm.

The part of the KPCR that’s ahead of the embedded KPRCB is highly stable. Of particular importance is that the offset of the Prcb member is reliable over all Windows versions. The KPRCB that it points to is the PrcbData member, and its offset too is stable over all versions. Members of the KPRCB are sometimes accessed through offsets from the KPCR. For some members, this is even the most usual access. Notably, the KeGetCurrentThread function is implemented very like

KTHREAD *KeGetCurrentThread (VOID)
    return (KTHREAD *) __readfsdword (FIELD_OFFSET (KPCR, PrcbData.CurrentThread));

both as exported and when inlined throughout the kernel. This access to the KPRCB members as nested in the KPCR is formalised in Microsoft’s assembly-language header KS386.INC through such definitions as PcCurrentThread for the preceding field offset. Whether such access is written in assembly language or C, one case is known of it going wrong, such that the offset applied to fs is relative to the KPRCB instead of the KPCR. Look far below for the DpcInterruptRequested member in Windows Vista SP1, specificially. It would not surprise if there have been others. Microsoft understandably does not say and I have neither time nor taste for tracking them down.

Processor Switching

In many cases, as with KeGetCurrentThread, kernel-mode code that must access a KPRCB member does better to use the fs register and an offset from the KPCR. This is because the address that some such routine as KeGetCurrentPrcb obtains is merely the address of the KPRCB for the processor that the current thread was being run on at the time. It remains the address of the current KPRCB only while the thread can ensure it is not switched to another processor. Look again at KeGetCurrentThread. Were it coded in two steps as

KTHREAD *KeGetCurrentThread_BAD (VOID)
    return KeGetCurrentPrcb () -> CurrentThread;

it would be unsafe for general use. Imagine calling this from thread X. A first instruction executes on processor A and gets the address of A’s KPRCB. A second gets the address of the KTHREAD for the thread that this KPRCB says is executing on processor A. If a thread switch can occur between these two instructions, then although the second instruction also executes for thread X, processor A can by then be executing some other thread Y and thread X can instead be executing on some other processor B. The routine will find the KTHREAD for Y, not X.

Often, the circumstances are such that the thread can’t be switched. But often is not always, even for the kernel’s own use. How much trouble has been caused by unsynchronised access to a KPRCB for some processor that the thread is no longer running on may be impossible to assess even roughly but I doubt it’s negligible.

Certainly it has not always been attended to closely even by Microsoft’s kernel programmers. As perhaps the simplest possible cases (though also perhaps the most inconsequential), consider the per-processor performance counters CcFastReadNoWait, CcFastReadWait and CcFastReadNotPossible. These are important enough to have been defined from the start—see below at offset 0x0240 for version 3.10. Put aside that it wasn’t until version 5.1 that the writers of third-party file system (filter) drivers were given such functions as FsRtlIncrementCcFastReadNoWait so that the count can include work they do independently of FsRtlCopyRead, etc. Look instead at the implementations. All that’s needed is one inc instruction relative to fs. Even without a lock prefix, as long as the counter in each KPRCB is never incremented any other way, each counter can only be incremented by the one intended processor. Each truly keeps a per-processor count. But Microsoft did not deliver this simplicity until version 6.1.

Up to and including version 5.2, for both the exported functions and the kernel for its own purposes, the counters are incremented in two steps: first, to get a pointer to the KPRCB; then an (unlocked) inc instruction, using the counter’s offset relative to the KPRCB. This leaves some very slight chance that thread X runs on processor A for the first step but is switched to processor B for the second step while thread Y gets run on processor A and also reaches the second step. The two threads are now running on different processors, X on B and Y on A, but both have pointers to the KPRCB for processor A and seek to increment the same counter concurrently. Without at least a lock prefix, the two processors’ reads and writes for their increments can be interleaved and one of the increments may be lost. Whether someone at Microsoft deduced this as having happened in a real-world case or merely contemplated it may never be known, but version 6.0 changes from inc to a lock xadd. For the limited purpose of these counters, this is good enough: given that thread X in the preceding scenario does run on both processors A and B in and around whatever event is being counted, who’s to say the count is wrong to go to one processor rather than the other?

This case—here just to lose an increment for statistical use only—is arguably of no great consequence. This may be why it went unattended for a decade or so. But this case is also as simple as problems with multi-processing can be, and yet it is hardly trivial. So, let that be a warning for accessing KPRCB members in real-world code!

Documentation Status

The KPRCB is not documented. Though C-language definitions of the KPRCB are in NTDDK.H from as long ago as the Device Driver Kit (DDK) for Windows NT 3.51, they are for other processors than the x86, do not continue beyond Windows XP, and are anyway incomplete. Microsoft’s first known disclosure of a C-language definition of the KPRCB for the x86 is in the NTOSP.H from early editions of the WDK for Windows 10. Publication of this header was possibly an oversight—Microsoft did not repeat it for the 1607 edition—and its definition too is incomplete. Comments explain that the published definitions are just of an “architecturally defined section” that “may be directly addressed by vendor/platform specific HAL code and will not change from version to version of NT.”

The practical equivalent of a C-language definition of the whole structure is available as type information in public symbol files for the kernel, starting with Windows 2000 SP3. Starting with Windows 8, these also tell that the type information came from compiling I386_X.H. Some symbol files, e.g., those for the HAL in Windows 7 and higher, have type information for only the architecturally defined section at the structure’s start. From these it is known that the incomplete definition is not only in NTOSP.H but also in NTHAL.H.


The KPRCB is highly variable. The layout changes not just from one version to another but even between builds. To save space and tedium, this article’s presentation of the variations refers to early and late builds of some versions, as defined in the following table of the structure’s varying size:

Version Whole Structure Architecturally Defined Section
3.10 0x0298 0x01BC
3.50 0x0348
3.51 0x0360
early 4.0 (before SP4);
late 4.0
5.0 0x09F0 0x043C
early 5.1 (before SP2);
late 5.1
0x0C50 0x04A0
early 5.2 (before SP1) 0x0DD0
late 5.2 0x0EC0 0x0520
early 6.0 (before SP1) 0x1F98
late 6.0 0x2008 0x05A0
6.1 0x3628 0x04A0
6.2 0x4160
6.3 0x4508
10.0 to 1703 0x4900
1709 0x4940
1803 to 2004 0x5F00

These sizes, and the names, types and offsets below, are from Microsoft’s public symbols for the kernel, starting from Windows 2000 SP3. Similarly definitive sources are scarce for earlier versions. A statically linked library named craShlib.Lib (sic) from sample code in a Software Development Kit for Windows NT 4.0 has an early form of type information for the KPRCB. Members are also named by the !strct command of the KDEX2X86 debugger extension for Windows NT 4.0 and Windows 2000. For most versions before Windows 2000 SP3, members are not known with certainty. Much use in the earliest versions can be more or less easily matched to use in versions for which names and types are known from symbol files. But where such correspondence isn’t known, Microsoft’s names are lost to history and types can only be guessed. It seems I never knew of any use for some large tracts of the KPRCB in the earliest versions. There’s only so much that’s practical to do about that now.

Please bear in mind that the KPRCB is among the largest of kernel-mode structures, not just in terms of size but of member count. There have been frequent rearrangements such that finding a good presentation even for the versions that are known from symbol files can only ever be a work in progress.

Architecturally Defined Section

Relative to the overall variability of the KPRCB, even just of the architecturally defined section, the very beginning of the structure is remarkably stable through many versions. The first change at all is that Windows Vista found space for a NestingLevel in what had been Reserved and refined the definition of the CpuStep (to clarify that the high and low bytes are model and stepping, respectively). The first change that breaks the stability of this region is when Windows 7 removes SetMember and thus shifts almost all subsequent members of the architecturally defined section.

Offset Definition Versions Remarks
USHORT MinorVersion;
USHORT MajorVersion;
KTHREAD *CurrentThread;
KTHREAD *NextThread;
KTHREAD *IdleThread;
CHAR Number;
3.10 to 5.2  
UCHAR Number;
6.0 only next as ULONG at 0x03CC
UCHAR LegacyNumber;
6.1 and higher  
CHAR Reserved;
3.10 to 5.2  
UCHAR NestingLevel;
6.0 and higher  
USHORT BuildType;
3.10 and higher  
3.10 to 6.0 next as GroupSetMember at 0x03C8
0x18 (3.10 to 6.0);
CHAR CpuType;
0x19 (3.10 to 6.0);
0x1A (3.10 to 6.0);
3.10 to 5.2  
union {
    USHORT CpuStep;
    struct {
        UCHAR CpuStepping;
        UCHAR CpuModel;
6.0 and higher  
0x1C (3.10 to 6.0);

By no stretch can even the architecturally defined section be thought to have lived up to the comment that it “will not change from version to version of NT”, but the MinorVersion and MajorVersion never have changed. All known versions of the x86 kernel set both to 1. From NTDDK.H for other processors in versions 3.51 to 5.1 inclusive, it can be inferred that Microsoft has the symbolic names PRCB_MAJOR_VERSION and PRCB_MINOR_VERSION for these version numbers, which NTOSP.H confirms for the x86.

Checked builds of the kernel set the 0x0001 bit in the BuildType. Single-processor builds set the 0x0002 bit. Whether a kernel is checked or single-processor is up to the kernel. There can be surprises. For instance, a checked build of NTOSKRNL.EXE for Windows NT 3.51 has the 0x0001 bit set but the 0x0002 bit clear because although it has the name of a single-processor build it has the code of a multi-processor build. Again, Microsoft’s names PRCB_BUILD_DEBUG and PRCB_BUILD_UNIPROCESSOR for these bits can be inferred from definitions for other processors in early versions and is confirmed by the NTOSP.H that is published for Windows 10.

The CpuType is what the processor manuals refer to as the family. In eax from cpuid leaf 1, bits 8 to 11 inclusive make a 4-bit family. If all four bits are set, then an 8-bit family to keep as the CpuType is formed by adding the 4-bit family, i.e., 15, to the 8-bit extended family from bits 20 to 27. Or so things go now, both in Intel’s manuals and in the Windows kernel. Beware, though, that the kernel has not always computed it this way exactly. The extended family is ignored before version 5.1. The earliest versions, up to but not including the version 4.0 from Windows NT 4.0 SP6, read the family just from bits 8 to 10. Indeed, this departure from Intel’s specification of a 4-bit family seems likely as the reason that Intel had to introduce the extended family.

The CpuID is redundant now that Windows assumes all processors have the cpuid instruction. Up to and including version 6.2, it is set to 1 if the processor has an acceptable cpuid instruction. Starting with version 3.50, acceptable means that the instruction supports at least leaf 1. Before the version 4.0 from Windows NT 4.0 SP6, it means also that the instruction does not support any leaf higher than 3. In version 6.3 and higher, CpuID is necessarily 1 (except briefly while the KPRCB exists but the processor’s family, model and stepping aren’t yet known).

The CpuStepping and CpuModel are named straightforwardly from the manuals. The CpuStepping is bits 0 to 3 inclusive of eax from cpuid leaf 1. The CpuModel is bits 4 to 7 inclusive, except that versions 5.1 and higher allow for two cases in which four high bits of the 8-bit CpuModel come from an extended model in bits 16 to 19. This use of the extended model is indicated if either: the 4-bit family is 15; or it is 6 and the vendor as known from cpuid leaf 0 is either GenuineIntel or, in version 6.2 and higher, CentaurHauls.

Within a processor family, the CpuModel and CpuStepping are much like major and minor version numbers. They usually are taken together, especially to compare against some cut-off, as when some feature flag is set but the indicated feature is thought to have been faulty before some model and stepping—or, the other way round, when the feature is known to have been present without the formal indication. Versions 3.10 and 3.50 have a case of taking the CpuID and CpuType together as a word, which may be a coding oversight but at least has some merit as comparing for either a minimum family or cpuid support. Versions before 6.3 have a curious case of taking CpuModel, CpuStepping, CpuID and CpuType together as a dword. This isn’t credibly anything other than a coding oversight. It will have had a possibly harmless side-effect for versions up to and including 3.51 when running on an 80386: presence of an 80387 will have caused the kernel to set the cr0 bit NE (5) which the 80386 doesn’t have.

Whatever Microsoft may have started out meaning by “architecturally defined”, it did not mean even as early as Windows 2000 that architecturally defined members do not move. The KPROCESSOR_STATE contains a CONTEXT. The latter has long been documented, with C-language definitions in WINNT.H and NTDDK.H, but because its size changed for Windows 2000, due to the addition of ExtendedRegisters, even the ancient KPRCB members that are defined beyond this point all change their position at least once.

Kernel And HAL Reservations

Two specifically reserved areas, one each for the kernel and the HAL, are also supposed to be architectural. The kernel’s reserved area starts to get fleshed out with Windows 8.1, though only then to define two members at the start.

Offset Definition Versions Remarks
KNODE *ParentNode;
6.3 and higher previously at 0x04CC
CHAR *PriorityState;
6.3 and higher  
0x013C (3.10 to 4.0);
0x033C (5.0 to 6.0);
0x0338 (6.1 to 6.2);
ULONG KernelReserved [0x10];
3.10 to 6.2  
ULONG KernelReserved [0x0E];
6.3 and higher  
0x017C (3.10 to 4.0);
0x037C (5.0 to 6.0);
ULONG HalReserved [0x10];

The HAL’s reserved area certainly does get used by at least some HALs—even from long ago, e.g., HALAPIC.DLL from Windows NT 3.51—but the kernel knows nothing of it.

Architectural Padding

The preceding reservations for the kernel and HAL look like they originally ended the architecturally defined section: the next few members in versions 3.10 to 4.0 survive to later versions whose symbol files place them firmly beyond the reach of the NTOSP.H definition. Version 5.0 inserted an array of per-processor spin lock queues as the architecturally defined section’s new end: see LockQueue at offset 0x03BC in version 5.0. Version 5.1 pushed this array further into the structure by inserting 0x5C bytes explicitly as padding: see PrcbPad0, below.

There seem to have been two intentions. One is that the LockQueue, initially at offset 0x03BC, should have some cache alignment. For reasons not yet understood, the cache alignment is not of the array from its beginning but from its second element. This is clearly by design, for the same outcome is achieved differently in the x64 KPRCB, and it is confirmed by a comment in the NTOSP.H from the WDK for Windows 10:

// N.B. The following padding is such that the first lock entry falls in the
//      last eight bytes of a cache line.

A plausible second intention was to set aside not the bare minimum for cache alignment but enough space that future additions to the architecturally defined section should not again shift the lock queues (which are presumably at the end so that they too can grow without shifting anything that’s architectural).

These future additions arrived with Windows Vista, which claims four bytes at the start. Its first Service Pack starts bringing members from (much) further into the structure, presumably for better-defined access to them from outside the kernel—and the HAL certainly does access many of them. But note two things. First, these members were not moved here to remain at their new locations forever. In particular, the CpuVendor was moved into this padding for version 6.1 but then version 6.3 reordered it relative to what remained of the padding. Second, no matter what is added, or even moved after being added, the padding is always adjusted—and even split—so that whatever was at offset 0x0418 after this insertion for version 5.1 is there forever.

Offset Definition Versions Remarks
0x03BC (6.0);
ULONG CFlushSize;
6.0 and higher  
0x03C0 (late 6.0);
UCHAR CoresPerPhysicalProcessor;
late 6.0 and higher previously at 0x1BBA
0x03C1 (late 6.0);
UCHAR LogicalProcessorsPerCore;
late 6.0 and higher previously at 0x1F8C
UCHAR CpuVendor;
6.3 and higher previously at 0x03C4
0x03BC (5.1 to 5.2);
0x03C0 (early 6.0);
0x03C2 (late 6.0);
0x03BE (6.1 to 6.2);
UCHAR PrcbPad0 [0x5C];
5.1 to 5.2  
UCHAR PrcbPad0 [0x58];
early 6.0 only  
UCHAR PrcbPad0 [2];
late 6.0 to 6.2  
UCHAR PrcbPad0 [1];
6.3 and higher  
0x03C4 (late 6.0);
late 6.0 and higher previously at 0x1BBC
0x03C4 (6.1 to 6.2)
UCHAR CpuVendor;
6.1 to 6.2 previously at 0x1C28;
next at 0x03BE
0x03C5 (6.1 to 6.2);
UCHAR GroupIndex;
6.1 and higher  
0x03C6 (6.1 to 6.2);
6.1 to 6.2  
UCHAR Group;
6.3 and higher  
UCHAR PrcbPad05 [2];
6.3 and higher  
KAFFINITY GroupSetMember;
6.1 and higher previously as SetMember at 0x14
ULONG Number;
6.1 and higher previously UCHAR at 0x10
BOOLEAN ClockOwner;
6.2 and higher  
UCHAR PendingTick;
6.2 only  
union {
    UCHAR PendingTickFlags;
    struct {
        UCHAR PendingTick : 1;          // 0x01
        UCHAR PendingBackupTick : 1;    // 0x02
6.3 and higher  
0x03C8 (late 6.0);
0x03D0 (6.1);
UCHAR PrcbPad1 [0x50];
late 6.0 only  
UCHAR PrcbPad1 [0x48];
6.1 only  
UCHAR PrcbPad1 [0x46];
6.2 only  
UCHAR PrcbPad10 [0x46];
6.3 and higher  

The CFlushSize is the size of the cache line in bytes as used for the clflush and clflushopt instructions. The kernel’s first use of clflush is for the exported function KeInvalidateRangeAllCaches in version 6.0. Other uses have been found since, and this first use is replaced in version 10.0 and higher by clflushopt if it’s available. In all versions, the cache line’s size is computed as eight times the second lowest byte, i.e., bits 8 to 15 inclusive, of what cpuid leaf 1 returns in ebx.

The number of CoresPerPhysicalProcessor defaults to 1, of course. That it can be anything else isn’t noticed before version 6.0. Generally, it is one more than what cpuid leaf 4 returns in the high 6 bits of eax. For AMD processors, the kernel instead interprets what cpuid leaf 0x80000008 returns in ecx. Either way, versions 6.1 and higher round up to a power of two.

The CpuVendor is a convenient interpretation of the CPU vendor string that is produced by cpuid leaf 0. It takes its values from the CPU_VENDORS enumeration.

Spin Lock Queues

That the kernel has a “per processor lock queue” is well known from comments in NTDDK.H from as long ago as the DDK for Windows XP (moved to WDM.H in the WDK for Windows Vista). The comments come a little before the definition of the KSPIN_LOCK_QUEUE structure, which appears to be provided only so that callers of such newly introduced HAL functions as KeAcquireInStackQueuedSpinLock can create the necessary KLOCK_QUEUE_HANDLE. Less well known is that this structure, and queued spin locks as a feature, date from Windows 2000. This first existence was not for general use but for dedicated purposes, with queueing supported only through the following array for each processor:

Offset Definition Versions
0x03BC (5.0);
KSPIN_LOCK_QUEUE LockQueue [0x10];
5.0 to 5.1
KSPIN_LOCK_QUEUE LockQueue [LockQueueMaximumLock];
5.2 and higher
0x0498 (5.1 to early 5.2)
UCHAR PrcbPad1 [8];
5.1 to early 5.2

As noted above, trouble is taken to keep the LockQueue reliably at offset 0x0418 ever since Windows XP. Comparison with the 64-bit KPRCB suggests that what’s wanted is perhaps not a specific offset but that the second entry is cache-aligned or that the first is in a separate cache line from all the others. Such alignment to 0x40 bytes becomes ever more important through successive versions. Remember always that what counts for alignment is not the offset within the KPRCB but within the containing KPCR (which is necessarily page-aligned). Programmers who deal much with the KPCR and KPRCB in the debugger—and reverse engineers—will be familiar with adding and subtracting 0x0120.

The LockQueue array is indexed by members of the enumeration KSPIN_LOCK_QUEUE_NUMBER, which is defined in WDM.H and is used in NTIFS.H for the declarations of the exported functions such as KeAcquireQueuedSpinLock that operate on the applicable spin locks through these per-processor lock queues. These declarations would have it that the functions require at least Windows XP. The functions are documented, but only to say they’re reserved.

The array initially allows for 0x10 entries even though use was not defined for all 0x10 of them until version 5.2 The versions that trouble over cache-alignment for the second entry also have either that the array ends at a cache-line boundary or is padded to the next boundary. Since version 5.1, then, the non-architectural section is cache-aligned.


The NTOSP.H from the WDK for Windows 10 confirms LockQueue as the last member of the architecturally defined section that is all that NTOSP.H defines of the KPRCB. Though trouble has been taken through successive revisions to keep the LockQueue array at a reliable offset, the array’s highly variable size through versions 5.1 to 6.1 means that most members in the non-architectural section shift wildly in these versions even when not reordered.

Offset Definition Versions Remarks
0x01BC (3.10 to 4.0);
0x043C (5.0);
0x04A0 (5.1 to early 5.2);
0x0520 (late 5.2 to early 6.0);
0x05A0 (late 6.0);
0x04A0 (6.1 to 6.2)
KTHREAD *NpxThread;
3.10 to 6.2 cache-aligned in 5.1 and higher
0x01C0 (3.10 to 4.0);
0x0440 (5.0);
0x04A4 (5.1 to early 5.2);
0x0524 (late 5.2 to early 6.0);
0x05A4 (late 6.0);
0x04A4 (6.1 to 6.2);
ULONG InterruptCount;
0x01C8 (3.10);
0x01C4 (3.50 to 4.0);
0x0444 (5.0);
0x04A8 (5.1 to early 5.2);
0x0528 (late 5.2 to early 6.0);
0x05A8 (late 6.0);
0x04A8 (6.1 to 6.2);
3.10 only  
ULONG KernelTime;
3.50 and higher  
0x01D0 (3.10);
0x01C8 (3.50 to 4.0);
0x0448 (5.0);
0x04AC (5.1 to early 5.2);
0x052C (late 5.2 to early 6.0);
0x05AC (late 6.0);
0x04AC (6.1 to 6.2);
3.10 only  
ULONG UserTime;
3.50 and higher  
0x01D8 (3.10);
0x01CC (3.50 to 4.0);
0x044C (5.0);
0x04B0 (5.1 to early 5.2);
0x0530 (late 5.2 to early 6.0);
0x05B0 (late 6.0);
0x04B0 (6.1 to 6.2);
3.10 only  
ULONG DpcTime;
3.50 and higher  
0x04B4 (5.1 to early 5.2);
0x0534 (late 5.2 to early 6.0);
0x05B4 (late 6.0);
0x04B4 (6.1 to 6.2);
ULONG DebugDpcTime;
5.1 to 5.2 previously at 0x0460
ULONG DpcTimeCount;
6.0 and higher  
0x01E0 (3.10);
0x01D0 (3.50 to 4.0);
0x0450 (5.0);
0x04B8 (5.1 to early 5.2);
0x0538 (late 5.2 to early 6.0);
0x05B8 (late 6.0);
0x04B8 (6.1 to 6.2);
LARGE_INTEGER InterruptTime;
3.10 only  
ULONG InterruptTime;
3.50 and higher  

The InterruptCount, KernelTime, UserTime, DpcTime and InterruptTime are all retrievable through the SystemProcessorPerformanceInformation case of NtQuerySystemInformation in all known versions. See that version 3.10 keeps the raw 64-bit times. Later versions scale by the so-called maximum increment.

The exported KeUpdateRunTime function that updates those times (and DpcTimeCount in version 6.0 and higher) also reduces the current thread’s quantum. If this leaves the thread with no more quantum, version 3.10 schedules a DPC that will find another thread to run. This was improved upon as early as version 3.50 so that the 0x20 bytes of this KDPC whose name may never be known seem to have became spare. This is here supposed as the origin of the Spare2 that is known from symbol files in later versions.

Offset Definition Versions Remarks
0x01D4 (3.51 to 4.0);
0x0454 (5.0)
ULONG ApcBypassCount;
3.51 to 5.0  
0x01D8 (3.51 to 4.0);
0x0458 (5.0)
ULONG DpcBypassCount;
3.51 to 5.0  
0x01DC (3.51 to 4.0);
0x045C (5.0)
ULONG AdjustDpcThreshold;
3.51 to 5.0 next at 0x04BC
0x0460 (5.0)
ULONG DebugDpcTime;
5.0 only next at 0x04B4
0x01E8 (3.10);
0x01D4 (3.50);
0x01E0 (3.51 to 4.0);
0x0464 (5.0)
unknown KDPC 3.10 only  
ULONG Spare2 [8];
3.50 only  
ULONG Spare2 [5];
3.51 to 4.0  
ULONG Spare2 [4];
5.0 only  

The ApcBypassCount and DpcBypassCount are retrievable through the SystemInterruptInformation (0x17) case of NtQuerySystemInformation in the applicable versions. Since their discontinuation in version 5.1, the corresponding members in the retrieved information are both zero.

No use of DebugDpcTime is known in any version, either here (where type information from public symbol files for the kernel places it definitively, at least for the later builds of version 5.0) or at its next position until version 6.0 reuses it (or relabels it) as DpcTimeCount. Type information in CRASHLIB.LIB from Dr. Watson sample code in the SDK for Windows NT 4.0 confirms that Spare2 leaves no room for DebugDpcTime in earlier versions.  

Offset Definition Versions Remarks

0x04BC (5.1 to early 5.2);
0x053C (late 5.2 to early 6.0);
0x05BC (late 6.0);
0x04BC (6.1 to 6.2);
ULONG AdjustDpcThreshold;
5.1 and higher previously at 0x045C
0x04C0 (5.1 to early 5.2);
0x0540 (late 5.2 to early 6.0);
0x05C0 (late 6.0);
0x04C0 (6.1 to 6.2);
ULONG PageColor;
5.1 and higher  
0x04C4 (5.1 to early 5.2);
0x0544 (late 5.2 to early 6.0);
0x05C4 (late 6.0)
ULONG SkipTick;
5.1 only previously UCHAR at 0x072C
UCHAR SkipTick;
5.2 to 6.0  
0x04C8 (5.1)
UCHAR MultiThreadSetBusy;
5.1 only  
0x04C5 (early 5.2);
0x0545 (late 5.2 to early 6.0);
0x05C5 (late 6.0);
0x04C4 (6.1 to 6.2);
UCHAR DebuggerSavedIRQL;
5.2 and higher  
0x0546 (late 5.2 to early 6.0);
0x05C6 (late 6.0);
0x04C5 (6.1 to 6.2);
UCHAR NodeColor;
late 5.2 and higher  
UCHAR DeepSleep;
10.0 and higher  
UCHAR TbFlushListActive;
1803 and higher  

0x04C9 (5.1);
0x04C6 (early 5.2);
0x0547 (late 5.2)
UCHAR Spare2 [3];
5.1 only  
UCHAR Spare1 [6];
early 5.2 only  
UCHAR Spare1;
late 5.2 only  
0x547 (early 6.0);
0x05C7 (late 6.0)
UCHAR PollSlot;
6.0 only  
0x04C6 (6.1 to 6.2);
0x04C2 (6.3);
0x04C3 (10.0 to 1709)
UCHAR PrcbPad20 [2];
6.1 to 6.2  
UCHAR PrcbPad20 [6];
6.3 only  
UCHAR PrcbPad20 [5];
10.0 to 1703  
UCHAR PrcbPad20;
1709 only  
PVOID volatile CachedStack;
1709 and higher  
0x0548 (late 5.2 to early 6.0);
0x05C8 (late 6.0);
ULONG NodeShiftedColor;
late 5.2 and higher  
0x04CC (5.1 to early 5.2);
0x054C (late 5.2 to early 6.0);
0x05CC (late 6.0);
0x04CC (6.1 to 6.2)
KNODE *ParentNode;
5.1 to 6.2 next at 0x0338
0x04D0 (5.1 to early 5.2);
0x0550 (late 5.2 to early 6.0);
0x05D0 (late 6.0)
ULONG MultiThreadProcessorSet;
5.1 to 6.0  
0x04D4 (5.1 to early 5.2);
0x0554 (late 5.2 to early 6.0);
0x05D4 (late 6.0)
KPRCB *MultiThreadSetMaster;
5.1 to 6.0  

No use of ThreadStartCount, below, is known in any version before symbol files show its reuse for late builds of version 5.2. That it is defined as early as version 3.51 is uncertain. It is confirmed for version 4.0 from type information in the statically linked library CRASHLIB.LIB from contemporaneous Dr. Watson sample code.

Offset Definition Versions Remarks
0x01F4 (3.51 to 4.0);
0x0474 (5.0);
0x04D8 (5.1 to early 5.2)
ULONG ThreadStartCount [2];
3.51 to early 5.2  
0x0558 (late 5.2 to early 6.0);
0x05D8 (late 6.0);
0x04D0 (6.1 to 6.2);
ULONG SecondaryColorMask;
late 5.2 and higher  
0x055C (late 5.2 to early 6.0);
0x05DC (late 6.0);
0x04D4 (6.1 to 6.2);
LONG Sleeping;
late 5.2 only next at 0x19C8
ULONG DpcTimeLimit;
6.0 and higher  

Inter-Processor Interrupts

The very earliest versions deal here with inter-processor interrupts. The implementation soon increased in sophistication and the supporting members were relocated much further into the KPRCB (and reordered).

Offset Definition Versions Remarks
0x0208 (3.10);
0x01F4 (3.50)
BOOLEAN volatile IpiCommands [4];
3.10 to 3.50 next at 0x02C0
0x020C (3.10);
0x01F8 (3.50)
UCHAR volatile IpiFrozen;
3.10 to 3.50 next at 0x02CC
0x0210 (3.10);
0x01FC (3.50)
ULONG volatile ReverseStall;
3.10 to 3.50 next at 0x02C8
0x0214 (3.10);
0x0200 (3.50)
ULONG volatile IpiLastPacket;
3.10 to 3.50 next at 0x02C4
0x0218 (3.10);
0x0204 (3.50)
unaccounted 0x20 bytes 3.10 to 3.50  

The IpiCommands array records which of the four possible types of packet-less request are being sent to this processor. They are set by the KiIpiSend function (which is exported in these versions) on the way to asking the HAL’s HalRequestIpi function to interrupt this processor. They get cleared at this processor by the kernel’s exported KiIpiServiceRoutine. The name IpiCommands is inferred with reasonable certainty from the I386KD debugger for version 3.10 and the KDEXTX86 debugger extension for version 3.51. Their descriptions of the four types of request are: APC, DPC, Clock and Freeze. These commands are essentially signals to this processor to start some pre-determined activity such as executing Deferred Procedure Calls. Each signal is either set or not. Each can have been set by multiple sending processors concurrently before this processor gets interrupted.

The names IpiFrozen and IpiLastPacket are also known from the debugger (and the latter from the extension).

The IpiLastPacket is the 32-bit sequence number of the packet that the processor last serviced (or most recently started servicing). In these versions, a processor can send a packet-based request to arbitrarily many processors but multiple processors cannot send requests concurrently. The packet’s three parameters (or four, in version 3.50) and the address of the worker routine that is to execute for the packet on each target processor are kept in the kernel’s own data. So too is an array of boolean flags, one for each of the possible 32 processors, which the worker routine must signal. The sending processor holds a spin lock and waits for the signal from each target processor. Among the inefficiencies is that while the sending processor waits, any target processor can receive not just the one inter-processor interrupt to service the packet-based request but others for packet-less commands. The sequence number is the defence against re-servicing a packet. Whether this sequence number is signed or unsigned is not known: type information never becomes available because it does not survive to version 4.0, which provides for concurrent senders.

Per-Processor File Locking

Two pointers that are explicitly spare in version 5.0 according to symbol files are known to be used in the earliest Windows versions. They each point to a chain of freed fixed-sized allocations for managing file locks. The first pointer is for shared locks, the second for exclusive. The allocations are containers for a copy of the FILE_LOCK_INFO structure that is given when locking bytes in a file. Some number (10 in 3.51 but 8 in 4.0) of allocations are obtained in advance from the non-paged pool and are pre-freed to these caches. In essence, these are rough-and-ready implementations of what version 5.0 would formalise as per-processor look-aside lists (see PPLookasideList, below, at offset 0x0500). Late builds of version 4.0 do indeed change to lookaside lists for file locking, but they use ordinary look-aside lists in the kernel’s data, and per-processor caching for file locking never is returned to.

Offset Definition Versions
0x0238 (3.10);
0x0224 (3.50);
0x01FC (3.51 to 4.0);
0x047C (5.0)
SINGLE_LIST_ENTRY FsRtlFreeSharedLockList;
3.10 to early 4.0
PVOID SpareHotData [2];
late 4.0 to 5.0
0x023C (3.10);
0x0228 (3.50);
0x0200 (3.51 to early 4.0)
SINGLE_LIST_ENTRY FsRtlFreeExclusiveLockList;
3.10 to early 4.0

Performance Counters

Though performance counters evidently were never intended as part of an “architecturally defined section”, the KPRCB has at least some from the very beginning, as with the following which help assess the performance of Fast I/O by the file system (including third-party file system drivers). That the kernel keeps a “per processor control block of cache manager system counters” is disclosed by Microsoft in documentation of such functions as FsRtlIncrementCcFastReadNoWait.

The point to counting separately for each processor is typically not to learn how the count for any one processor differs from that for any other. The total count is what matters, but keeping it is faster if done in parts. Given that the counter for each processor is incremented only by code that is running on that processor, the increments can be done safely and quickly without concern for synchronisation. They don’t even need to be interlocked. To evaluate the counter is to tally the per-processor instances, but since this will be wanted only infrequently its overhead is nothing relative to the gain from the likely numerous increments.

Sets of counters that are likely to be touched together would better be in the same cache line. In versions 5.1 to 6.0, the first six counters do indeed start at a 64-byte address boundary, relative to the containing KPCR. Although this cache alignment is achieved without explicit padding, it seems unlikely to be accidental, especially given that version 5.1 is when Windows starts paying attention to cache lines. Later versions work to keep it, first by padding explicitly, then by bringing the space into use:

Offset Definition Versions
0x04D8 (6.1 to 6.2);
0x04D4 (6.3 to 1709)
ULONG PrcbPad21 [2];
6.1 to 6.2
ULONG PrcbPad21 [3];
6.3 to 1703
ULONG PrcbPad21 [2];
1709 only
PVOID MmFlushList;
1803 only
PVOID MmInternal;
1809 and higher
1803 and higher
PVOID SchedulerAssist;
1709 and higher

Totals for the first six of the per-processor performance counters have always been retrievable through the SystemPerformanceInformation case of NtQuerySystemInformation.

Offset Definition Versions Remarks
0x0240 (3.10);
0x022C (3.50);
0x0204 (3.51 to 4.0);
0x0484 (5.0);
0x04E0 (5.1 to early 5.2);
0x0560 (late 5.2 to early 6.0);
0x05E0 (late 6.0);
ULONG CcFastReadNoWait;
all cache-aligned in 5.1 and higher
0x0244 (3.10);
0x0230 (3.50);
0x0208 (3.51 to 4.0);
0x0488 (5.0);
0x04E4 (5.1 to early 5.2);
0x0564 (late 5.2 to early 6.0);
0x05E4 (late 6.0);
ULONG CcFastReadWait;
0x0248 (3.10);
0x0234 (3.50);
0x020C (3.51 to 4.0);
0x048C (5.0);
0x04E8 (5.1 to early 5.2);
0x0568 (late 5.2 to early 6.0);
0x05E8 (late 6.0);
ULONG CcFastReadNotPossible;
0x024C (3.10);
0x0238 (3.50);
0x0210 (3.51 to 4.0);
0x0490 (5.0);
0x04EC (5.1 to early 5.2);
0x056C (late 5.2 to early 6.0);
0x05EC (late 6.0);
ULONG CcCopyReadNoWait;
0x0250 (3.10);
0x023C (3.50);
0x0214 (3.51 to 4.0);
0x0494 (5.0);
0x04F0 (5.1 to early 5.2);
0x0570 (late 5.2 to early 6.0);
0x05F0 (late 6.0);
ULONG CcCopyReadWait;
0x0254 (3.10);
0x0240 (3.50);
0x0218 (3.51 to 4.0);
0x0498 (5.0);
0x04F4 (5.1 to early 5.2);
0x0574 (late 5.2 to early 6.0);
0x05F4 (late 6.0);
ULONG CcCopyReadNoWaitMiss;

Windows Vista adds substantially, including some that Windows Server 2003 SP1 had added further on. All except MmSpinLockOrdering are retrievable through the SystemPerformanceInformation case of NtQuerySystemInformation (as are the preceding originals). They always have been, but as simple counters in kernel data. It just took a while before Windows counted them in per-processor parts.

Offset Definition Versions Remarks
0x0578 (early 6.0);
0x05F8 (late 6.0);
LONG volatile MmSpinLockOrdering;
6.0 and higher  
0x057C (early 6.0);
0x05FC (late 6.0);
LONG volatile IoReadOperationCount;
6.0 and higher previously at 0x059C
0x0580 (early 6.0);
0x0600 (late 6.0);
LONG volatile IoWriteOperationCount;
6.0 and higher previously at 0x05A0
0x0584 (early 6.0);
0x0604 (late 6.0);
LONG volatile IoOtherOperationCount;
6.0 and higher previously at 0x05A4
0x0588 (early 6.0);
0x0608 (late 6.0);
LARGE_INTEGER IoReadTransferCount;
6.0 and higher previously at 0x05A8
0x0590 (early 6.0);
0x0610 (late 6.0);
LARGE_INTEGER IoWriteTransferCount;
6.0 and higher previously at 0x05B0
0x0598 (early 6.0);
0x0618 (late 6.0);
LARGE_INTEGER IoOtherTransferCount;
6.0 and higher previously at 0x05B8
0x05A0 (early 6.0);
0x0620 (late 6.0);
ULONG CcFastMdlReadNoWait;
6.0 and higher  
0x05A4 (early 6.0);
0x0624 (late 6.0);
ULONG CcFastMdlReadWait;
6.0 and higher  
0x05A8 (early 6.0);
0x0628 (late 6.0);
ULONG CcFastMdlReadNotPossible;
6.0 and higher  
0x05AC (early 6.0);
0x062C (late 6.0);
ULONG CcMapDataNoWait;
6.0 and higher  
0x05B0 (early 6.0);
0x0630 (late 6.0);
ULONG CcMapDataWait;
6.0 and higher  
0x05B4 (early 6.0);
0x0634 (late 6.0);
ULONG CcPinMappedDataCount;
6.0 and higher  
0x05B8 (early 6.0);
0x0638 (late 6.0);
ULONG CcPinReadNoWait;
6.0 and higher  
0x05BC (early 6.0);
0x063C (late 6.0);
ULONG CcPinReadWait;
6.0 and higher  
0x05C0 (early 6.0);
0x0640 (late 6.0);
ULONG CcMdlReadNoWait;
6.0 and higher  
0x05C4 (early 6.0);
0x0644 (late 6.0);
ULONG CcMdlReadWait;
6.0 and higher  
0x05C8 (early 6.0);
0x0648 (late 6.0);
ULONG CcLazyWriteHotSpots;
6.0 and higher  
0x05CC (early 6.0);
0x064C (late 6.0);
ULONG CcLazyWriteIos;
6.0 and higher  
0x05D0 (early 6.0);
0x0650 (late 6.0);
ULONG CcLazyWritePages;
6.0 and higher  
0x05D4 (early 6.0);
0x0654 (late 6.0);
ULONG CcDataFlushes;
6.0 and higher  
0x05D8 (early 6.0);
0x0658 (late 6.0);
ULONG CcDataPages;
6.0 and higher  
0x05DC (early 6.0);
0x065C (late 6.0);
ULONG CcLostDelayedWrites;
6.0 and higher  
0x05E0 (early 6.0);
0x0660 (late 6.0);
ULONG CcFastReadResourceMiss;
6.0 and higher  
0x05E4 (early 6.0);
0x0664 (late 6.0);
ULONG CcCopyReadWaitMiss;
6.0 and higher  
0x05E8 (early 6.0);
0x0668 (late 6.0);
ULONG CcFastMdlReadResourceMiss;
6.0 and higher  
0x05EC (early 6.0);
0x066C (late 6.0);
ULONG CcMapDataNoWaitMiss;
6.0 and higher  
0x05F0 (early 6.0);
0x0670 (late 6.0);
ULONG CcMapDataWaitMiss;
6.0 and higher  
0x05F4 (early 6.0);
0x0674 (late 6.0);
ULONG CcPinReadNoWaitMiss;
6.0 and higher  
0x05F8 (early 6.0);
0x0678 (late 6.0);
ULONG CcPinReadWaitMiss;
6.0 and higher  
0x05FC (early 6.0);
0x067C (late 6.0);
ULONG CcMdlReadNoWaitMiss;
6.0 and higher  
0x0600 (early 6.0);
0x0680 (late 6.0);
ULONG CcMdlReadWaitMiss;
6.0 and higher  
0x0604 (early 6.0);
0x0684 (late 6.0);
ULONG CcReadAheadIos;
6.0 and higher  

The preceding selection of performance counters for the Cache Manager expanded as soon as version 3.50 with some miscellany for the Kernel Core. Several are retrievable through NtQuerySystemInformation:

No use is known of KeDcacheFlushCount or KeIcacheFlushCount in any version.

Offset Definition Versions Remarks
0x0244 (3.50);
0x021C (3.51 to 4.0);
0x049C (5.0);
0x04F8 (5.1 to early 5.2);
0x0578 (late 5.2);
0x0608 (early 6.0);
0x0688 (late 6.0);
ULONG KeAlignmentFixupCount;
3.50 and higher  
0x0248 (3.50);
0x0220 (3.51 to 4.0);
0x04A0 (5.0);
0x04FC (5.1 to early 5.2);
0x057C (late 5.2)
ULONG KeContextSwitches;
3.50 to 5.1 next as ContextSwitches in KPCR
ULONG SpareCounter0;
5.2 only  
0x024C (3.50);
0x0224 (3.51 to 4.0);
0x04A4 (5.0);
0x0500 (5.1 to early 5.2);
0x0580 (late 5.2)
ULONG KeDcacheFlushCount;
3.50 to 5.2  
0x0250 (3.50);
0x0228 (3.51 to 4.0);
0x04A8 (5.0);
0x0504 (5.1 to early 5.2);
0x0584 (late 5.2);
0x060C (early 6.0);
0x068C (late 6.0);
ULONG KeExceptionDispatchCount;
3.50 and higher  
0x0254 (3.50);
0x022C (3.51 to 4.0);
0x04AC (5.0);
0x0508 (5.1 to early 5.2);
0x0588 (late 5.2)
ULONG KeFirstLevelTbFills;
3.50 to 5.2  
0x0258 (3.50);
0x0230 (3.51 to 4.0);
0x04B0 (5.0);
0x050C (5.1 to early 5.2);
0x058C (late 5.2)
ULONG KeFloatingEmulationCount;
3.50 to 5.2  
0x025C (3.50);
0x0234 (3.51 to 4.0);
0x04B4 (5.0);
0x0510 (5.1 to early 5.2);
0x0590 (late 5.2)
ULONG KeIcacheFlushCount;
3.50 to 5.2  
0x0260 (3.50);
0x0238 (3.51 to 4.0);
0x04B8 (5.0);
0x0514 (5.1 to early 5.2);
0x0594 (late 5.2)
ULONG KeSecondLevelTbFills;
3.50 to 5.2  
0x0264 (3.50);
0x023C (3.51 to 4.0);
0x04BC (5.0);
0x0518 (5.1 to early 5.2);
0x0598 (late 5.2);
0x0610 (early 6.0);
0x0690 (late 6.0);
ULONG KeSystemCalls;
3.50 and higher  
ULONG AvailableTime;
6.1 and higher  

When Windows Server 2003 SP1 added counters for I/O operations, it appended to the existing counters. But they didn’t stay for long: Windows Vista moved them forward.

Offset Definition Versions Remarks
0x059C (late 5.2)
LONG volatile IoReadOperationCount;
late 5.2 only next at 0x057C
0x05A0 (late 5.2)
LONG volatile IoWriteOperationCount;
late 5.2 only next at 0x0580
0x05A4 (late 5.2)
LONG volatile IoOtherOperationCount;
late 5.2 only next at 0x0584
0x05A8 (late 5.2)
LARGE_INTEGER IoReadTransferCount;
late 5.2 only next at 0x0588
0x05B0 (late 5.2)
LARGE_INTEGER IoWriteTransferCount;
late 5.2 only next at 0x0590
0x05B8 (late 5.2)
LARGE_INTEGER IoOtherTransferCount;
late 5.2 only next at 0x0598

The early versions follow their performance counters with space for which no use is known other than two pointers that added to the original optimisation of file locking. It is mere supposition that this unaccounted space in the earliest versions is just a larger allowance for the same reservation that is known for Windows 2000 from symbol files.

Offset Definition Versions Remarks
0x0240 (early 4.0)
SINGLE_LIST_ENTRY FsRtlFreeWaitingLockList;
early 4.0 only  
0x0244 (early 4.0)
SINGLE_LIST_ENTRY FsRtlFreeLockTreeNodeList;
early 4.0 only  
0x0258 (3.10);
0x0268 (3.50);
0x0240 (3.51);
0x0248 (early 4.0);
0x0240 (late 4.0);
0x04C0 (5.0);
0x051C (5.1 to early 5.2);
0x05C0 (late 5.2);
0x0614 (early 6.0);
0x0694 (late 6.0);
ULONG ReservedCounter [0x10];
3.10 only last member in 3.10
ULONG ReservedCounter [0x20];
3.50 to 3.51  
ULONG ReservedCounter [6];
early 4.0 only  
ULONG ReservedCounter [8];
late 4.0 to 5.0  
ULONG SpareCounter0 [1];
5.1 only  
ULONG SpareCounter1;
early 5.2 only  
ULONG SpareCounter1 [8];
late 5.2 only  
ULONG PrcbPad1 [3];
early 6.0 only  
ULONG PrcbPad2 [3];
late 6.0 only  
ULONG PrcbPad22 [2];
6.1 and higher  

That space had been set aside for counters was remembered in the names until a change for version 6.0. But whatever the name, every version from 4.0 onwards allows for expansion but takes care to adjust the padding to end exactly at the next 64-byte alignment boundary relative to the containing KPCR.

Per-Processor Lookaside Lists

Symbol files for Windows 2000 SP3 and SP4 and even the CRASHLIB.LIB for Windows NT 4.0 define the following pointers, apparently to chains of freed structures as a cache to speed their reuse. However, no use is known of them. They perhaps remain from development of the per-processor lookaside lists for special purposes (taken up next). After all, the names suggest the same purposes in the same order.

Offset Definition Versions
0x0260 (4.0);
0x04E0 (5.0)
PVOID SmallIrpFreeEntry;
4.0 to 5.0
0x0264 (4.0);
0x04E4 (5.0)
PVOID LargeIrpFreeEntry;
4.0 to 5.0
0x0268 (4.0);
0x04E8 (5.0)
PVOID MdlFreeEntry;
4.0 to 5.0
0x026C (4.0);
0x04EC (5.0)
PVOID CreateInfoFreeEntry;
4.0 to 5.0
0x0270 (4.0);
0x04F0 (5.0)
PVOID NameBufferFreeEntry;
4.0 to 5.0
0x0274 (4.0);
0x04F4 (5.0)
PVOID SharedCacheMapFreeEntry;
4.0 to 5.0
0x0278 (4.0);
0x04F8 (5.0)
ULONG CachePad0 [2];
4.0 to 5.0

Note that this first name that explicitly suggests padding for cache alignment does not actually cache-align what follows. It does within the KPRCB, but the KPRCB itself is not cache-aligned within the KPCR.

System Lookaside Lists

Though lookaside lists were introduced for version 4.0, it’s not until version 5.0 that the kernel sets some up for per-processor use. Broadly speaking, there are two separate types of per-processor lookaside lists. First come the system lookaside lists.

Offset Definition Versions Remarks
0x0500 (5.0);
0x0520 (5.1 to early 5.2);
0x05E0 (late 5.2);
0x0620 (early 6.0);
0x06A0 (late 6.0);
PP_LOOKASIDE_LIST PPLookasideList [0x10];
5.0 and higher cache-aligned in 5.1 and higher

The PPLookasideList array is indexed by the undocumented enumeration PP_NPAGED_LOOKASIDE_NUMBER. Its different values represent lookaside lists that cache very different fixed-size structures that each have a very specific purpose.

The undocumented PP_LOOKASIDE_LIST structure is a pair of pointers, P and L, to the actual lookaside lists. Ideally, they point to separate lists: the first just for the processor; the second shared. Allocations are sought first from the per-processor list, for speed, else from the shared. Allocations are freed to the per-processor list for easy re-allocation, except that if that list has reached its capacity the allocation is instead freed to the shared list.

To support the SystemLookasideInformation case of the NtQuerySystemInformation function the kernel keeps a double-linked list of all the lookaside lists that are pointed to from the PPLookasideList arrays for all processors. Although additions to the list, when building the KPRCB for a newly added processor, can be made by only one thread at a time, no synchronisation is known with this function’s enumeration of the list.

Note that in version 5.1 and higher, the PPLookasideList array starts on a 64-byte boundary, relative to the containing KPCR. Indeed, all the array that is known to be used before Windows 7 fits in one 64-byte cache line. The allowance for 0x10 lists when only 9 are yet defined is presumably intended so that the pool lookaside lists that follow are also cache-aligned.

Pool Lookaside Lists

Offset Definition Versions Remarks
GENERAL_LOOKASIDE_POOL PPNxPagedLookasideList [0x20];
6.2 and higher cache-aligned
0x0580 (5.0);
0x05A0 (5.1 to early 5.2);
0x0660 (late 5.2);
0x06A0 (early 6.0);
0x0720 (late 6.0);
0x0620 (6.1);
PP_LOOKASIDE_LIST PPNPagedLookasideList [8];
5.0 only  
PP_LOOKASIDE_LIST PPNPagedLookasideList [0x20];
5.1 to 5.2 cache-aligned
GENERAL_LOOKASIDE_POOL PPNPagedLookasideList [0x20];
6.0 and higher cache-aligned
0x05C0 (5.0);
0x06A0 (5.1 to early 5.2);
0x0760 (late 5.2);
0x0FA0 (early 6.0);
0x1020 (late 6.0);
0x0F20 (6.1);
PP_LOOKASIDE_LIST PPPagedLookasideList [8];
5.0 only  
PP_LOOKASIDE_LIST PPPagedLookasideList [0x20];
5.1 to 5.2 cache-aligned
GENERAL_LOOKASIDE_POOL PPPagedLookasideList [0x20];
6.0 and higher cache-aligned

The pool lookaside lists help with the efficiency of small allocations from various types of pool (NonPagedPool, PagedPool and, in version 6.2 and higher, NonPagedPoolNx). Successive lookaside lists in each array are for successively larger sizes of allocation from that pool type. When first introduced, for Windows 2000, the caching was relatively coarse. The first list was for all allocations up to and including 0x20 bytes, which was also the increment from one list to the next. Eight lists thus supported per-processor caching of freed pool allocations up to and including 0x0100 bytes. Windows XP and higher have the same coverage but in increments of 0x08 bytes.

To support the SystemPerformanceInformation and SystemLookasideInformation cases of the NtQuerySystemInformation function the kernel keeps a double-linked list of all the pool lookaside lists for all processors. Although additions to the list, when building the KPRCB for a newly added processor, can be made by only one thread at a time, no synchronisation is known with this function’s enumeration of the list.

Offset Definition Versions
0x0280 (4.0);
0x0600 (5.0)
ULONG ReservedPad [0x80];
4.0 only
ULONG ReservedPad [0x20];
5.0 only

Inter-Processor Interrupts

As an earlier implementation of inter-processor interrupts grew in sophistication it moved here for version 3.51, and has mostly stayed, albeit with reordering. Insertions for versions 4.0 and 5.1 put the supporting members into three sets, each in its own cache line.

Offset Definition Versions Remarks
0x07A0 (5.1 to early 5.2);
0x0860 (late 5.2);
0x18A0 (early 6.0);
0x1920 (late 6.0);
0x1820 (6.1);
ULONG volatile PacketBarrier;
5.1 and higher cache-aligned
0x07A4 (5.1 to early 5.2);
0x0864 (late 5.2);
0x18A4 (early 6.0);
0x1924 (late 6.0);
0x1824 (6.1);
ULONG volatile ReverseStall;
5.1 and higher previously at 0x06A8
0x07A8 (5.1 to early 5.2);
0x0868 (late 5.2);
0x18A8 (early 6.0);
0x1928 (late 6.0);
0x1828 (6.1);
PVOID IpiFrame;
5.1 and higher previously at 0x06AC
0x07AC (5.1 to early 5.2);
0x086C (late 5.2);
0x18AC (early 6.0);
0x192C (late 6.0);
0x182C (6.1);
UCHAR PrcbPad2 [0x34];
5.1 to early 6.0  
UCHAR PrcbPad3 [0x34];
late 6.0 and higher  

The name ReverseStall takes its name from the stall that is typically entered by the sender to wait for its request to be serviced by all the targets. The reverse is that the target stalls too. Having signalled that it is done with the packet, it stalls until someone changes whatever was in the sender’s ReverseStall.

Offset Definition Versions Remarks
0x0480 (4.0);
0x0680 (5.0);
0x07E0 (5.1 to early 5.2);
0x08A0 (late 5.2);
0x18E0 (early 6.0);
0x1960 (late 6.0);
0x1860 (6.1);
PVOID volatile CurrentPacket [3];
4.0 and higher cache-aligned in 5.1 and higher
0x048C (4.0);
0x068C (5.0);
0x07EC (5.1 to early 5.2);
0x08AC (late 5.2);
0x18EC (early 6.0);
0x196C (late 6.0);
0x186C (6.1);
KAFFINITY volatile TargetSet;
4.0 and higher  
0x0490 (4.0);
0x0690 (5.0);
0x07F0 (5.1 to early 5.2);
0x08B0 (late 5.2);
0x18F0 (early 6.0);
0x1970 (late 6.0);
0x1870 (6.1);
PKIPI_WORKER volatile WorkerRoutine;
4.0 and higher  
0x0494 (4.0);
0x0694 (5.0);
0x07F4 (5.1 to early 5.2);
0x08B4 (late 5.2);
0x18F0 (early 6.0);
0x1974 (late 6.0);
0x1874 (6.1);
ULONG volatile IpiFrozen;
4.0 and higher previously as UCHAR volatile at 0x02CC
0x0498 (4.0);
0x0698 (5.0);
0x07F8 (5.1 to early 5.2);
0x08B8 (late 5.2);
0x18F8 (early 6.0);
0x1978 (late 6.0);
0x1878 (6.1);
ULONG CachePad1 [2];
4.0 to 5.0  
UCHAR PrcbPad3 [0x28];
5.1 to early 6.0  
UCHAR PrcbPad4 [0x28];
late 6.0 and higher  

These additions for version 4.0 are the essence of what may at the time have been a big advance in sending packet-based requests between processors. Before version 4.0, a processor could send a packet to multiple targets but two processors cannot send packets concurrently, not even to separate targets. The new CurrentPacket, TargetSet and WorkerRoutine are in essence the packet: three arbitrary parameters; a bitmap of targets; a worker routine that’s to execute on each target (and be given the three parameters). The bottleneck in earlier versions is that these are kept in the kernel’s own data. Version 4.0 keeps them in the sender’s KPRCB. Each target knows who sent the packet because the sender records the address of its own KPRCB as the SignalDone member (in the next cache line) in each target’s KPRCB.

The type PKIPI_WORKER is is defined in one or more of the NTDDK.H, WDM.H and NTIFS.H from the Device Driver Kit (DDK) for Windows NT 4.0 up to and including the WDK for Windows, and then only ever again in the NTOSP.H from those early editions of the WDK for Windows 10. The type has a subtle change between versions 4.0 and 5.0. In all versions it is

    PKIPI_CONTEXT PacketContext, 
    PVOID Parameter1, 
    PVOID Parameter2, 
    PVOID Parameter3);

but the PKIPI_CONTEXT is a pointer to a ULONG in version 4.0 and then becomes a pointer to void. In version 3.51, this first argument is a pointer to a 20-byte structure which perhaps was named KIPI_CONTEXT. Microsoft’s names for its members are not known, though it would not surprise to find that some of them survive as (otherwise quirky) names of other new KPRCB members.

Offset Definition Versions Remarks
0x02C0 (3.51);
0x04A0 (4.0);
0x06A0 (5.0);
0x0820 (5.1 to early 5.2);
0x08E0 (late 5.2);
0x1920 (early 6.0);
0x19A0 (late 6.0);
0x18A0 (6.1);
BOOLEAN volatile IpiCommands [4];
3.51 only previously at 0x01F4
ULONG volatile RequestSummary;
4.0 and higher cache-aligned
0x02C4 (3.51);
0x04A4 (4.0);
0x06A4 (5.0);
0x0824 (5.1 to early 5.2);
0x08E4 (late 5.2);
0x1924 (early 6.0);
0x19A4 (late 6.0);
0x18A4 (6.1);
ULONG volatile IpiLastPacket;
3.51 only previously at 0x0200
KPRCB volatile *SignalDone;
4.0 to 6.3  
LONG volatile TargetCount;
10.0 and higher  
0x02C8 (3.51);
0x04A8 (4.0);
0x06A8 (5.0)
ULONG volatile ReverseStall;
3.51 to 5.0 previously at 0x01FC;
next at 0x07A4
0x02CC (3.51)
UCHAR volatile IpiFrozen;
3.51 only previously at 0x01F8;
next as ULONG volatile at 0x0494
0x04AC (4.0);
0x06AC (5.0)
PVOID IpiFrame;
4.0 to 5.0 next at 0x07A8

The RequestSummary starts with the old implementation as an array of bytes that record which of the four possible types of packet-less request are being sent to the processor. Version 4.0 changed to bit flags, perhaps anticipating new types of requests, one of which was added for version 5.0. Microsoft’s assembly-language names for these bit flags have long been public: see, for instance, IPI_APC, in the KS386.INC from the DDK for Windows Server 2003 SP1.

Offset Definition Versions Remarks
ULONGLONG LastNonHrTimerExpiration;
1607 to 1903  
ULONG PrcbPad94 [2];
2004 and higher  
ULONGLONG TrappedSecurityDomain;
1803 and higher  
union {
    USHORT BpbState;
    struct {
        USHORT BpbIbrsPresent : 1;
        USHORT BpbStibpPresent : 1;
        USHORT BpbSmepPresent : 1;
        USHORT BpbSimulateSpecCtrl : 1;
        USHORT BpbSimulateIbpb : 1;
        USHORT BpbIbpbPresent : 1;
        USHORT BpbCpuIdle : 1;
        USHORT BpbClearSpecCtrlOnIdle : 1;
        USHORT BpbHTDisabled : 1;
        USHORT BpbUserToUserOnly : 1;
        USHORT BpbReserved : 6;
1803 only  
union {
    UCHAR BpbState;
    struct {
        UCHAR BpbCpuIdle : 1;
        UCHAR BpbFlushRsbOnTrap : 1;
        UCHAR BpbIbpbOnReturn : 1;
        UCHAR BpbIbpbOnTrap : 1;
        UCHAR BpbReserved : 4;
1809 and higher  
union {
    UCHAR BpbFeatures;
    struct {
        UCHAR BpbClearOnIdle : 1;
        UCHAR BpbEnabled : 1;
        UCHAR BpbSmep : 1;
        UCHAR BpbFeaturesReserved : 5;
1809 and higher  
UCHAR BpbSpecCtrlValue;
1803 only  
UCHAR BpbCurrentSpecCtrl;
1809 and higher  
UCHAR BpbCtxSwapSetValue;
1803 only  
UCHAR BpbKernelSpecCtrl;
1809 and higher  
UCHAR BpbNmiSpecCtrl;
1809 and higher  
UCHAR BpbUserSpecCtrl;
1809 and higher  
UCHAR PrcbPad49 [2];
1809 and higher  
ULONG ProcessorSignature;
1809 and higher  
ULONG ProcessorFlags;
1809 and higher  
0x02D0 (3.51);
0x04B0 (4.0);
0x06B0 (5.0);
0x0828 (5.1 to early 5.2);
0x08E8 (late 5.2);
0x1928 (early 6.0);
0x19A8 (late 6.0);
0x18A8 (6.1);
0x21A8 (6.2 to 1511);
0x21B0 (1607 to 1709);
0x21BC (1803);
ULONG CachePad2 [4];
3.51 to 5.0  
UCHAR PrcbPad4 [0x38];
5.1 to early 6.0  
UCHAR PrcbPad5 [0x38];
late 6.0 only  
UCHAR PrcbPad50 [0x38];
6.1 only  
UCHAR PrcbPad50 [0x30];
6.2 only  
UCHAR PrcbPad50 [0x28];
6.3 to 1511  
UCHAR PrcbPad50 [0x20];
1607 to 1709  
UCHAR PrcbPad50 [0x14];
1803 only  
UCHAR PrcbPad50 [8];
1809 and higher  

Despite being named CachePad2 initially, the preceding padding does not actually cache-align what follows in versions 4.0 and 5.0. It doesn’t in version 6.2 and higher, but deliberately. These versions instead squeeze two or four members into the end of the cache line:

Offset Definition Versions
0x21D8 (6.2);
ULONG InterruptLastCount;
6.2 and higher
0x21DC (6.2);
ULONG InterruptRate;
6.2 and higher
ULONG DeviceInterrupts;
6.3 and higher
PVOID IsrDpcStats;
6.3 and higher

What IsrDpcStats points to, when it does hold a pointer, is an ISRDPCSTATS structure.

Deferred Procedure Calls

A region of members concerned with DPC management was rearranged so much for version 5.2, with its introduction of Threaded DPCs, that it seems better to separate the layouts. Even before then, there were substantial introductions for version 3.51 and then rearrangements for version 5.1.

Offset Definition Versions Remarks
0x02E0 (3.51);
0x04C0 (4.0);
0x06C0 (5.0)
ULONG volatile DpcInterruptRequested;
3.51 to 5.0 next at 0x0878
0x02E4 (3.51);
0x04C4 (4.0);
0x06C4 (5.0)
PVOID ChainedInterruptList;
5.0 only  
0x02E4 (3.51);
0x04C4 (4.0);
0x06C8 (5.0)
ULONG CachePad3 [3];
3.51 to 4.0  
ULONG CachePad3 [2];
5.0 only  
0x02F0 (3.51);
0x04D0 (4.0);
0x06D0 (5.0)
ULONG MaximumDpcQueueDepth;
3.51 to 5.0 next at 0x0884
0x02F4 (3.51);
0x04D4 (4.0);
0x06D4 (5.0)
ULONG MinimumDpcRate;
3.51 to 5.0 next at 0x0888
0x02F8 (3.51);
0x04D8 (4.0);
0x06D8 (5.0)
ULONG CachePad4 [2];
3.51 to 5.0  

There is some suggestion that the members from DpcListHead to DpcLock, below, were once modelled as a sub-structure. For instance, code for KiDispatchInterrupt in version 3.50 accesses the lock as an offset from the list head, and this seems unlikely to be just an optimisation by the contemporaneous compiler. The space between these members is here thought to have been the original KernelReserved2, as if planned for development as the DPC functionality got more sophisticated.

Offset Definition Versions Remarks
0x02E8 (3.50);
0x0300 (3.51);
0x04E0 (4.0);
0x06E0 (5.0);
0x0860 (5.1)
3.50 to 5.1  
0x0868 (5.1)
PVOID DpcStack;
5.1 only previously at 0x06FC;
next at 0x0888
0x086C (5.1)
ULONG DpcCount;
5.1 only previously at 0x06F0
0x02F0 (3.50);
0x0308 (3.51);
0x04E8 (4.0);
0x06E8 (5.0);
0x0870 (5.1)
ULONG DpcQueueDepth;
3.50 to 5.0  
ULONG volatile DpcQueueDepth;
5.1 only  

0x030C (3.51);
0x04EC (4.0);
0x06EC (5.0);
0x0874 (5.1)
ULONG DpcRoutineActive;
3.51 to 5.0 previously BOOLEAN at 0xD8 in KPCR
ULONG volatile DpcRoutineActive;
5.1 only next as BOOLEAN volatile at 0x089A
0x0878 (5.1)
ULONG volatile DpcInterruptRequested;
5.1 only previously at 0x06C0;
next at BOOLEAN volatile at 0x0898
0x0310 (3.51);
0x04F0 (4.0);
0x06F0 (5.0)
ULONG DpcCount;
3.51 to 5.0 next at 0x086C
0x0314 (3.51);
0x04F4 (4.0);
0x06F4 (5.0);
0x087C (5.1)
ULONG DpcLastCount;
3.51 to 5.1 next at 0x08A0
0x0318 (3.51);
0x04F8 (4.0);
0x06F8 (5.0);
0x0880 (5.1)
ULONG DpcRequestRate;
3.51 to 5.1 next at 0x0890
0x0884 (5.1)
ULONG MaximumDpcQueueDepth;
5.1 only previously at 0x06D0;
next at 0x088C
0x0888 (5.1)
ULONG MinimumDpcRate;
5.1 only previously at 0x06D4;
next at 0x0894
0x06FC (5.0)
PVOID DpcStack;
5.0 only next at 0x0868
0x088C (5.1)
ULONG QuantumEnd;
5.1 only previously at 0x0750;
next as BOOLEAN volatile at 0x08C1
0x02F4 (3.50);
0x031C (3.51);
0x04FC (4.0);
0x0700 (5.0);
0x0890 (5.1)
ULONG KernelReserved2 [0x0F];
3.50 only  
ULONG KernelReserved2 [0x0B];
3.51 to 4.0  
ULONG KernelReserved2 [0x0A];
5.0 only  
UCHAR PrcbPad5 [0x10];
5.1 only  
0x0330 (3.50);
0x0348 (3.51);
0x0528 (4.0);
0x0728 (5.0);
0x08A0 (5.1)
3.50 to 5.1  
0x08A4 (5.1)
UCHAR PrcbPad6 [0x3C];
early 5.1 only  
UCHAR PrcbPad6 [0x1C];
late 5.1 only  

When DpcRoutineActive was moved here from the KPCR it didn’t become a ULONG just to formalise that the kernel sometimes accessed it as 32-bit boolean. The implementation changed such that DpcRoutineActive holds a pointer into the stack of the kernel routine that holds the DpcLock and is retiring the DPCs that it removes ffrom the DpcListHead. That the exported function KeIsExecutingDpc continued to return all 32 bits may mean it was never intended to return a BOOLEAN specifically. There’s no documention even now or a contemporaneous declaration to go by. Yet when DpcRoutineActive was reimplemented as a BOOLEAN for version 5.2, the code was changed to return a zero-extended byte.

DPC Management in Windows Server 2003 and Higher

Version 5.2 introduced Threaded DPCs. These reproduce some of the control data that support normal DPCs, and thus DpcListHead, DpcLock, DpcQueueDepth and DpcCount were gathered into a structure. Each processor has two of these KDPC_DATA structures: the first for normal DPCs; the second for Threaded DPCs.

Offset Definition Versions Remarks
0x0860 (early 5.2);
0x0920 (late 5.2);
0x1960 (early 6.0);
0x19E0 (late 6.0);
0x18E0 (6.1);
KDPC_DATA DpcData [2];
5.2 and higher cache-aligned
0x0888 (early 5.2);
0x0948 (late 5.2);
0x1988 (early 6.0);
0x1A08 (late 6.0);
0x1908 (6.1);
0x2208 (6.2);
PVOID DpcStack;
5.2 and higher previously at 0x0868
0x088C (early 5.2);
0x094C (late 5.2);
0x198C (early 6.0);
0x1A0C (late 6.0);
0x190C (6.1);
0x220C (6.2);
ULONG MaximumDpcQueueDepth;
5.2 only previously at 0x0884
LONG MaximumDpcQueueDepth;
6.0 and higher  
0x0890 (early 5.2);
0x0950 (late 5.2);
0x1990 (early 6.0);
0x1A10 (late 6.0);
0x1910 (6.1);
0x2210 (6.2);
ULONG DpcRequestRate;
5.2 and higher previously at 0x0880
0x0894 (early 5.2);
0x0954 (late 5.2);
0x1994 (early 6.0);
0x1A14 (late 6.0);
0x1914 (6.1);
0x2214 (6.2);
ULONG MinimumDpcRate;
5.2 and higher previously at 0x0888
0x0898 (early 5.2);
0x0958 (late 5.2);
0x1998 (early 6.0);
0x1A18 (late 6.0)
BOOLEAN volatile DpcInterruptRequested;
5.2 to 6.0 previously ULONG volatile at 0x0878
0x0899 (early 5.2);
0x0959 (late 5.2);
0x1999 (early 6.0);
0x1A19 (late 6.0)
BOOLEAN volatile DpcThreadRequested;
5.2 to 6.0  
0x089A (early 5.2);
0x095A (late 5.2);
0x199A (early 6.0);
0x1A1A (late 6.0)
BOOLEAN volatile DpcRoutineActive;
5.2 to 6.0 previously ULONG volatile at 0x0874;
next at 0x1932
0x089B (early 5.2);
0x095B (late 5.2);
0x199B (early 6.0);
0x1A1B (late 6.0)
BOOLEAN volatile DpcThreadActive;
5.2 to 6.0 next as bit at 0x1934

The DpcInterruptRequested member is notable for some confusion in Windows Vista SP1. This build added one instruction at very nearly the start of the KiDispatchInterrupt function just to clear this member. This function addresses KPRCB members via a pointer to the current processor’s KPCR, relying on the KPRCB to be embedded in the KPCR as the PrcbData member (at offset 0x0120). For Windows Vista SP1, however, the function addresses DpcInterruptRequested relative to the KPRCB not the KPCR. Instead of clearing the intended byte, it clears a byte 0x0120 bytes lower in memory. Put aside any ill effect from not clearing the intended byte. Look instead at what byte gets cleared by mistake. Fortunately, this byte is in the Tag in the PPPagedLookasideList for paged pool allocations of 0x0100 bytes. This means the corruption is harmless but it also gives the curiosity of being observable from user mode (in the output from the SystemLookasideInformation case of NtQuerySystemInformation). The error in addressing was corrected as early as Windows Vista SP2 and it was soon made irrelevant by a reworking of DPC management for Windows 7.

As noted earlier, version 5.2 made DpcRoutineActive into a BOOLEAN and recoded the undocumented KeIsExecutingDpc function to return just the zero-extended BOOLEAN. Version 6.0 computes its answer as TRUE or FALSE not from DpcRoutineActive alone but from it and DpcThreadActive taken together as one 16-bit word. Its answer for whether its caller is in turn called from someone’s handling of a DPC is therefore yes if either byte is non-zero.

Offset Definition Versions Remarks
0x089C (early 5.2);
0x095C (late 5.2);
0x199C (early 6.0);
0x1A1C (late 6.0)
ULONG PrcbLock;
5.2 to 6.0 next at 0x191C
0x08A0 (early 5.2);
0x0960 (late 5.2);
0x19A0 (early 6.0);
0x1A20 (late 6.0);
0x1918 (6.1);
0x2218 (6.2);
ULONG DpcLastCount;
5.2 and higher previously at 0x087C
0x191C (6.1);
0x221C (6.2);
ULONG PrcbLock;
6.1 and higher previously at 0x1A1C
0x1920 (6.1);
0x2220 (6.2);
KGATE DpcGate;
6.1 and higher  
0x08A4 (early 5.2);
0x0964 (late 5.2);
0x19A4 (early 6.0);
0x1A24 (late 6.0)
ULONG volatile TimerHand;
5.2 to 6.0 next at 0x1938 (6.1)
0x08A8 (early 5.2);
0x0968 (late 5.2);
0x19A8 (early 6.0);
0x1A28 (late 6.0)
ULONG volatile TimerRequest;
5.2 to 6.0  
0x19AC (early 6.0);
0x1A2C (late 6.0)
PVOID PrcbPad41;
6.0 only  
0x08AC (early 5.2);
0x096C (late 5.2);
PVOID DpcThread;
5.2 only  
0x08B0 (early 5.2);
0x0970 (late 5.2);
0x19B0 (early 6.0);
0x1A30 (late 6.0)
KEVENT DpcEvent;
5.2 to 6.0  
0x08C0 (early 5.2);
0x0980 (late 5.2);
0x19C0 (early 6.0);
0x1A40 (late 6.0);
0x1930 (6.1);
0x2230 (6.2);
0x2238 (6.3)
BOOLEAN ThreadDpcEnable;
5.2 to 6.3 next at 0x2259
UCHAR IdleState;
10.0 and higher  
0x08C1 (early 5.2);
0x0981 (late 5.2);
0x19C1 (early 6.0);
0x1A41 (late 6.0);
0x1931 (6.1);
0x2231 (6.2);
BOOLEAN volatile QuantumEnd;
5.2 and higher previously ULONG at 0x088C
0x08C2 (early 5.2);
0x0982 (late 5.2);
0x19C2 (early 6.0);
0x1A42 (late 6.0)
UCHAR PrcbPad50;
5.2 to 6.0  
0x1932 (6.1);
0x2232 (6.2);
BOOLEAN volatile DpcRoutineActive;
6.1 and higher previously at 0x1A1A
0x08C3 (early 5.2);
0x0983 (late 5.2);
0x19C3 (early 6.0);
0x1A43 (late 6.0);
0x1933 (6.1);
0x2233 (6.2);
BOOLEAN volatile IdleSchedule;
5.2 and higher  
0x08C4 (early 5.2);
0x0984 (late 5.2);
0x19C4 (early 6.0);
0x1A44 (late 6.0);
0x1934 (6.1);
0x2234 (6.2);
LONG DpcSetEventRequest;
5.2 to 6.0  
union {
    LONG volatile DpcRequestSummary;
    SHORT DpcRequestSlot [2];
    /*  changing members, follow link  */
6.1 and higher  


Offset Definition Versions Remarks
0x19C8 (early 6.0);
0x1A48 (late 6.0);
0x1938 (6.1);
0x2238 (6.2);
0x2240 (6.3 to 1903)
LONG Sleeping;
6.0 only previously at 0x055C
ULONG volatile TimerHand;
6.1 only previously at 0x1A24
ULONG LastTimerHand;
6.2 to 1903  
0x193C (6.1);
0x223C (6.2);
0x2244 (6.3 to 1903);
ULONG LastTick;
6.1 and higher  
LONG MasterOffset;
6.1 only  
ULONG PrcbPad41 [2];
6.1 only  
0x19CC (early 6.0);
0x1A4C (late 6.0);
0x194C (6.1);
0x2240 (6.2);
0x2248 (6.3 to 1903);
ULONG PeriodicCount;
6.0 and higher  
0x19D0 (early 6.0);
0x1A50 (late 6.0);
0x1950 (6.1);
0x2244 (6.2);
0x224C (6.3 to 1903);
ULONG PeriodicBias;
6.0 and higher  
0x08C8 (early 5.2);
0x0988 (late 5.2);
0x19D4 (early 6.0);
0x1A54 (late 6.0);
0x1954 (6.1)
UCHAR PrcbPad5 [0x16];
early 5.2 only  
UCHAR PrcbPad5 [0x12];
late 5.2 only  
UCHAR PrcbPad5 [6];
early 6.0 only  
UCHAR PrcbPad51 [6];
late 6.0 to 6.1  
0x099C (late 5.2);
0x19DC (early 6.0);
0x1A5C (late 6.0);
0x1958 (6.1);
0x2248 (6.2);
0x2250 (6.3 to 1903);
LONG TickOffset;
late 5.2 to 6.0  
6.1 only  
ULONG ClockInterrupts;
6.2 and higher  
0x224C (6.2);
0x2254 (6.3 to 1903);
ULONG ReadyScanTick;
6.2 and higher  
0x2250 (6.2)
UCHAR BalanceState;
6.2 only  
0x2251 (6.2);
0x2258 (6.3 to 1903);
BOOLEAN GroupSchedulingOverQuota;
6.2 and higher  
0x2259 (10.0 to 1903);
UCHAR ThreadDpcEnable;
10.0 and higher previously at 0x2238
0x2252 (6.2);
0x2259 (6.3);
0x225A (10.0 to 1903);
UCHAR PrcbPad41 [10];
6.2 only  
UCHAR PrcbPad41 [3];
6.3 only  
UCHAR PrcbPad41 [2];
10.0 to 1903  
UCHAR PrcbPad41 [6];
2004 and higher  


Offset Definition Versions Remarks
0x1960 (6.1);
6.1 and higher cache-aligned
ULONG PrcbPad92 [12];
2004 and higher  
0x08C0 (late 5.1);
0x08E0 (early 5.2);
0x09A0 (late 5.2);
0x19E0 (early 6.0);
0x1A60 (late 6.0);
0x31A0 (6.1);
0x3AA0 (6.2 to 1903);
KDPC CallDpc;
late 5.1 and higher cache-aligned
0x1A00 (early 6.0);
0x1A80 (late 6.0);
0x31C0 (6.1);
0x3AC0 (6.2 to 1903);
LONG ClockKeepAlive;
6.0 and higher  
0x1A04 (early 6.0);
0x1A84 (late 6.0);
0x31C4 (6.1)
UCHAR ClockCheckSlot;
6.0 to 6.1  
0x1A05 (early 6.0);
0x1A85 (late 6.0);
0x31C5 (6.1)
UCHAR ClockPollCycle;
6.0 to 6.1  
0x1A06 (early 6.0);
0x1A86 (late 6.0);
0x31C6 (6.1);
0x3AC4 (6.2 to 1903);
UCHAR PrcbPad6 [2];
6.0 to 6.1  
UCHAR PrcbPad6 [4];
6.2 and higher  
0x1A08 (early 6.0);
0x1A88 (late 6.0);
0x31C8 (6.1);
0x3AC8 (6.2 to 1903);
LONG DpcWatchdogPeriod;
6.0 and higher  
0x1A0C (early 6.0);
0x1A8C (late 6.0);
0x31CC (6.1);
0x3ACC (6.2 to 1903);
LONG DpcWatchdogCount;
6.0 and higher  
0x1A10 (early 6.0);
0x1A90 (late 6.0);
0x31D0 (6.1)
LONG ThreadWatchdogPeriod;
6.0 to 6.1  
0x1A14 (early 6.0);
0x1A94 (late 6.0);
0x31D4 (6.1)
LONG ThreadWatchdogCount;
6.0 to 6.1  
0x31D8 (6.1);
0x3AD0 (6.2 to 1903);
LONG volatile KeSpinLockOrdering;
6.1 and higher  
0x3AD4 (1703 to 1903);
ULONG DpcWatchdogProfileCumulativeDpcThreshold;
1703 and higher  
0x0900 (early 5.2);
0x09C0 (late 5.2);
0x1A18 (early 6.0);
0x1A98 (late 6.0);
0x31DC (6.1);
0x3AD4 (6.1 to 1607)
ULONG PrcbPad7 [8];
5.2 only  
ULONG PrcbPad70 [2];
6.0 only  
ULONG PrcbPad70 [1];
6.1 to 1607  


Offset Definition Versions Remarks
0x3AD8 (6.2 to 1903);
ULONG QueueIndex;
6.2 and higher previously at 0x31F0
0x3ADC (6.2 to 1903);
SINGLE_LIST_ENTRY DeferredReadyListHead;
6.2 and higher previously at 0x31F4
0x0920 (early 5.2);
0x09E0 (late 5.2);
0x1A20 (early 6.0);
0x1AA0 (late 6.0);
0x31E0 (6.1);
0x3AE0 (6.2)
LIST_ENTRY WaitListHead;
5.2 to 6.2 cache-aligned;
next at 0x3AEC
0x3AE0 (6.3 to 1903);
ULONG ReadySummary;
6.3 and higher previously at 3AEC
0x3AE4 (6.3 to 1903);
LONG AffinitizedSelectionMask;
6.3 and higher  
0x1A28 (early 6.0);
0x1AA8 (late 6.0);
0x31E8 (6.1);
0x3AE8 (6.2 to 1903);
6.0 and higher  
0x0928 (early 5.2);
0x09E8 (late 5.2);
0x1A2C (early 6.0);
0x1AAC (late 6.0);
0x31EC (6.1);
0x3AEC (6.2)
ULONG ReadySummary;
5.2 to 6.2 next at 3AE0
0x3AEC (6.3 to 1903);
LIST_ENTRY WaitListHead;
6.3 and higher previously at 0x3AE0
0x092C (early 5.2);
0x09EC (late 5.2);
0x1A30 (early 6.0);
0x1AB0 (late 6.0);
0x31F0 (6.1);
0x3AF0 (6.2)
ULONG SelectNextLast;
early 5.2 only  
ULONG QueueIndex;
late 5.2 to 6.1 next at 0x3AD8
ULONG ReadyQueueWeight;
6.2 only  
0x0930 (early 5.2);
0x09F0 (late 5.2)
LIST_ENTRY DispatcherReadyListHead [0x20];
5.2 only next at 0x1A60
0x0A30 (early 5.2);
0x0AF0 (late 5.2);
0x1A34 (early 6.0);
0x1AB4 (late 6.0);
0x31F4 (6.1);
0x3AF4 (6.2 to 1903);
SINGLE_LIST_ENTRY DeferredReadyListHead;
5.2 to 6.1 next at 0x3ADC
KPRCB *BuddyPrcb;
6.2 only  
ULONG ScbOffset;
6.3 and higher previously at 0x3B14
0x3AF8 (1703 to 1903);
ULONG ReadyThreadCount;
1703 and higher  
0x1A38 (early 6.0);
0x1AB8 (late 6.0);
0x31F8 (6.1);
0x3AF8 (6.2 to 1607);
0x3B00 (1703 to 1903);
ULONGLONG StartCycles;
6.0 and higher  
0x3B00 (10.0 to 1607);
0x3B08 (1703 to 1903);
ULONGLONG TaggedCyclesStart;
10.0 and higher  
0x3B08 (10.0 to 1607);
0x3B10 (1703 to 1903);
ULONGLONG TaggedCycles [2];
10.0 to 1903  
ULONGLONG TaggedCycles [3];
2004 and higher  
0x3B00 (6.2 to 6.3);
0x3B18 (10.0 to 1607);
0x3B20 (1703 to 1903)
ULONGLONG GenerationTarget;
6.2 to 1903  
0x1A40 (early 6.0);
0x1AC0 (late 6.0);
0x3200 (6.1);
0x3B08 (6.2 to 6.3);
0x3B20 (10.0 to 1607);
0x3B28 (1703 to 1903);
6.0 only  
ULONGLONG volatile CycleTime;
6.1 and higher  
0x3208 (6.1);
0x3B10 (6.2)
ULONG volatile HighCycleTime;
6.1 to 6.2 next at 0x3B30
0x3B14 (6.2)
ULONG ScbOffset;
6.2 only next at 0x3AF4
0x3B10 (6.3);
0x3B28 (10.0 to 1607);
0x3B30 (1703 to 1903);
ULONGLONG AffinitizedCycles;
6.3 and higher previously at 0x3B18
0x3B38 (1703 to 1903);
ULONGLONG ImportantCycles;
1703 and higher  
0x3B40 (1703 to 1903);
ULONGLONG UnimportantCycles;
1703 and higher  
0x3B48 (1703 to 1903);
ULONGLONG ReadyQueueExpectedRunTime;
1703 and higher  
0x3B18 (6.2 to 6.3);
0x3B30 (10.0 to 1607);
0x3B50 (1703 to 1903);
ULONGLONG AffinitizedCycles;
6.2 only next at 0x3B10
ULONG volatile HighCycleTime;
6.3 and higher previously at 0x3B10
0x3B38 (10.0 to 1607);
0x3B58 (1703 to 1903);
ULONGLONG Cycles [4][2];
10.0 and higher  
0x0A34 (early 5.2);
0x0AF4 (late 5.2);
0x1A48 (early 6.0);
0x1AC8 (late 6.0);
0x320C (6.1)
ULONG PrcbPad72 [11];
5.2 only  
ULONGLONG PrcbPad71 [3];
6.0 only  
ULONG PrcbPad71;
ULONGLONG PrcbPad72 [2];
6.1 only  
0x3B1C (6.3);
0x3B78 (10.0 to 1607);
0x3B98 (1703 to 1903);
ULONG PrcbPad71;
6.3 only  
ULONG PrcbPad71 [10];
10.0 to 1607  
ULONG PrcbPad71 [2];
1703 to 1903  
ULONG PrcbPad71;
2004 and higher  
ULONG DpcWatchdogSequenceNumber;
2004 and higher  


Offset Definition Versions Remarks
0x1A60 (early 6.0);
0x1AE0 (late 6.0);
0x3220 (6.1);
0x3B20 (6.2 to 6.3);
0x3BA0 (10.0 to 1903);
LIST_ENTRY DispatcherReadyListHead [0x20];
6.0 and higher previously at 0x09F0;
0x08E0 (5.1);
0x0A60 (early 5.2);
0x0B20 (late 6.2);
0x1B60 (early 6.0);
0x1BE0 (late 6.0);
0x3320 (6.1);
0x3C20 (6.2 to 6.3);
0x3CA0 (10.0 to 1903);
PVOID ChainedInterruptList;
5.1 and higher previously at 0x06C4
0x08E4 (5.1);
0x0A64 (early 5.2);
0x0B24 (late 6.2);
0x1B64 (early 6.0);
0x1BE4 (late 6.0);
0x3324 (6.1);
0x3C24 (6.2 to 6.3);
0x3CA4 (10.0 to 1903);
LONG LookasideIrpFloat;
5.1 and higher  
0x3C28 (6.2 to 6.3);
0x3CA8 (10.0 to 1903);
6.2 and higher  
0x3C30 (6.2 to 6.3);
0x3CB0 (10.0 to 1903);
6.2 and higher  
0x08E8 (5.1);
0x0A68 (early 5.2);
0x0B28 (late 5.2);
0x1B68 (early 6.0);
0x1BE8 (late 6.0);
0x3328 (6.1);
0x3C38 (6.2 to 6.3);
0x3CB8 (10.0 to 1903);
ULONG SpareFields0 [6];
5.1 only  
ULONG SpareFields0 [4];
early 5.2 only  
LONG volatile MmPageFaultCount;
late 5.2 and higher  
0x0B2C (late 5.2);
0x1B6C (early 6.0);
0x1BEC (late 6.0);
0x332C (6.1);
0x3C3C (6.2 to 6.3);
0x3CBC (10.0 to 1903);
LONG volatile MmCopyOnWriteCount;
late 5.2 and higher  
0x0B30 (late 5.2);
0x1B70 (early 6.0);
0x1BF0 (late 6.0);
0x3330 (6.1);
0x3C40 (6.2 to 6.3);
0x3CC0 (10.0 to 1903);
LONG volatile MmTransitionCount;
late 5.2 and higher  
0x0B34 (late 5.2);
0x1B74 (early 6.0);
0x1BF4 (late 6.0);
0x3334 (6.1);
0x3C44 (6.2 to 6.3);
0x3CC4 (10.0 to 1903);
LONG volatile MmCacheTransitionCount;
late 5.2 and higher  
0x0B38 (late 5.2);
0x1B78 (early 6.0);
0x1BF8 (late 6.0);
0x3338 (6.1);
0x3C48 (6.2 to 6.3);
0x3CC8 (10.0 to 1903);
LONG volatile MmDemandZeroCount;
late 5.2 and higher  
0x0B3C (late 5.2);
0x1B7C (early 6.0);
0x1BFC (late 6.0);
0x333C (6.1);
0x3C4C (6.2 to 6.3);
0x3CCC (10.0 to 1903);
LONG volatile MmPageReadCount;
late 5.2 and higher  
0x0B40 (late 5.2);
0x1B80 (early 6.0);
0x1C00 (late 6.0);
0x3340 (6.1);
0x3C50 (6.2 to 6.3);
0x3CD0 (10.0 to 1903);
LONG volatile MmPageReadIoCount;
late 5.2 and higher  
0x0B44 (late 5.2);
0x1B84 (early 6.0);
0x1C04 (late 6.0);
0x3344 (6.1);
0x3C54 (6.2 to 6.3);
0x3CD4 (10.0 to 1903);
LONG volatile MmCacheReadCount;
late 5.2 and higher  
0x0B48 (late 5.2);
0x1B88 (early 6.0);
0x1C08 (late 6.0);
0x3348 (6.1);
0x3C58 (6.2 to 6.3);
0x3CD8 (10.0 to 1903);
LONG volatile MmCacheIoCount;
late 5.2 and higher  
0x0B4C (late 5.2);
0x1B8C (early 6.0);
0x1C0C (late 6.0);
0x334C (6.1);
0x3C5C (6.2 to 6.3);
0x3CDC (10.0 to 1903);
LONG volatile MmDirtyPagesWriteCount;
late 5.2 and higher  
0x0B50 (late 5.2);
0x1B90 (early 6.0);
0x1C10 (late 6.0);
0x3350 (6.1);
0x3C60 (6.2 to 6.3);
0x3CE0 (10.0 to 1903);
LONG volatile MmDirtyWriteIoCount;
late 5.2 and higher  
0x0B54 (late 5.2);
0x1B94 (early 6.0);
0x1C14 (late 6.0);
0x3354 (6.1);
0x3C64 (6.2 to 6.3);
0x3CE4 (10.0 to 1903);
LONG volatile MmMappedPagesWriteCount;
late 5.2 and higher  
0x0B58 (late 5.2);
0x1B98 (early 6.0);
0x1C18 (late 6.0);
0x3358 (6.1);
0x3C68 (6.2 to 6.3);
0x3CE8 (10.0 to 1903);
LONG volatile MmMappedWriteIoCount;
late 5.2 and higher  
0x1B9C (early 6.0);
0x1C1C (late 6.0);
0x335C (6.1);
0x3C6C (6.2 to 6.3);
0x3CEC (10.0 to 1903);
ULONG volatile CachedCommit;
6.0 and higher  
0x1BA0 (early 6.0);
0x1C20 (late 6.0);
0x3360 (6.1);
0x3C70 (6.2 to 6.3);
0x3CF0 (10.0 to 1903);
ULONG volatile CachedResidentAvailable;
6.0 and higher  
0x1BA4 (early 6.0);
0x1C24 (late 6.0);
0x3364 (6.1);
0x3C74 (6.2 to 6.3);
0x3CF4 (10.0 to 1903);
PVOID HyperPte;
6.0 and higher  
0x0B5C (late 5.2)
ULONG SpareFields0 [1];
late 5.2 only  

The Old End

Offset Definition Versions Remarks
0x0334 (3.50);
0x034C (3.51);
0x052C (4.0);
0x072C (5.0)
3.50 to 5.0 next at 0x04C4
0x1BA8 (early 6.0);
0x1C28 (late 6.0)
UCHAR CpuVendor;
6.0 only next at 0x03C4
0x1BA9 (early 6.0);
0x1C29 (late 6.0);
0x3368 (6.1);
0x3C78 (6.2 to 6.3);
0x3CF8 (10.0 to 1903);
UCHAR PrcbPad9 [3];
early 6.0 only  
UCHAR PrcbPad8 [3];
late 6.0 only  
UCHAR PrcbPad8 [4];
6.1 and higher  
0x0335 (3.50);
0x034D (3.51);
0x052D (4.0);
0x072D (5.0);
0x0900 (5.1);
0x0A78 (early 5.2);
0x0B60 (late 5.2);
0x1BAC (early 6.0);
0x1C2C (late 6.0);
0x336C (6.1);
0x3C7C (6.2 to 6.3);
0x3CFC (10.0 to 1903);
UCHAR VendorString [0x0D];
3.50 and higher  
0x090D (5.1);
0x0A85 (early 5.2);
0x0B6D (late 5.2);
0x1BB9 (early 6.0);
0x1C39 (late 6.0);
0x3379 (6.1);
0x3C89 (6.2 to 6.3);
0x3D09 (10.0 to 1903);
UCHAR InitialApicId;
5.1 and higher  
0x1BBA (early 6.0)
UCHAR CoresPerPhysicalProcessor;
early 6.0 only next at 0x03C0
0x090E (5.1);
0x0A86 (early 5.2);
0x0B6E (late 5.2);
0x1BBB (early 6.0);
0x1C3A (late 6.0);
0x337A (6.1);
0x3C8A (6.2 to 6.3);
0x3D0A (10.0 to 1903);
UCHAR LogicalProcessorsPerPhysicalProcessor;
5.1 and higher  
0x1C3B (late 6.0);
0x337B (6.1);
0x3C8B (6.2 to 6.3);
0x3D0B (10.0 to 1903);
UCHAR PrcbPad9 [5];
late 6.0 and higher  
0x053C (4.0);
0x073C (5.0);
0x0910 (5.1);
0x0A88 (early 5.2);
0x0B70 (late 5.2);
0x1BBC (early 6.0)
4.0 to early 6.0 next at 0x03C4
0x0540 (4.0);
0x0740 (5.0);
0x0914 (5.1);
0x0A8C (early 5.2);
0x0B74 (late 5.2);
0x1BC0 (early 6.0);
0x1C40 (late 6.0);
0x3380 (6.1);
0x3C90 (6.2 to 6.3);
0x3D10 (10.0 to 1903);
ULONG FeatureBits;
4.0 to 6.3  
ULONGLONG FeatureBits;
10.0 and higher  
0x0548 (4.0);
0x0748 (5.0);
0x0918 (5.1);
0x0A90 (early 5.2);
0x0B78 (late 5.2);
0x1BC8 (early 6.0);
0x1C48 (late 6.0);
0x3388 (6.1);
0x3C98 (6.2 to 6.3);
0x3D18 (10.0 to 1903);
LARGE_INTEGER UpdateSignature;
4.0 and higher  
0x0344 (3.50);
0x035C (3.51);
0x0550 (4.0);
0x0750 (5.0)
ULONG QuantumEnd;
3.50 to 5.0 last member in 3.50;
last member in 3.51;
last member in 4.0;
next at 0x088C

The InitialApicId is the high byte of what’s returned in ebx for cpuid leaf 1.

Given a GenuineIntel processor whose CpuType, i.e., family, is at least 6, the UpdateSignature is all 64 bits that are read from the Model Specific Register 0x8B (IA32_BIOS_SIGN_ID), having first written zero to that register and then executed the cpuid instruction’s leaf 1. Starting with version 6.2, Windows also gets the UpdateSignature—by a straightforward read of the MSR—if the vendor is AuthenticAMD and the family is at least 15.

What QuantumEnd holds is an indicator of which thread was most recently seen to have ended its quantum while running on this processor. The indicator is an address of apparently no signficance from the thread’s stack at the time. Later versions eventually turn it into a BOOLEAN. The kernel’s KiDispatchInterrupt function checks for this indicator after retiring DPCs so that action to take at the end of the quantum is in effect a lowest-priority DPC. Version 3.10 actually did schedule this action as a DPC, with no control over the ordering.

Appended for Windows 2000

The significant development of power management for Windows 2000 brought to the KPRCB a large structure for which Microsoft provides a C-language definition in NTPOAPI.H. The PROCESSOR_POWER_STATE has since changed beyond recognition, yet the definition for Windows 2000 remains even in the NTPOAPI.H from the WDK for Windows 10. Throughout, a comment describes it as the “Power structure in each processor’s PRCB”.

The PowerState and NpxSaveArea anyway got swapped for version 5.1. The latter needs 16-byte alignment. This left version 5.2 with 8 bytes to use for the IsrTime. That it remains seems to have given later versions some work.

Offset Definition Versions Remarks
0x0758 (5.0)
5.0 only next at 0x0B30
0x0A98 (early 5.2);
0x0B80 (late 5.2);
0x1BD0 (early 6.0);
0x1C50 (late 6.0);
0x3390 (6.1);
0x3CA0 (6.2 to 6.3);
0x3D20 (10.0 to 1903);
ULONGLONG volatile IsrTime;
5.2 and higher  
0x0B88 (late 5.2);
0x1BD8 (early 6.0);
0x1C58 (late 6.0);
0x3398 (6.1);
0x3CA8 (6.2)
ULONGLONG SpareField1;
late 5.2 to 6.0  
ULONGLONG RuntimeAccumulation;
6.1 only  
ULONG Stride;
6.2 only  
0x3CAC (6.2);
0x3CA8 (6.3);
ULONG PrcbPad90;
6.2 only  
ULONG PrcbPad90 [2];
6.3 and higher  
ULONGLONG GenerationTarget;
2004 and higher  
0x07E0 (5.0);
0x0920 (5.1);
0x0AA0 (early 5.2);
0x0B90 (late 5.2);
0x1BE0 (early 6.0);
0x1C60 (late 6.0)
5.0 to 6.0 last member in 5.0

Appended for Windows XP

Offset Definition Versions Remarks
0x0B30 (5.1);
0x0CB0 (early 5.2);
0x0DA0 (late 5.2);
0x1DF0 (early 6.0);
0x1E70 (late 6.0);
0x33A0 (6.1);
0x3CB0 (6.2 to 6.3);
0x3D30 (10.0 to 1903);
5.1 and higher previously at 0x0758;
last member in 5.1;
last member in 5.2

The intention behind the following padding is not known.

Offset Definition Versions
0x3EB0 (1703);
0x3ED8 (1709 to 1903);
KDPC ForceIdleDpc;
1703 and higher
2004 and higher
0x3E30 (6.2);
0x3E40 (6.3);
0x3EB0 (10.0 to 1607);
0x3ED0 (1703);
0x3DF8 (1709 to 1903);
ULONG PrcbPad91 [1];
6.2 only
ULONG PrcbPad91 [11];
6.3 to 1607
ULONG PrcbPad91 [8];
1703 only
ULONG PrcbPad91 [14];
1709 to 1903
ULONG PrcbPad91 [6];
2004 and higher

Appended For Windows Vista

Offset Definition Versions Remarks
RTL_HASH_TABLE *DpcRuntimeHistoryHashTable;
2004 and higher  
KDPC *DpcRuntimeHistoryHashTableCleanupDpc;
2004 and higher  
ULONGLONG CurrentDpcRuntimeHistoryCached;
2004 and higher  
ULONGLONG CurrentDpcStartTime;
2004 and higher  
2004 and higher  
0x3EF0 (1703);
0x3F30 (1709 to 1903);
ULONG DpcWatchdogProfileSingleDpcThreshold;
1703 and higher  
0x1ED0 (early 6.0);
0x1F38 (late 6.0);
0x3468 (6.1);
0x3E34 (6.2);
0x3E74 (6.3);
0x3EF4 (10.0 to 1703);
0x3F34 (1709 to 1903);
KDPC DpcWatchdogDpc;
6.0 and higher  
0x1EF0 (early 6.0);
0x1F58 (late 6.0);
0x3488 (6.1);
0x3E58 (6.2);
0x3E98 (6.3);
0x3F18 (10.0 to 1703);
0x3F58 (1709 to 1903);
KTIMER DpcWatchdogTimer;
6.0 and higher  
0x1F18 (early 6.0);
0x1F80 (late 6.0);
0x34B0 (6.1)
PVOID WheaInfo;
6.0 to 6.1 next at 0x3F04
0x1F1C (early 6.0);
0x1F84 (late 6.0);
0x34B4 (6.1)
PVOID EtwSupport;
6.0 to 6.1 next at 0x3F08
0x1F20 (early 6.0);
0x1F88 (late 6.0);
0x34B8 (6.1)
SLIST_HEADER InterruptObjectPool;
6.0 to 6.1 next at 0x3F10
0x1F28 (early 6.0);
0x1F90 (late 6.0);
0x34C0 (6.1);
0x3E80 (6.2);
0x3EC0 (6.3);
0x3F40 (10.0 to 1703);
0x3F80 (1709 to 1903);
LARGE_INTEGER HypercallPagePhysical;
early 6.0 only  
SLIST_HEADER HypercallPageList;
late 6.0 and higher  
0x1F30 (early 6.0);
0x1F98 (late 6.0);
0x34C8 (6.1);
0x3E88 (6.2);
0x3EC8 (6.3);
0x3F48 (10.0 to 1703);
0x3F88 (1709 to 1903);
PVOID HypercallPageVirtual;
6.0 to 6.3  
PVOID HypercallCachedPages;
10.0 and higher  
0x1F9C (late 6.0);
0x34CC (6.1);
0x3E8C (6.2);
0x3ECC (6.3);
0x3F4C (10.0 to 1703);
0x3F8C (1709 to 1903);
PVOID VirtualApicAssist;
late 6.0 and higher  
0x1FA0 (late 6.0);
0x34D0 (6.1);
0x3E90 (6.2);
0x3ED0 (6.3);
0x3F50 (10.0 to 1703);
0x3F90 (1709 to 1903);
ULONGLONG *StatisticsPage;
late 6.0 and higher  
0x1F34 (early 6.0);
0x1FA4 (late 6.0);
0x34D4 (6.1)
PVOID RateControl;
6.0 to 6.1  
0x1F38 (early 6.0);
0x1FA8 (late 6.0);
0x34D8 (6.1);
0x3E94 (6.2);
0x3ED4 (6.3);
0x3F54 (10.0 to 1703);
0x3F94 (1709 to 1903);
6.0 and higher  
0x1F74 (early 6.0);
0x1FE4 (late 6.0);
0x3514 (6.1);
0x3ED0 (6.2);
0x3F10 (6.3);
0x3F90 (10.0 to 1703);
0x3FD0 (1709 to 1903);
ULONG CacheCount;
6.0 and higher  
0x1F78 (early 6.0);
0x1FE8 (late 6.0);
0x3518 (6.1)
ULONG CacheProcessorMask [5];
6.0 to 6.1 next at 0x3EE0
0x1F8C (early 6.0)
UCHAR LogicalProcessorsPerCore;
early 6.0 only next at 0x03C1
0x1F8D (early 6.0)
UCHAR PrcbPad8 [3];
early 6.0 only  
0x1F90 (early 6.0);
0x1FFC (late 6.0);
0x352C (6.1);
0x3ED4 (6.2);
0x3F14 (6.3);
0x3F94 (10.0 to 1703);
0x3FD4 (1709 to 1903);
KAFFINITY PackageProcessorSet;
6.0 only  
KAFFINITY_EX PackageProcessorSet;
6.1 and higher  
0x3EE0 (6.2);
0x3F20 (6.3);
0x3FA0 (10.0 to 1703);
0x3FE0 (1709 to 1903);
ULONG CacheProcessorMask [5];
6.2 only previously at 0x3518;
next at 0x3F34
ULONG SharedReadyQueueMask;
6.3 and higher  
0x3538 (6.1);
0x3EF4 (6.2);
0x3F24 (6.3);
0x3FA4 (10.0 to 1703);
0x3FE4 (1709 to 1903);
ULONG PrcbPad91 [1];
6.1 only  
ULONG ScanSiblingMask;
6.2 only next at 0x3F2C
6.3 and higher  
0x3FA8 (10.0 to 1703);
0x3FE8 (1709 to 1903);
ULONG SharedQueueScanOwner;
10.0 and higher  
0x1F94 (early 6.0);
0x2000 (late 6.0);
0x353C (6.1);
0x3EF8 (6.2);
0x3F28 (6.3);
0x3FAC (10.0 to 1703);
0x3FEC (1709 to 1903);
ULONG CoreProcessorSet;
6.0 and higher last member in 6.0

That the KPRCB has a KAFFINITY_EX among its members has a curious side-effect in the public symbols for the WOW64 variant of NTDLL.DLL. Leave aside the wonder that a symbol file for a user-mode NTDLL.DLL has type information for the kernel-mode KPRCB. (It is perhaps not intended to, but picks it up from inclusion of I386_X.H. This header both defines the KPRCB and uses it in an inline routine, which is enough to generate type information.) The side-effect for the WOW64 variant is that the type information is wrong because the KAFFINITY_EX is compiled with the greater allowance that 64-bit Windows has for processor groups (20 instead 1). For this symbol file, which the 32-bit debugger uses for NTDLL.DLL when debugging a 32-bit application, everything after PackageProcessorSet is said to be further into the KPRCB than it truly is.

Inserted For Windows 8

Offset Definition Versions Remarks
0x3F2C (6.3);
0x3FB0 (10.0 to 1703);
0x3FF0 (1709 to 1903);
ULONG ScanSiblingMask;
6.3 and higher previously at 0x3EF4
0x3F30 (6.3);
0x3FB4 (10.0 to 1703);
0x3FF4 (1709 to 1903);
6.3 and higher  
0x3F34 (6.3);
0x3FB8 (10.0 to 1703);
0x3FF8 (1709 to 1903);
ULONG CacheProcessorMask [5];
6.3 and higher previously at 0x3EE0
0x3EFC (6.2);
0x3F48 (6.3);
0x3FCC (10.0 to 1703);
0x400C (1709 to 1903);
ULONG ScanSiblingIndex;
6.2 and higher  
0x3F00 (6.2)
6.2 only  
0x3F04 (6.2);
0x3F4C (6.3);
0x3FD0 (10.0 to 1703);
0x4010 (1709 to 1903);
PVOID WheaInfo;
6.2 and higher previously at 0x3430
0x3F08 (6.2);
0x3F50 (6.3);
0x3FD4 (10.0 to 1703);
0x4014 (1709 to 1903);
PVOID EtwSupport;
6.2 and higher previously at 0x34B4
0x3F10 (6.2);
0x3F58 (6.3);
0x3FD8 (10.0 to 1703);
0x4018 (1709 to 1903);
SLIST_HEADER InterruptObjectPool;
6.2 and higher previously at 0x34B8
0x3F60 (6.3)
ULONG SharedReadyQueueOffset;
6.3 only  
0x3FE0 (1703);
0x4020 (1709 to 1903);
PVOID *DpcWatchdogProfile;
1703 and higher  
0x3FE4 (1703);
0x4024 (1709 to 1903);
PVOID *DpcWatchdogProfileCurrentEmptyCapture;
1703 and higher  
0x3F18 (6.2);
0x3F64 (6.3);
0x3FE0 (10.0 to 1607);
0x3FE8 (1703);
0x4028 (1709 to 1903);
ULONG PrcbPad92 [8];
6.2 only  
ULONG PrcbPad92 [2];
6.3 only  
ULONG PrcbPad92 [3];
10.0 to 1607  
ULONG PrcbPad92 [1];
1703 to 1709  
ULONG PackageId;
1903 and higher  
0x3F6C (6.3);
0x3FEC (10.0 to 1703);
0x402C (1709 to 1903);
ULONG PteBitCache;
6.3 and higher  
0x3F70 (6.3);
0x3FF0 (10.0 to 1703);
0x4030 (1709 to 1903);
ULONG PteBitOffset;
6.3 and higher  
0x3F74 (6.3);
0x3FF4 (10.0 to 1703);
0x4034 (1709 to 1903);
ULONG PrcbPad93;
6.3 and higher  
0x3F38 (6.2);
0x3F78 (6.3);
0x3FF8 (10.0 to 1703);
0x4038 (1709 to 1903);
6.2 and higher  
0x3F3C (6.2);
0x3F7C (6.3);
0x3FFC (10.0 to 1703);
0x403C (1709 to 1903);
PVOID ProfileEventIndexAddress;
6.2 and higher  

Appended For Windows 7

Offset Definition Versions
0x3540 (6.1);
0x3F40 (6.2);
0x3F80 (6.3);
0x4000 (10.0 to 1703);
0x4040 (1709 to 1903);
KDPC TimerExpirationDpc;
6.1 and higher

Windows 7 appends numerous performance counters for synchronisation, but they didn’t stay as separate members for long.

Offset Definition Versions
0x3560 (6.1)
ULONG SpinLockAcquireCount;
6.1 only
0x3564 (6.1)
ULONG SpinLockContentionCount;
6.1 only
0x3568 (6.1)
ULONG SpinLockSpinCount;
6.1 only
0x356C (6.1)
ULONG IpiSendRequestBroadcastCount;
6.1 only
0x3570 (6.1)
ULONG IpiSendRequestRoutineCount;
6.1 only
0x3574 (6.1)
ULONG IpiSendSoftwareInterruptCount;
6.1 only
0x3578 (6.1)
ULONG ExInitializeResourceCount;
6.1 only
0x357C (6.1)
ULONG ExReInitializeResourceCount;
6.1 only
0x3580 (6.1)
ULONG ExDeleteResourceCount;
6.1 only
0x3584 (6.1)
ULONG ExecutiveResourceAcquiresCount;
6.1 only
0x3588 (6.1)
ULONG ExecutiveResourceContentionsCount;
6.1 only
0x358C (6.1)
ULONG ExecutiveResourceReleaseExclusiveCount;
6.1 only
0x3590 (6.1)
ULONG ExecutiveResourceReleaseSharedCount;
6.1 only
0x3594 (6.1)
ULONG ExecutiveResourceConvertsCount;
6.1 only
0x3598 (6.1)
ULONG ExAcqResExclusiveAttempts;
6.1 only
0x359C (6.1)
ULONG ExAcqResExclusiveAcquiresExclusive;
6.1 only
0x35A0 (6.1)
ULONG ExAcqResExclusiveAcquiresExclusiveRecursive;
6.1 only
0x35A4 (6.1)
ULONG ExAcqResExclusiveWaits;
6.1 only
0x35A8 (6.1)
ULONG ExAcqResExclusiveNotAcquires;
6.1 only
0x35AC (6.1)
ULONG ExAcqResSharedAttempts;
6.1 only
0x35B0 (6.1)
ULONG ExAcqResSharedAcquiresExclusive;
6.1 only
0x35B4 (6.1)
ULONG ExAcqResSharedAcquiresShared;
6.1 only
0x35B8 (6.1)
ULONG ExAcqResSharedAcquiresSharedRecursive;
6.1 only
0x35BC (6.1)
ULONG ExAcqResSharedWaits;
6.1 only
0x35C0 (6.1)
ULONG ExAcqResSharedNotAcquires;
6.1 only
0x35C4 (6.1)
ULONG ExAcqResSharedStarveExclusiveAttempts;
6.1 only
0x35C8 (6.1)
ULONG ExAcqResSharedStarveExclusiveAcquiresExclusive;
6.1 only
0x35CC (6.1)
ULONG ExAcqResSharedStarveExclusiveAcquiresShared;
6.1 only
0x35D0 (6.1)
ULONG ExAcqResSharedStarveExclusiveAcquiresSharedRecursive;
6.1 only
0x35D4 (6.1)
ULONG ExAcqResSharedStarveExclusiveWaits;
6.1 only
0x35D8 (6.1)
ULONG ExAcqResSharedStarveExclusiveNotAcquires;
6.1 only
0x35DC (6.1)
ULONG ExAcqResSharedWaitForExclusiveAttempts;
6.1 only
0x35E0 (6.1)
ULONG ExAcqResSharedWaitForExclusiveAcquiresExclusive;
6.1 only
0x35E4 (6.1)
ULONG ExAcqResSharedWaitForExclusiveAcquiresShared;
6.1 only
0x35E8 (6.1)
ULONG ExAcqResSharedWaitForExclusiveAcquiresSharedRecursive;
6.1 only
0x35EC (6.1)
ULONG ExAcqResSharedWaitForExclusiveWaits;
6.1 only
0x35F0 (6.1)
ULONG ExAceResSharedWaitForExclusiveNotAcquires;
6.1 only
0x35F4 (6.1)
ULONG ExSetResOwnerPointerExclusive;
6.1 only
0x35F8 (6.1)
ULONG ExSetResOwnerPointerSharedNew;
6.1 only
0x35FC (6.1)
ULONG ExSetResOwnerPointerSharedOld;
6.1 only
0x3600 (6.1)
ULONG ExTryToAcqExclusiveAttempts;
6.1 only
0x3604 (6.1)
ULONG ExTryToAcqExclusiveAcquires;
6.1 only
0x3608 (6.1)
ULONG ExBoostExclusiveOwner;
6.1 only
0x360C (6.1)
ULONG ExBoostSharedOwners;
6.1 only
0x3610 (6.1)
ULONG ExEtwSynchTrackingNotificationsCount;
6.1 only
0x3614 (6.1)
ULONG ExEtwSynchTrackingNotificationsAccountedCount;
6.1 only

Windows 8 formalises the preceding as one structure and inserts another for file and disk I/O.

Offset Definition Versions
0x3F60 (6.2);
0x3FA0 (6.3);
0x4020 (10.0 to 1703);
0x4060 (1709 to 1903);
6.2 and higher
0x4018 (6.2);
0x4058 (6.3);
0x40D8 (10.0 to 1703);
0x4118 (1709 to 1903);
6.2 and higher


Offset Definition Versions Remarks
0x3618 (6.1);
0x4028 (6.2);
0x4068 (6.3);
0x40E8 (10.0 to 1703);
0x4128 (1709 to 1903);
CONTEXT *Context;
6.1 and higher  
0x361C (6.1);
0x402C (6.2);
0x406C (6.3);
0x40EC (10.0 to 1703);
0x412C (1709 to 1903);
ULONG ContextFlags;
6.1 only  
ULONG ContextFlagsInit;
6.2 and higher  
0x3620 (6.1);
0x4030 (6.2);
0x4070 (6.3);
0x40F0 (10.0 to 1703);
0x4130 (1709 to 1903);
XSAVE_AREA *ExtendedState;
6.1 and higher last member in 6.1

Appended For Windows 8

Offset Definition Versions Remarks
0x4034 (6.2);
0x4074 (6.3);
0x40F4 (10.0 to 1703);
0x4134 (1709 to 1903);
6.2 and higher last member in 6.2

Appended For Windows 8.1

Offset Definition Versions Remarks
0x419C (6.3);
0x421C (10.0 to 1703);
0x425C (1709 to 1903);
PVOID IsrStack;
6.3 and higher  
0x41A0 (6.3);
0x4220 (10.0 to 1703);
0x4260 (1709 to 1903);
KINTERRUPT *VectorToInterruptObject [0xD0];
6.3 and higher  
0x44E0 (6.3);
0x4560 (10.0 to 1703);
0x45A0 (1709 to 1903);
6.3 and higher  
0x44E4 (6.3);
0x4564 (10.0 to 1703);
0x45A4 (1709 to 1903);
SINGLE_LIST_ENTRY AbPropagateBoostsList;
6.3 and higher  
0x44E8 (6.3);
0x4568 (10.0 to 1703);
0x45A8 (1709 to 1903);
6.3 and higher last member in 6.3

The VectorToInterruptObject array is for interrupt vectors 0x30 to 0xFF inclusive. This is the range that all x86 versions allow for hardware interrupts.

Appended For Windows 10

The additions for Windows 10 pad three times, apparently for cache alignment in each case.

Offset Definition Versions
0x4588 (10.0 to 1703);
0x45C8 (1709 to 1903);
IOP_IRP_STACK_PROFILER IoIrpStackProfilerCurrent;
10.0 and higher
0x45DC (10.0 to 1703);
0x461C (1709 to 1903);
IOP_IRP_STACK_PROFILER IoIrpStackProfilerPrevious;
10.0 and higher
0x4630 (10.0 to 1703);
0x4670 (1709 to 1903);
KTIMER_EXPIRATION_TRACE TimerExpirationTrace [0x10];
10.0 and higher
0x4730 (10.0 to 1703);
0x4770 (1709 to 1903);
ULONG TimerExpirationTraceCount;
10.0 and higher
0x4734 (10.0 to 1703);
0x4774 (1709 to 1903);
PVOID ExSaPageArray;
10.0 and higher
0x4778 (1803 to 1903);
XSAVE_AREA_HEADER *ExtendedSupervisorState;
1803 and higher
0x4738 (10.0 to 1703);
0x4778 (1709);
0x477C (1803 to 1903);
ULONG PrcbPad100 [10];
10.0 to 1709
ULONG PrcbPad100 [9];
1803 and higher


Offset Definition Versions Remarks
0x4760 (10.0 to 1703);
0x47A0 (1709 to 1903);
KSHARED_READY_QUEUE LocalSharedReadyQueue;
10.0 and higher cache-aligned
0x4894 (10.0 to 1607)
UCHAR PrcbPad95 [12];
10.0 to 1607  

One might wonder, though, at having a cache line for just one pointer:

Offset Definition Versions Remarks
0x48A0 (10.0 to 1703);
0x48E0 (1709 to 1903);
10.0 and higher cache-aligned
0x48A4 (10.0 to 1703);
0x48E4 (1709 to 1903);
UCHAR PrcbPad [0x3C];
10.0 to 1709  
UCHAR PrcbPad [0x05FC];
1803 and higher  

The preceding padding is greatly expanded for Version 1803 so that additions are not just cache-aligned but page-aligned.

Offset Definition Versions Remarks
ULONG KernelDirectoryTableBase;
1803 and higher page-aligned
ULONG EspBaseShadow;
1803 and higher  
ULONG UserEspShadow;
1803 and higher  
ULONG ShadowFlags;
1803 and higher  
1803 and higher  
1803 and higher  
1803 and higher  
PVOID EspIretd;
1803 and higher  
ULONG RestoreSegOption;
1803 and higher  
ULONG SavedEsi;
1803 and higher  
USHORT VerwSelector;
2004 and higher  
USHORT PrcbShadowPad;
2004 and higher  
ULONG TaskSwitchCount;
2004 and higher  
0x4F08 (1803 to 1903);
ULONG DbgLogs [0x0200];
1803 and higher  
0x5708 (1803 to 1903);
ULONG DbgCount;
1803 and higher  
0x570C (1803 to 1903);
ULONG PrcbPadRemainingPage [0x01F5];
1803 to 1903  
ULONG PrcbPadRemainingPage [0x01F3];
2004 and higher  

The RequestMailbox array is at the very end so that the kernel can easily provide as many mailboxes as there can ever be processors.

Offset Definition Versions Remarks
0x48E0 (10.0 to 1703);
0x4920 (1709);
10.0 and higher cache-aligned;
page-aligned in 1803 and higher;
last member in 10.0 and higher