KPRCB (i386)

The name KPRCB stands for (Kernel) Processor Control Block. The kernel keeps one KPRCB (formally _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 (amd64) is presented separately.

Access

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:

FORCEINLINE
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 apparently written in assembly language. The .DBG symbol files for Windows NT 4.0 not only name i386pcr.asm as the source file but even tell us that the routine’s two instructions are at lines 61 and 64.

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. See dword ptr fs:[20h] in kernel-mode code for any 32-bit Windows version and you know that what’s sought is the currently executing processor’s KPRCB.

The KPRCB that Prcb points to is the PrcbData member. Its offset within the KPCR also 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. This offset too is stable: see dword ptr fs:[0124h] in kernel-mode code for any 32-bit Windows version and you know that what’s sought is the current thread.

Whether such access to KPRCB members from fs 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 (to offset 0x1A18) for the DpcInterruptRequested member in Windows Vista SP1, specifically. 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

Problems of incorrect offset computation aside, acessing a KPRCB member through the fs register and an offset from the containing KPCR generally is better. 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.

To sense the danger, look again at KeGetCurrentThread and imagine it as coded in two steps:

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

This is unsafe for general use. If you don’t already see why, then kernel-mode programming is not yet an accomplishment—and if you don’t see why by the end of this paragraph, then please leave kernel-mode programming alone for a while. Suppose this bad KeGetCurrentThread is called 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 that 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 a header named i386_x.h. Some other 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 the twice-published NTOSP.H but also in the never-published NTHAL.H.

Layout

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
0x0558
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 (SDK) 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 of this structure’s development even through 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
0x00
USHORT MinorVersion;
all  
0x02
USHORT MajorVersion;
all  
0x04
KTHREAD *CurrentThread;
all  
0x08
KTHREAD *NextThread;
all  
0x0C
KTHREAD *IdleThread;
all  
0x10
CHAR Number;
3.10 to 5.2  
UCHAR Number;
6.0 only next as ULONG at 0x03CC
UCHAR LegacyNumber;
6.1 and higher  
0x11
CHAR Reserved;
3.10 to 5.2  
UCHAR NestingLevel;
6.0 and higher  
0x12
USHORT BuildType;
3.10 and higher  
0x14
KAFFINITY SetMember;
3.10 to 6.0 next as GroupSetMember at 0x03C8
0x18 (3.10 to 6.0);
0x14
CHAR CpuType;
all  
0x19 (3.10 to 6.0);
0x15
CHAR CpuID;
all  
0x1A (3.10 to 6.0);
0x16
USHORT CpuStep;
3.10 to 5.2  
union {
    USHORT CpuStep;
    struct {
        UCHAR CpuStepping;
        UCHAR CpuModel;
    };
};
6.0 and higher  
0x1C (3.10 to 6.0);
0x18
KPROCESSOR_STATE ProcessorState;
all  

By no stretch can even the architecturally defined section be thought to have lived up to Microsoft’s 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 are confirmed for the x86 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 and including the version 4.0 from Windows NT 4.0 SP5, read only a 3-bit family from bits 8 to 10. Indeed, this departure of Microsoft’s from Intel’s specification of a 4-bit family may be 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 an extended model in bits 16 to 19 supply four high bits to make an 8-bit CpuModel. 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.

The ProcessorState itself moved for version 6.1. This shift gave it 8-byte alignment, which may have been seen as a happy side-effect when version 6.2 added a 64-bit register to the KSPECIAL_REGISTERS that’s nested in the KPROCESSOR_STATE.

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
0x0338
KNODE *ParentNode;
6.3 and higher previously at 0x04CC
0x033C
CHAR *PriorityState;
6.3 and higher  
0x013C (3.10 to 4.0);
0x033C (5.0 to 6.0);
0x0338 (6.1 to 6.2);
0x0340
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);
0x0378
ULONG HalReserved [0x10];
all  

The HAL’s reserved area certainly does get used by at least some HALs—even from long ago, e.g., HALMPS.DLL from Windows NT 3.50—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 of the architecturally defined section so that they too can grow without shifting anything else that’s architectural).

These future additions arrived with Windows Vista, which claims four bytes at the start. Its first Service Pack brings 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 of padding for version 5.1 is there forever.

Offset Definition Versions Remarks
0x03BC (6.0);
0x03B8
ULONG CFlushSize;
6.0 and higher  
0x03C0 (late 6.0);
0x03BC
UCHAR CoresPerPhysicalProcessor;
late 6.0 and higher previously at 0x1BBA
0x03C1 (late 6.0);
0x03BD
UCHAR LogicalProcessorsPerCore;
late 6.0 and higher previously at 0x1F8C
0x03BE
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);
0x03BF
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);
0x03C0
ULONG MHz;
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);
0x03C4
UCHAR GroupIndex;
6.1 and higher  
0x03C6 (6.1 to 6.2);
0x03C5
USHORT Group;
6.1 to 6.2  
UCHAR Group;
6.3 and higher  
0x03C6
UCHAR PrcbPad05 [2];
6.3 and higher  
0x03C8
KAFFINITY GroupSetMember;
6.1 and higher previously as SetMember at 0x14
0x03CC
ULONG Number;
6.1 and higher previously UCHAR at 0x10
0x03D0
BOOLEAN ClockOwner;
6.2 and higher  
0x03D1
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);
0x03D2
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 (x86-specific) 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);
0x0418
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 not just cache-aligned but page-aligned). Programmers who deal much with the KPCR and KPRCB in the debugger—and reverse engineers in their disassemblies—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.

Non-Architectural

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);
0x04A0
ULONG InterruptCount;
all  
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);
0x04A4
LARGE_INTEGER KernelTime;
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);
0x04A8
LARGE_INTEGER UserTime;
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);
0x04AC
LARGE_INTEGER DpcTime;
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);
0x04B0
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);
0x04B4
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 (0x08) 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. The space recovered from this unknown KDPC started to get used in version 3.51:

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 has Spare2 follow immediately from AdjustDpcThreshold, confirming that DebugDpcTime was not yet defined.

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);
0x04B8
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);
0x04BC
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);
0x04C0
UCHAR DebuggerSavedIRQL;
5.2 and higher  
0x0546 (late 5.2 to early 6.0);
0x05C6 (late 6.0);
0x04C5 (6.1 to 6.2);
0x04C1
UCHAR NodeColor;
late 5.2 and higher  
0x04C2
UCHAR DeepSleep;
10.0 and higher  
0x04C3
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  
0x04C4
PVOID volatile CachedStack;
1709 and higher  
0x0548 (late 5.2 to early 6.0);
0x05C8 (late 6.0);
0x04C8
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);
0x04CC
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);
0x04D0
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 kernel’s 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
0x04D4
PVOID MmFlushList;
1803 only
PVOID MmInternal;
1809 and higher
0x04D8
KPRCBFLAG PrcbFlags;
1803 and higher
0x04DC
PVOID SchedulerAssist;
1709 and higher

Totals for the first six of the per-processor performance counters have always been retrievable through the SystemPerformanceInformation (0x02) 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);
0x04E0
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);
0x04E4
ULONG CcFastReadWait;
all  
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);
0x04E8
ULONG CcFastReadNotPossible;
all  
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);
0x04EC
ULONG CcCopyReadNoWait;
all  
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);
0x04F0
ULONG CcCopyReadWait;
all  
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);
0x04F4
ULONG CcCopyReadNoWaitMiss;
all  

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);
0x04F8
LONG volatile MmSpinLockOrdering;
6.0 and higher  
0x057C (early 6.0);
0x05FC (late 6.0);
0x04FC
LONG volatile IoReadOperationCount;
6.0 and higher previously at 0x059C
0x0580 (early 6.0);
0x0600 (late 6.0);
0x0500
LONG volatile IoWriteOperationCount;
6.0 and higher previously at 0x05A0
0x0584 (early 6.0);
0x0604 (late 6.0);
0x0504
LONG volatile IoOtherOperationCount;
6.0 and higher previously at 0x05A4
0x0588 (early 6.0);
0x0608 (late 6.0);
0x0508
LARGE_INTEGER IoReadTransferCount;
6.0 and higher previously at 0x05A8
0x0590 (early 6.0);
0x0610 (late 6.0);
0x0510
LARGE_INTEGER IoWriteTransferCount;
6.0 and higher previously at 0x05B0
0x0598 (early 6.0);
0x0618 (late 6.0);
0x0518
LARGE_INTEGER IoOtherTransferCount;
6.0 and higher previously at 0x05B8
0x05A0 (early 6.0);
0x0620 (late 6.0);
0x0520
ULONG CcFastMdlReadNoWait;
6.0 and higher  
0x05A4 (early 6.0);
0x0624 (late 6.0);
0x0524
ULONG CcFastMdlReadWait;
6.0 and higher  
0x05A8 (early 6.0);
0x0628 (late 6.0);
0x0528
ULONG CcFastMdlReadNotPossible;
6.0 and higher  
0x05AC (early 6.0);
0x062C (late 6.0);
0x052C
ULONG CcMapDataNoWait;
6.0 and higher  
0x05B0 (early 6.0);
0x0630 (late 6.0);
0x0530
ULONG CcMapDataWait;
6.0 and higher  
0x05B4 (early 6.0);
0x0634 (late 6.0);
0x0534
ULONG CcPinMappedDataCount;
6.0 and higher  
0x05B8 (early 6.0);
0x0638 (late 6.0);
0x0538
ULONG CcPinReadNoWait;
6.0 and higher  
0x05BC (early 6.0);
0x063C (late 6.0);
0x053C
ULONG CcPinReadWait;
6.0 and higher  
0x05C0 (early 6.0);
0x0640 (late 6.0);
0x0540
ULONG CcMdlReadNoWait;
6.0 and higher  
0x05C4 (early 6.0);
0x0644 (late 6.0);
0x0544
ULONG CcMdlReadWait;
6.0 and higher  
0x05C8 (early 6.0);
0x0648 (late 6.0);
0x0548
ULONG CcLazyWriteHotSpots;
6.0 and higher  
0x05CC (early 6.0);
0x064C (late 6.0);
0x054C
ULONG CcLazyWriteIos;
6.0 and higher  
0x05D0 (early 6.0);
0x0650 (late 6.0);
0x0550
ULONG CcLazyWritePages;
6.0 and higher  
0x05D4 (early 6.0);
0x0654 (late 6.0);
0x0554
ULONG CcDataFlushes;
6.0 and higher  
0x05D8 (early 6.0);
0x0658 (late 6.0);
0x0558
ULONG CcDataPages;
6.0 and higher  
0x05DC (early 6.0);
0x065C (late 6.0);
0x055C
ULONG CcLostDelayedWrites;
6.0 and higher  
0x05E0 (early 6.0);
0x0660 (late 6.0);
0x0560
ULONG CcFastReadResourceMiss;
6.0 and higher  
0x05E4 (early 6.0);
0x0664 (late 6.0);
0x0564
ULONG CcCopyReadWaitMiss;
6.0 and higher  
0x05E8 (early 6.0);
0x0668 (late 6.0);
0x0568
ULONG CcFastMdlReadResourceMiss;
6.0 and higher  
0x05EC (early 6.0);
0x066C (late 6.0);
0x056C
ULONG CcMapDataNoWaitMiss;
6.0 and higher  
0x05F0 (early 6.0);
0x0670 (late 6.0);
0x0570
ULONG CcMapDataWaitMiss;
6.0 and higher  
0x05F4 (early 6.0);
0x0674 (late 6.0);
0x0574
ULONG CcPinReadNoWaitMiss;
6.0 and higher  
0x05F8 (early 6.0);
0x0678 (late 6.0);
0x0578
ULONG CcPinReadWaitMiss;
6.0 and higher  
0x05FC (early 6.0);
0x067C (late 6.0);
0x057C
ULONG CcMdlReadNoWaitMiss;
6.0 and higher  
0x0600 (early 6.0);
0x0680 (late 6.0);
0x0580
ULONG CcMdlReadWaitMiss;
6.0 and higher  
0x0604 (early 6.0);
0x0684 (late 6.0);
0x0584
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);
0x0588
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);
0x058C
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);
0x0590
ULONG KeSystemCalls;
3.50 and higher  
0x0594
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 NT 4.0 and Windows 2000 from type information.

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);
0x0598
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);
0x05A0
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
0x0620
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);
0x0F20
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);
0x1820
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);
0x2120
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);
0x2124
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);
0x2128
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);
0x212C
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);
0x2160
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);
0x216C
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);
0x2170
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);
0x2174
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);
0x2178
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

typedef 
VOID 
(*PKIPI_WORKER) (
    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);
0x21A0
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);
0x21A4
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
0x21A8
ULONGLONG LastNonHrTimerExpiration;
1607 to 1903  
ULONG PrcbPad94 [2];
2004 and higher  
0x21B0
ULONGLONG TrappedSecurityDomain;
1803 and higher  
0x21B8
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  
0x21B9
union {
    UCHAR BpbFeatures;
    struct {
        UCHAR BpbClearOnIdle : 1;
        UCHAR BpbEnabled : 1;
        UCHAR BpbSmep : 1;
        UCHAR BpbFeaturesReserved : 5;
    };
};
1809 and higher  
0x21BA
UCHAR BpbSpecCtrlValue;
1803 only  
UCHAR BpbCurrentSpecCtrl;
1809 and higher  
0x21BB
UCHAR BpbCtxSwapSetValue;
1803 only  
UCHAR BpbKernelSpecCtrl;
1809 and higher  
0x21BC
UCHAR BpbNmiSpecCtrl;
1809 and higher  
0x21BD
UCHAR BpbUserSpecCtrl;
1809 and higher  
0x21BE
UCHAR PrcbPad49 [2];
1809 and higher  
0x21C0
ULONG ProcessorSignature;
1809 and higher  
0x21C4
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);
0x21C8
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);
0x21D0
ULONG InterruptLastCount;
6.2 and higher
0x21DC (6.2);
0x21D4
ULONG InterruptRate;
6.2 and higher
0x21D8
ULONG DeviceInterrupts;
6.3 and higher
0x21DC
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)
LIST_ENTRY DpcListHead;
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)
KSPIN_LOCK DpcLock;
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);
0x21E0
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);
0x2210
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);
0x2214
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);
0x2218
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);
0x221C
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);
0x2220
ULONG DpcLastCount;
5.2 and higher previously at 0x087C
0x191C (6.1);
0x221C (6.2);
0x2224
ULONG PrcbLock;
6.1 and higher previously at 0x1A1C
0x1920 (6.1);
0x2220 (6.2);
0x2228
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
0x2238
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);
0x2239
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);
0x223A
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);
0x223B
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);
0x223C
LONG DpcSetEventRequest;
5.2 to 6.0  
union {
    LONG volatile DpcRequestSummary;
    SHORT DpcRequestSlot [2];
    /*  changing members, follow link  */
};
6.1 and higher  

Timing

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);
0x2240
ULONG LastTick;
6.1 and higher  
0x1940
LONG MasterOffset;
6.1 only  
0x1944
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);
0x2244
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);
0x2248
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);
0x224C
LONG TickOffset;
late 5.2 to 6.0  
ULONGLONG TickOffset;
6.1 only  
ULONG ClockInterrupts;
6.2 and higher  
0x224C (6.2);
0x2254 (6.3 to 1903);
0x2250
ULONG ReadyScanTick;
6.2 and higher  
0x2250 (6.2)
UCHAR BalanceState;
6.2 only  
0x2251 (6.2);
0x2258 (6.3 to 1903);
0x2254
BOOLEAN GroupSchedulingOverQuota;
6.2 and higher  
0x2259 (10.0 to 1903);
0x2255
UCHAR ThreadDpcEnable;
10.0 and higher previously at 0x2238
0x2252 (6.2);
0x2259 (6.3);
0x225A (10.0 to 1903);
0x2256
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  

TO BE DONE

Offset Definition Versions Remarks
0x1960 (6.1);
0x2260
KTIMER_TABLE TimerTable;
6.1 and higher cache-aligned
0x3AB0
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);
0x3AE0
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);
0x3B00
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);
0x3B04
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);
0x3B08
LONG DpcWatchdogPeriod;
6.0 and higher  
0x1A0C (early 6.0);
0x1A8C (late 6.0);
0x31CC (6.1);
0x3ACC (6.2 to 1903);
0x3B0C
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);
0x3B10
LONG volatile KeSpinLockOrdering;
6.1 and higher  
0x3AD4 (1703 to 1903);
0x3B14
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  

TO BE DONE

Offset Definition Versions Remarks
0x3AD8 (6.2 to 1903);
0x3B18
ULONG QueueIndex;
6.2 and higher previously at 0x31F0
0x3ADC (6.2 to 1903);
0x3B1C
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);
0x3B20
ULONG ReadySummary;
6.3 and higher previously at 3AEC
0x3AE4 (6.3 to 1903);
0x3B24
LONG AffinitizedSelectionMask;
6.3 and higher  
0x1A28 (early 6.0);
0x1AA8 (late 6.0);
0x31E8 (6.1);
0x3AE8 (6.2 to 1903);
0x3B28
KSPIN_LOCK WaitLock;
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);
0x3B2C
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);
0x3B34
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);
0x3B38
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);
0x3B40
ULONGLONG StartCycles;
6.0 and higher  
0x3B00 (10.0 to 1607);
0x3B08 (1703 to 1903);
0x3B48
ULONGLONG TaggedCyclesStart;
10.0 and higher  
0x3B08 (10.0 to 1607);
0x3B10 (1703 to 1903);
0x3B50
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);
0x3B68
ULONGLONG CycleTime;
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);
0x3B70
ULONGLONG AffinitizedCycles;
6.3 and higher previously at 0x3B18
0x3B38 (1703 to 1903);
0x3B78
ULONGLONG ImportantCycles;
1703 and higher  
0x3B40 (1703 to 1903);
0x3B80
ULONGLONG UnimportantCycles;
1703 and higher  
0x3B48 (1703 to 1903);
0x3B88
ULONGLONG ReadyQueueExpectedRunTime;
1703 and higher  
0x3B18 (6.2 to 6.3);
0x3B30 (10.0 to 1607);
0x3B50 (1703 to 1903);
0x3B90
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);
0x3B98
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);
0x3BD8
ULONG PrcbPad71;
6.3 only  
ULONG PrcbPad71 [10];
10.0 to 1607  
ULONG PrcbPad71 [2];
1703 to 1903  
ULONG PrcbPad71;
2004 and higher  
0x3BDC
ULONG DpcWatchdogSequenceNumber;
2004 and higher  

TO BE DONE

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);
0x3BE0
LIST_ENTRY DispatcherReadyListHead [0x20];
6.0 and higher previously at 0x09F0;
cache-aligned
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);
0x3CE0
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);
0x3CE4
LONG LookasideIrpFloat;
5.1 and higher  
0x3C28 (6.2 to 6.3);
0x3CA8 (10.0 to 1903);
0x3CE8
RTL_RB_TREE ScbQueue;
6.2 and higher  
0x3C30 (6.2 to 6.3);
0x3CB0 (10.0 to 1903);
0x3CF0
LIST_ENTRY ScbList;
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);
0x3CF8
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);
0x3CFC
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);
0x3D00
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);
0x3D04
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);
0x3D08
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);
0x3D0C
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);
0x3D10
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);
0x3D14
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);
0x3D18
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);
0x3D1C
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);
0x3D20
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);
0x3D24
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);
0x3D28
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);
0x3D2C
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);
0x3D30
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);
0x3D34
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)
BOOLEAN SkipTick;
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);
0x3D38
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);
0x3D3C
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);
0x3D49
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);
0x3D4A
UCHAR LogicalProcessorsPerPhysicalProcessor;
5.1 and higher  
0x1C3B (late 6.0);
0x337B (6.1);
0x3C8B (6.2 to 6.3);
0x3D0B (10.0 to 1903);
0x3D4B
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)
ULONG MHz;
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);
0x3D50
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);
0x3D58
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)
PROCESSOR_POWER_STATE PowerState;
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);
0x3D60
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);
0x3D28
ULONG PrcbPad90;
6.2 only  
ULONG PrcbPad90 [2];
6.3 and higher  
0x3D68
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)
FX_SAVE_AREA NpxSaveArea;
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);
0x3D70
PROCESSOR_POWER_STATE PowerState;
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);
0x3F18
KDPC ForceIdleDpc;
1703 and higher
0x3F38
ULONGLONG MsrIa32TsxCtrl;
2004 and higher
0x3E30 (6.2);
0x3E40 (6.3);
0x3EB0 (10.0 to 1607);
0x3ED0 (1703);
0x3DF8 (1709 to 1903);
0x3F40
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
0x3F58
RTL_HASH_TABLE *DpcRuntimeHistoryHashTable;
2004 and higher  
0x3F5C
KDPC *DpcRuntimeHistoryHashTableCleanupDpc;
2004 and higher  
0x3F60
ULONGLONG CurrentDpcRuntimeHistoryCached;
2004 and higher  
0x3F68
ULONGLONG CurrentDpcStartTime;
2004 and higher  
0x3F70
KDEFERRED_ROUTINE *CurrentDpcRoutine;
2004 and higher  
0x3EF0 (1703);
0x3F30 (1709 to 1903);
0x3F74
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);
0x3F78
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);
0x3F98
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

The WHEA in the naming of WheaInfo is the Windows Hardware Error Architecture. What the WheaInfo points to is a WHEAP_INFO_BLOCK.

Windows 8 moves the WheaInfo, EtwSupport and (8-byte aligned) InterruptObjectPool further into the structure, keeping them together. Whether they’re intended as some sort of set is not known, but they are also together in the x64 KPRCB.

According to what the WMITRACE.DLL debugger extension’s undocumented !systrace StackTraceCounters command would do if given the expected kernel symbols, what the EtwSupport points to is an ETW_TRACING_BLOCK. The kernel’s public symbols do not show this structure, nor do any symbol files in the downloadable packages of public symbols. Whether any at Microsoft’s public symbol server do, I don’t know, but it’s safe to say that the ETW_TRACING_BLOCK is as obscure as can be for a kernel structure whose name is known with the certainty of its being looked for by a debugger extension. Searching for it through Google today, 8th February 2023, both with and without a leading underscore, gets zero matches.

Offset Definition Versions Remarks
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);
0x3FC0
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);
0x3FC8
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);
0x3FCC
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);
0x3FD0
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);
0x3FD4
CACHE_DESCRIPTOR Cache [5];
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);
0x4010
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);
0x4014
KAFFINITY PackageProcessorSet;
6.0 only  
KAFFINITY_EX PackageProcessorSet;
6.1 and higher  

That the KPRCB has a KAFFINITY_EX among its members has a curious side-effect in the symbol file WNTDLL.PDB for the WOW64 variant of NTDLL.DLL. Its type information for the KPRCB 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 the 32-bit NTDLL.DLL when debugging a 32-bit application, everything after PackageProcessorSet is said to be further into the KPRCB than it truly is.

The wonder, of course, is that a symbol file for any user-mode DLL has type information for the kernel-mode KPRCB. Even the symbol file for NTDLL surely is not intended to. It picks up KPRCB from a #include of I386_X.H. That this header gets included may itself be unintended. But included it is, and since it not only defines the KPRCB but uses it in an inline routine even the public symbols get type information for it. The practical consequence for debugging is nothing, the KPRCB having no meaning in the circumstances, but the significance for reverse engineering is two-fold. First, type information can turn up in symbol files for modules that make no run-time use of the type. This also has significance for programmers, and perhaps their lawyers, who are concerned for what’s revealed in symbol files. Second, type information is not as certainly correct as reverse engineers tend to think (or blindly hope).

Offset Definition Versions Remarks
0x3EE0 (6.2);
0x3F20 (6.3);
0x3FA0 (10.0 to 1703);
0x3FE0 (1709 to 1903);
0x4020
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);
0x4024
ULONG PrcbPad91 [1];
6.1 only  
ULONG ScanSiblingMask;
6.2 only next at 0x3F2C
KSHARED_READY_QUEUE *SharedReadyQueue;
6.3 and higher  
0x3FA8 (10.0 to 1703);
0x3FE8 (1709 to 1903);
0x4028
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);
0x402C
ULONG CoreProcessorSet;
6.0 and higher last member in 6.0

Inserted For Windows 8

Offset Definition Versions Remarks
0x3F2C (6.3);
0x3FB0 (10.0 to 1703);
0x3FF0 (1709 to 1903);
0x4030
ULONG ScanSiblingMask;
6.3 and higher previously at 0x3EF4
0x3F30 (6.3);
0x3FB4 (10.0 to 1703);
0x3FF4 (1709 to 1903);
0x4034
ULONG LLCMask;
6.3 and higher  
0x3F34 (6.3);
0x3FB8 (10.0 to 1703);
0x3FF8 (1709 to 1903);
0x4038
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);
0x404C
ULONG ScanSiblingIndex;
6.2 and higher  
0x3F00 (6.2)
ULONG LLCLevel;
6.2 only  
0x3F04 (6.2);
0x3F4C (6.3);
0x3FD0 (10.0 to 1703);
0x4010 (1709 to 1903);
0x4050
PVOID WheaInfo;
6.2 and higher previously at 0x34B0
0x3F08 (6.2);
0x3F50 (6.3);
0x3FD4 (10.0 to 1703);
0x4014 (1709 to 1903);
0x4054
PVOID EtwSupport;
6.2 and higher previously at 0x34B4
0x3F10 (6.2);
0x3F58 (6.3);
0x3FD8 (10.0 to 1703);
0x4018 (1709 to 1903);
0x4058
SLIST_HEADER InterruptObjectPool;
6.2 and higher previously at 0x34B8
0x3F60 (6.3)
ULONG SharedReadyQueueOffset;
6.3 only  
0x3FE0 (1703);
0x4020 (1709 to 1903);
0x4060
PVOID *DpcWatchdogProfile;
1703 and higher  
0x3FE4 (1703);
0x4024 (1709 to 1903);
0x4064
PVOID *DpcWatchdogProfileCurrentEmptyCapture;
1703 and higher  
0x3F18 (6.2);
0x3F64 (6.3);
0x3FE0 (10.0 to 1607);
0x3FE8 (1703);
0x4028 (1709 to 1903);
0x4068
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);
0x406C
ULONG PteBitCache;
6.3 and higher  
0x3F70 (6.3);
0x3FF0 (10.0 to 1703);
0x4030 (1709 to 1903);
0x4070
ULONG PteBitOffset;
6.3 and higher  
0x3F74 (6.3);
0x3FF4 (10.0 to 1703);
0x4034 (1709 to 1903);
0x4074
ULONG PrcbPad93;
6.3 and higher  
0x3F38 (6.2);
0x3F78 (6.3);
0x3FF8 (10.0 to 1703);
0x4038 (1709 to 1903);
0x4078
PROCESSOR_PROFILE_CONTROL_AREA *ProcessorProfileControlArea;
6.2 and higher  
0x3F3C (6.2);
0x3F7C (6.3);
0x3FFC (10.0 to 1703);
0x403C (1709 to 1903);
0x407C
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);
0x4080
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);
0x40A0
SYNCH_COUNTERS SynchCounters;
6.2 and higher
0x4018 (6.2);
0x4058 (6.3);
0x40D8 (10.0 to 1703);
0x4118 (1709 to 1903);
0x4158
FILESYSTEM_DISK_COUNTERS FsCounters;
6.2 and higher

TO BE DONE

Offset Definition Versions Remarks
0x3618 (6.1);
0x4028 (6.2);
0x4068 (6.3);
0x40E8 (10.0 to 1703);
0x4128 (1709 to 1903);
0x4168
CONTEXT *Context;
6.1 and higher  
0x361C (6.1);
0x402C (6.2);
0x406C (6.3);
0x40EC (10.0 to 1703);
0x412C (1709 to 1903);
0x416C
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);
0x4170
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);
0x4174
KENTROPY_TIMING_STATE EntropyTimingState;
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);
0x429C
PVOID IsrStack;
6.3 and higher  
0x41A0 (6.3);
0x4220 (10.0 to 1703);
0x4260 (1709 to 1903);
0x42A0
KINTERRUPT *VectorToInterruptObject [0xD0];
6.3 and higher  
0x44E0 (6.3);
0x4560 (10.0 to 1703);
0x45A0 (1709 to 1903);
0x45E0
SINGLE_LIST_ENTRY AbSelfIoBoostsList;
6.3 and higher  
0x44E4 (6.3);
0x4564 (10.0 to 1703);
0x45A4 (1709 to 1903);
0x45E4
SINGLE_LIST_ENTRY AbPropagateBoostsList;
6.3 and higher  
0x44E8 (6.3);
0x4568 (10.0 to 1703);
0x45A8 (1709 to 1903);
0x45E8
KDPC AbDpc;
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);
0x4608
IOP_IRP_STACK_PROFILER IoIrpStackProfilerCurrent;
10.0 and higher
0x45DC (10.0 to 1703);
0x461C (1709 to 1903);
0x465C
IOP_IRP_STACK_PROFILER IoIrpStackProfilerPrevious;
10.0 and higher
0x4630 (10.0 to 1703);
0x4670 (1709 to 1903);
0x46B0
KTIMER_EXPIRATION_TRACE TimerExpirationTrace [0x10];
10.0 and higher
0x4730 (10.0 to 1703);
0x4770 (1709 to 1903);
0x47B0
ULONG TimerExpirationTraceCount;
10.0 and higher
0x4734 (10.0 to 1703);
0x4774 (1709 to 1903);
0x47B4
PVOID ExSaPageArray;
10.0 and higher
0x4778 (1803 to 1903);
0x47B8
XSAVE_AREA_HEADER *ExtendedSupervisorState;
1803 and higher
0x4738 (10.0 to 1703);
0x4778 (1709);
0x477C (1803 to 1903);
0x47BC
ULONG PrcbPad100 [10];
10.0 to 1709
ULONG PrcbPad100 [9];
1803 and higher

TO BE DONE

Offset Definition Versions Remarks
0x4760 (10.0 to 1703);
0x47A0 (1709 to 1903);
0x47E0
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);
0x4920
REQUEST_MAILBOX *Mailbox;
10.0 and higher cache-aligned
0x48A4 (10.0 to 1703);
0x48E4 (1709 to 1903);
0x4924
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
0x4EE0
ULONG KernelDirectoryTableBase;
1803 and higher page-aligned
0x4EE4
ULONG EspBaseShadow;
1803 and higher  
0x4EE8
ULONG UserEspShadow;
1803 and higher  
0x4EEC
ULONG ShadowFlags;
1803 and higher  
0x4EF0
ULONG UserDS;
1803 and higher  
0x4EF4
ULONG UserES;
1803 and higher  
0x4EF8
ULONG UserFS;
1803 and higher  
0x4EFC
PVOID EspIretd;
1803 and higher  
0x4F00
ULONG RestoreSegOption;
1803 and higher  
0x4F04
ULONG SavedEsi;
1803 and higher  
0x4F08
USHORT VerwSelector;
2004 and higher  
0x4F0A
USHORT PrcbShadowPad;
2004 and higher  
0x4F0C
ULONG TaskSwitchCount;
2004 and higher  
0x4F08 (1803 to 1903);
0x4F10
ULONG DbgLogs [0x0200];
1803 and higher  
0x5708 (1803 to 1903);
0x5710
ULONG DbgCount;
1803 and higher  
0x570C (1803 to 1903);
0x5714
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);
0x5EE0
REQUEST_MAILBOX RequestMailbox [ANYSIZE_ARRAY];
10.0 and higher cache-aligned;
page-aligned in 1803 and higher;
last member in 10.0 and higher