KPRCB (i386)

The name KPRCB stands for (Kernel) Processor Control Block. The kernel keeps one KPRCB for each logical processor, embedded in 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.

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, apparently all along, as confirmed by the NTOSP.H that has recently been disclosed in a Windows Driver Kit (WDK) for Windows 10.

The part of the KPCR that’s ahead of the embedded KPRCB is highly stable, notably so that the offset of the Prcb member is reliable over all Windows versions, as is the offset of the KPRCB within the KPCR. Members of the KPRCB are sometimes accessed through offsets from the KPCR. For some members, this is even the most usual access. Notably, the KeGetCurrentThread function is implemented very like

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

both as exported and when inlined throughout the kernel. This access to the KPRCB members as nested in the KPCR is formalised in Microsoft’s assembly-language header KS386.INC through such definitions as PcCurrentThread for the preceding field offset. Whether such access is written in assembly language or C, one case is known of it going wrong, such that an offset from the KPRCB is applied instead to the address of a KPCR, and it would not surprise if there have been others.

Contention

In many typical cases, including that of KeGetCurrentThread, kernel-mode code does much better to access a KPRCB member via the fs register and an offset from the KPCR. This is because the address that some such routine as KeGetCurrentPrcb obtains is merely the address of the KPRCB for the processor that the current thread was being run on at the time. It remains the address of the current KPRCB only while the thread can ensure it is not switched to another processor. Often, it can’t be—at least in the circumstances that have the kernel itself access the KPRCB. 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, whether in the kernel and from outside, is not known.

It seems at least a fair proposition that this point 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 includes work they do independently of FsRtlCopyRead, etc. Look instead at the implementations. All that’s needed is one inc instruction relative to fs. Even without a lock prefix, as long as the counter in each KPRCB is never incremented any other way, each counter can only be incremented by the one intended processor. Each truly keeps a per-processor count. But Microsoft did not deliver this certainty until version 6.1.

Even then, Microsoft did not get to it directly. 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 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 the circumstances surrounding the accounting event, who’s to say the increment is wrong to go to one processor rather than the other?

This case of loss—here just of 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 can be, though it is hardly trivial. So, let that be a warning for any access you attempt to per-processor data in real-world code!

Variability

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. Early means from before some cut-off, late starts at the cut-off:

Names, types and offsets are from Microsoft’s symbol files for the kernel, starting from Windows 2000 SP3. Some symbol files, e.g., those for the HAL in Windows 7 and higher, define a smaller KPRCB that is just an “architecturally defined section” at the structure’s start. This term is long established from comments in C-language definitions of a partial KPRCB for other processor architectures all the way back to the NTDDK.H from the Device Driver Kit (DDK) for Windows NT 3.51. Microsoft’s first known disclosure of a C-language definition of the KPRCB for the i386 architecture—though still only of the architecturally defined section—is in the NTOSP.H from the WDK for Windows 10.

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

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. 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 version Windows 7 removes SetMember and thus shifts all subsequent members of the architecturally defined section (except for an adjustment that stabilises the LockQueue at offset 0x0418).

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  

Long before Windows 7, however, “architecturally defined” was certainly not defended by its programmers as meaning that 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 change their position at least once.

Kernel And HAL Reservations

Two specifically reserved areas, one each for the kernel and the HAL, are also supposed to be architectural—and may even be, at least since their last shift (for Windows 7). 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
0x013C (3.10 to 4.0);
0x033C (5.0 to 6.0);
0x0338
ULONG KernelReserved [0x10];
3.10 to 6.2  
KNODE *ParentNode;
6.3 and higher previously at 0x04CC
0x033C
CHAR *PriorityState;
6.3 and higher  
0x0340
ULONG KernelReserved [0x0E];
6.3 and higher  

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

Offset Definition Versions
0x017C (3.10 to 4.0);
0x037C (5.0 to 6.0);
0x0378
ULONG HalReserved [0x10];
all

Architectural Padding

The specific reservations for the kernel and HAL look like they originally ended the architecturally defined section. Version 5.0 inserted an array of per-processor spin lock queues as the new end. Version 5.1 pushed those further into the structure by inserting 0x5C bytes explicitly as padding.

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 KPRCB for the amd64 architecture, 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 such that future additions to the architecturally defined section should not again shift the lock queues (which remain at the end so that they too can grow without shifting anything that’s architectural).

Those additions wait until Windows Vista claims four bytes at the start. Its first Service Pack starts bringing members from (much) further into the structure, presumably for better-defined access to them from outside the kernel—and the HAL certainly does access many of them. Note two things, however. 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 and then was reordered it relative to its fellows for version 6.3. Second, no matter what is added, or even moved after being added, the padding is always adjusted to end at the same offset. However much the structure has been allowed to vary here, someone really does seem to have wanted that whatever was at offset 0x0418 after this insertion for version 5.1 is there forever.

Offset Definition Applicable Versions Relocations
0x03BC (5.1 to 6.0);
0x03B8
UCHAR PrcbPad0 [0x5C];
5.1 to 5.2  
ULONG CFlushSize;
6.0 and higher  
0x03C0 (6.0);
0x03BC
UCHAR PrcbPad0 [0x58];
early 6.0 only  
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
0x03C2 (late 6.0);
0x03BE
UCHAR PrcbPad0 [2];
late 6.0 to 6.2  
UCHAR CpuVendor;
6.3 and higher previously at 0x03C4
0x03BF
UCHAR PrcbPad0 [1];
6.3 and higher  
0x03C4 (late 6.0);
0x03C0
ULONG MHz;
late 6.0 and higher previously at 0x1BBC
0x03C8 (late 6.0)
UCHAR PrcbPad1 [0x50];
late 6.0 only  
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
UCHAR PrcbPad1 [0x48];
6.1 only  
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  
0x03D2
UCHAR PrcbPad1 [0x46];
6.2 only  
UCHAR PrcbPad10 [0x46];
6.3 and higher  

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 queued spin locks 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 early 5.2
KSPIN_LOCK_QUEUE LockQueue [0x21];
late 5.2 to early 6.0
KSPIN_LOCK_QUEUE LockQueue [0x31];
late 6.0 only
KSPIN_LOCK_QUEUE LockQueue [0x11];
6.1 and higher
0x0498 (5.1 to early 5.2)
UCHAR PrcbPad1 [8];
5.1 to early 5.2

The array is indexed by members of the enumeration KSPIN_LOCK_QUEUE_NUMBER, which is defined in WDM.H and 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.

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 means that most members in the non-architectural section shift wildly through versions 5.1 to 6.1 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  
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 case of NtQuerySystemInformation in all known versions. See that version 3.10 keeps the raw 64-bit times. Later versions scale by the so-called maximum increment.

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

Offset Definition Versions Remarks
0x01E8 (3.10);
0x01D4 (3.50)
a KDPC for when a thread ends its quantum 3.10 only  
ULONG Spare2 [8];
3.50 only  
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);
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;
3.51 and higher  
0x01E0 (3.51 to 4.0);
0x0460 (5.0)
ULONG DebugDpcTime;
3.51 to 5.0 next at 0x04B4

The ApcBypassCount and DpcBypassCount are retrievable through the SystemInterruptInformation (0x17) case of NtQuerySystemInformation in the applicable versions.

That DebugDpcTime is defined before the builds of version 5.0 that have type information in symbol files is uncertain. No use is known of it in any version, here or at its next position until version 6.0 reuses it (or relabels it) as DpcTimeCount.

Offset Definition Versions Remarks
0x01E4 (3.51 to 4.0);
0x0464 (5.0);
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 Spare2 [4];
3.51 to 5.0  
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  
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  
0x04C6 (early 5.2);
0x0546 (late 5.2 to early 6.0);
0x05C6 (late 6.0);
0x04C5 (6.1 to 6.2);
0x04C1
UCHAR Spare1 [6];
early 5.2 only  
UCHAR NodeColor;
late 5.2 and higher  
0x0547 (late 5.2 to early 6.0);
0x05C7 (late 6.0);
0x04C6 (6.1 to 6.2);
0x04C2
UCHAR Spare1;
late 5.2 only  
UCHAR PollSlot;
6.0 only  
UCHAR PrcbPad20 [2];
6.1 to 6.2  
UCHAR PrcbPad20 [6];
6.3 only  
UCHAR DeepSleep;
10.0 and higher  
0x04C3
UCHAR PrcbPad20 [5];
10.0 and higher  
0x04C8 (5.1)
UCHAR MultiThreadSetBusy;
5.1 only  
0x04C9 (5.1)
UCHAR Spare2 [3];
5.1 only  
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

By version 5.1, the 0x20 bytes that were left spare from removal of the version 3.10 KDPC were not completely reused, but they were filled enough that the following are in new space. They became obsolete when Windows 7 allowed more than 32 processors.

Offset Definition Applicable Versions
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

That ThreadStartCount, below, is defined before the builds of version 5.0 that have type information in the symbol files is uncertain. No use is known of it in any version until it gets reused for late builds of version 5.2.

Offset Definition Applicable Versions Remarks
0x01F4 (3.51 to 4.0);
0x0474 (5.0);
0x04D8 (5.1 to early 5.2);
0x0558 (late 5.2 to early 6.0);
0x05D8 (late 6.0);
0x04D0 (6.1 to 6.2);
0x04CC
ULONG ThreadStartCount [2];
3.51 to early 5.2  
ULONG SecondaryColorMask;
late 5.2 and higher  
0x055C (late 5.2 to early 6.0);
0x05DC (late 6.0);
0x04D4 (6.1 to 6.2);
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 RequestSummary [4];
3.10 to 3.50 next at 0x02C0 (3.51)
0x020C (3.10);
0x01F8 (3.50)
UCHAR volatile IpiFrozen;
3.10 to 3.50 next at 0x02CC (3.51)
0x0210 (3.10);
0x01FC (3.50)
ULONG volatile ReverseStall;
3.10 to 3.50 next at 0x02C8 (3.51)
0x0214 (3.10);
0x0200 (3.50)
unknown dword 3.10 to 3.50 next at 0x02C4 (3.51)
0x0218 (3.10);
0x0204 (3.50)
0x20 bytes with no known use 3.10 to 3.50  

The RequestSummary array records which of the four possible types of request are being sent to the processor. They are set at the sending processor by (what was then) the kernel’s exported KiIpiSend function on the way to asking the HAL’s HalRequestIpi function to deliver the request. They get cleared at the receiving processor by the kernel’s exported KiIpiServiceRoutine. The unknown dword supports an early form of signalling completion. Like its re-implementation, it may have been named SignalDone but the correspondence is loose enough that I prefer not to settle on either the name or type.

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. Specifically, 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 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 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. That Microsoft’s name for the pointer array was HotData is mere supposition from the name that survives their being marked spare.

Offset Definition Versions
0x0238 (3.10);
0x0224 (3.50);
0x01FC (3.51 to 4.0);
0x047C (5.0)
PVOID HotData [2];
3.10 to early 4.0
PVOID SpareHotData [2];
late 4.0 to 5.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:

Offset Definition Applicable Versions
0x04D8 (6.1 to 6.2);
0x04D4
ULONG PrcbPad21 [2];
6.1 to 6.2
ULONG PrcbPad21 [3];
6.3 and higher

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

Offset Definition Versions
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
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 Applicable 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  

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 Applicable 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 a large amount of space—and in version 4.0, a very large amount of space—for which no use is known other than two pointers that added to the original optimisation of file locking. It is mere supposition that this unaccounted space in the earliest versions is just a larger allowance for the same reservation that is known for Windows 2000 from symbol files.

Offset Definition Versions Remarks
0x0258 (3.10);
0x0268 (3.50);
0x0240 (3.51 to 4.0)
ULONG ReservedCounter [0x10];
3.10 only last member in 3.10
ULONG ReservedCounter [0x20];
3.50 to 3.51  
array of two pointers for file locks;
for shared and exclusive, respectively
early 4.0 only  
ULONG ReservedCounter [0x90];
late 4.0 only  
0x0248 (early 4.0)
ULONG ReservedCounter [0x8E];
early 4.0 only  

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 5.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. When Windows 7 added a counter, it shifted the padding.

Offset Definition Applicable Versions
0x04C0 (5.0);
0x051C (5.1 to early 5.2);
0x05C0 (late 5.2);
0x0614 (early 6.0);
0x0694 (late 6.0);
0x0594
ULONG ReservedCounter [8];
5.0 only
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 AvailableTime;
6.1 and higher
0x0598
ULONG PrcbPad22 [2];
6.1 and higher

Lookaside Lists

Symbol files for Windows 2000 SP3 and SP4 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
0x04E0 (5.0)
PVOID SmallIrpFreeEntry;
5.0 only
0x04E4 (5.0)
PVOID LargeIrpFreeEntry;
5.0 only
0x04E8 (5.0)
PVOID MdlFreeEntry;
5.0 only
0x04EC (5.0)
PVOID CreateInfoFreeEntry;
5.0 only
0x04F0 (5.0)
PVOID NameBufferFreeEntry;
5.0 only
0x04F4 (5.0)
PVOID SharedCacheMapFreeEntry;
5.0 only
0x04F8 (5.0)
ULONG CachePad0 [2];
5.0 only

Note that this first symbol that explicitly suggests padding for cache alignment does not actually cache-align what follows.

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

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 Applicable Versions
0x0620
GENERAL_LOOKASIDE_POOL PPNxPagedLookasideList [0x20];
6.2 and higher
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
GENERAL_LOOKASIDE_POOL PPNPagedLookasideList [0x20];
6.0 and higher
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
GENERAL_LOOKASIDE_POOL PPPagedLookasideList [0x20];
6.0 and higher

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
0x0600 (5.0)
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 Applicable Versions Relocations
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  
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  

TO BE DONE

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  
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
ULONG 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
KIPI_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  

The KIPI_WORKER type is a callback function that takes four PVOID arguments and returns VOID. It is defined in the NTOSP.H from the WDK for Windows 10.

The RequestSummary, below, starts with the old implementation as an array of bytes that record which of the four possible types of 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
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 RequestSummary [4];
3.51 only previously at 0x01F4
ULONG volatile RequestSummary;
4.0 and higher  
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
unknown dword 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
0x04AC (4.0);
0x06AC (5.0)
PVOID IpiFrame;
4.0 to 5.0 next at 0x07A8
0x02CC (3.51)
UCHAR volatile IpiFrozen;
3.51 only perviously at 0x01F8;
next as ULONG volatile at 0x0494
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
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 and higher  

The preceding padding does not cache-align in version 6.2 and higher. These versions instead squeeze two or four members into the end of the cache line.

Offset Definition Applicable 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

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;
3.51 to 5.0  
0x02E8 (3.51);
0x04C8 (4.0);
0x06C8 (5.0)
ULONG CachePad3 [2];
3.51 to 5.0  
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 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  
0x02F4 (3.50);
0x030C (3.51);
0x04EC (4.0);
0x06EC (5.0);
0x0874 (5.1)
ULONG KernelReserved2 [0x0F];
3.50 only  
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;
  previously at 0x06D4;
next at 0x0894
0x031C (3.51);
0x04FC (4.0);
0x06FC (5.0)
PVOID DpcStack;
3.51 to 5.0 next at 0x0868
0x088C (5.1)
ULONG QuantumEnd;
5.1 only previously at 0x0750;
next as BOOLEAN volatile at 0x08C1
0x0320 (3.51);
0x0500 (4.0);
0x0700 (5.0);
0x0890 (5.1)
ULONG KernelReserved2 [0x0A];
3.51 to 5.0  
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.0  
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 Applicable 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  
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 next as bit at 0x1934
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. That this byte is in the Tag in the PPPagedLookasideList for paged pool allocations of 0x0100 bytes means that the corruption is harmless but also that it is 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 a TRUE versus FALSE answer 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 yes if either byte is non-zero.

Offset Definition Applicable 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 to 6.0 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
UCHAR volatile IdleSchedule;
5.2 and higher  

A reworking for Windows 7 shows here in the introduction of bit flags for DPC management, which Windows 8 formalised: see that DpcThreadActive and DpcThreadRequested were previously BOOLEAN volatile at offsets 0x1A1B and 0x1A19.

Offset Definition Applicable Versions
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];
    struct {
        SHORT NormalDpcState;
        union {
            USHORT volatile DpcThreadActive : 1;        // 0x0001
            SHORT ThreadDpcState;
        };
    };
};
6.1 only
union {
    LONG volatile DpcRequestSummary;
    SHORT DpcRequestSlot [2];
    struct {
        SHORT NormalDpcState;
        SHORT ThreadDpcState;
    };
    struct {
        ULONG DpcNormalProcessingActive : 1;            // 0x00000001
        ULONG DpcNormalProcessingRequested : 1;         // 0x00000002
        ULONG DpcNormalThreadSignal : 1;                // 0x00000004
        ULONG DpcNormalTimerExpiration : 1;             // 0x00000008
        ULONG DpcNormalDpcPresent : 1;                  // 0x00000010
        ULONG DpcNormalLocalInterrupt : 1;              // 0x00000020
        ULONG DpcNormalSpare : 10;
        ULONG DpcThreadActive : 1;                      // 0x00010000
        ULONG DpcThreadRequested : 1;                   // 0x00020000
        ULONG DpcThreadSpare : 14;
    };
};
6.2 and higher

Timing

Offset Definition Applicable Versions Remarks
0x19C8 (early 6.0);
0x1A48 (late 6.0);
0x1938 (6.1);
0x2238 (6.2);
0x2240
LONG Sleeping;
6.0 only previously at 0x055C
ULONG volatile TimerHand;
6.1 only previously at 0x1A24
ULONG LastTimerHand;
6.2 and higher  
0x193C (6.1);
0x223C (6.2);
0x2244
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
ULONG PeriodicCount;
6.0 and higher  
0x19D0 (early 6.0);
0x1A50 (late 6.0);
0x1950 (6.1);
0x2244 (6.2);
0x224C
ULONG PeriodicBias;
6.0 and higher  
0x08C8 (early 5.2);
0x0988 (late 5.2);
0x19D4 (early 6.0);
0x1A54 (late 6.0);
0x1954
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
LONG TickOffset;
late 5.2 to 6.0  
ULONGLONG TickOffset;
6.1 only  
ULONG ClockInterrupts;
6.2 and higher  
0x224C (6.2);
0x2254
ULONG ReadyScanTick;
6.2 and higher  
0x2250
UCHAR BalanceState;
6.2 only  
0x2251 (6.2);
0x2258
BOOLEAN GroupSchedulingOverQuota;
6.2 and higher  
0x2252 (6.2);
0x2259
UCHAR PrcbPad41 [0x0A];
6.2 only  
UCHAR PrcbPad41 [3];
6.3 only  
UCHAR ThreadDpcEnable;
10.0 and higher previously at 0x2238
0x225A
UCHAR PrcbPad41 [2];
10.0 and higher  
0x1960 (6.1);
0x2260
KTIMER_TABLE TimerTable;
6.1 and higher  

TO BE DONE

Offset Definition Applicable Versions
0x08C0 (late 5.1);
0x08E0 (early 5.2);
0x09A0 (late 5.2);
0x19E0 (early 6.0);
0x1A60 (late 6.0);
0x31A0 (6.1);
0x3AA0
KDPC CallDpc;
late 5.1 and higher
0x1A00 (early 6.0);
0x1A80 (late 6.0);
0x31C0 (6.1);
0x3AC0
LONG ClockKeepAlive;
6.0 and higher
0x1A04 (early 6.0);
0x1A84 (late 6.0);
0x31C4 (6.1);
0x3AC4
UCHAR ClockCheckSlot;
6.0 to 6.1
UCHAR PrcbPad6 [4];
6.2 and higher
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)
UCHAR PrcbPad6 [2];
6.0 to 6.1
0x1A08 (early 6.0);
0x1A88 (late 6.0);
0x31C8 (6.1);
0x3AC8
LONG DpcWatchdogPeriod;
6.0 and higher
0x1A0C (early 6.0);
0x1A8C (late 6.0);
0x31CC (6.1);
0x3ACC
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

TO BE DONE

Offset Definition Applicable Versions
0x0900 (early 5.2);
0x09C0 (late 5.2);
0x1A18 (early 6.0);
0x1A98 (late 6.0);
0x31D8 (6.1);
0x3AD0
ULONG PrcbPad7 [8];
5.2 only
ULONG PrcbPad70 [2];
6.0 only
LONG volatile KeSpinLockOrdering;
6.1 and higher
0x31DC (6.1);
0x3AD4
ULONG PrcbPad70 [1];
6.1 and higher

TO BE DONE

Offset Definition Applicable Versions Remarks
0x3AD8
ULONG QueueIndex;
6.2 and higher previously at 0x31F0
0x3ADC
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
LIST_ENTRY WaitListHead;
5.2 to 6.2 next at 0x3AEC
ULONG ReadySummary;
6.3 and higher previously at 3AEC
0x3AE4
LONG AffinitizedSelectionMask;
6.3 and higher  
0x1A28 (early 6.0);
0x1AA8 (late 6.0);
0x31E8 (6.1);
0x3AE8
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
ULONG ReadySummary;
5.2 to 6.2 next at 3AE0
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
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
0x1A38 (early 6.0);
0x1AB8 (late 6.0);
0x31F8 (6.1);
0x3AF8
ULONGLONG StartCycles;
6.0 and higher  
0x3B00
ULONGLONG TaggedCyclesStart;
10.0 and higher  
0x3B08
ULONGLONG TaggedCycles [2];
10.0 and higher  
0x3B00 (6.2 to 6.3);
0x3B18
ULONGLONG GenerationTarget;
6.2 and higher  
0x1A40 (early 6.0);
0x1AC0 (late 6.0);
0x3200 (6.1);
0x3B08 (6.2 to 6.3);
0x3B20
ULONGLONG CycleTime;
6.0 only  
ULONGLONG volatile CycleTime;
6.1 and higher  
0x0A34 (early 5.2);
0x0AF4 (late 5.2);
0x1A48 (early 6.0);
0x1AC8 (late 6.0);
0x3208 (6.1);
0x3B10 (6.2 to 6.3);
0x3B28
ULONG PrcbPad72 [0x0B];
5.2 only  
ULONGLONG PrcbPad71 [3];
6.0 only  
ULONG volatile HighCycleTime;
6.1 to 6.2 next at 0x3B30
ULONGLONG AffinitizedCycles;
6.3 and higher previously at 0x3B18
0x320C (6.1);
0x3B14 (6.2)
ULONG PrcbPad71;
6.1 only  
ULONG ScbOffset;
6.2 only next at 0x3AF4
0x3210 (6.1);
0x3B18 (6.2 to 6.3);
0x3B30
ULONGLONG PrcbPad72 [2];
6.1 only  
ULONGLONG AffinitizedCycles;
6.2 only next at 0x3B10
ULONG volatile HighCycleTime;
6.3 and higher previously at 0x3B10
0x3B38
ULONGLONG Cycles [4][2];
10.0 and higher  
0x3B1C (6.3);
0x3B78
ULONG PrcbPad71;
6.3 only  
ULONG PrcbPad71 [10];
10.0 and higher  
0x1A60 (early 6.0);
0x1AE0 (late 6.0);
0x3220 (6.1);
0x3B20 (6.2 to 6.3);
0x3BA0
LIST_ENTRY DispatcherReadyListHead [0x20];
6.0 and higher previously at 0x09F0

TO BE DONE

Offset Definition Applicable Versions Remarks
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
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
LONG LookasideIrpFloat;
5.1 and higher  
0x3C28 (6.2 to 6.3);
0x3CA8
RTL_RB_TREE ScbQueue;
6.2 and higher  
0x3C30 (6.2 to 6.3);
0x3CB0
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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.51 to 5.0 next at 0x04C4
0x1BA8 (early 6.0);
0x1C28 (late 6.0)
UCHAR CpuVendor;
6.0 only  
0x1BA9 (early 6.0);
0x1C29 (late 6.0);
0x3368 (6.1);
0x3C78 (6.2 to 6.3);
0x3CF8
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
UCHAR VendorString [0x0D];
3.51 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
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
UCHAR LogicalProcessorsPerPhysicalProcessor;
5.1 and higher  
0x1C3B (late 6.0);
0x337B (6.1);
0x3C8B (6.2 to 6.3);
0x3D0B
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
ULONG FeatureBits;
4.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
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

Given a GenuineIntel processor whose CpuType, i.e., familiy, 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. Typically of Windows, obtaining any sort of UpdateSignature was at first specific to Intel’s processors. 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
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 to 6.3);
0x3D28
ULONGLONG SpareField1;
late 5.2 to 6.0  
ULONGLONG RuntimeAccumulation;
6.1 only  
ULONG Stride;
6.2 only  
ULONG PrcbPad90 [2];
6.3 and higher  
0x3CAC (6.2)
ULONG PrcbPad90;
6.2 only  
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 Applicable 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
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 Applicable Versions
0x3E30 (6.2);
0x3E40 (6.3);
0x3EB0
ULONG PrcbPad91 [1];
6.2 only
ULONG PrcbPad91 [0x0B];
6.3 and higher

Appended For Windows Vista

Offset Definition Applicable Versions Remarks
0x1ED0 (early 6.0);
0x1F38 (late 6.0);
0x3468 (6.1);
0x3E34 (6.2);
0x3E74 (6.3);
0x3EF4
KDPC DpcWatchdogDpc;
6.0 and higher  
0x1EF0 (early 6.0);
0x1F58 (late 6.0);
0x3488 (6.1);
0x3E58 (6.2);
0x3E98 (6.3);
0x3F18
KTIMER DpcWatchdogTimer;
6.0 and higher  
0x1F18 (early 6.0);
0x1F80 (late 6.0);
0x34B0 (6.1)
PVOID WheaInfo;
6.0 to 6.1 next at 0x3F04
0x1F1C (early 6.0);
0x1F84 (late 6.0);
0x34B4 (6.1)
PVOID EtwSupport;
6.0 to 6.1 next at 0x3F08
0x1F20 (early 6.0);
0x1F88 (late 6.0);
0x34B8 (6.1)
SLIST_HEADER InterruptObjectPool;
6.0 to 6.1 next at 0x3F10
0x1F28 (early 6.0);
0x1F90 (late 6.0);
0x34C0 (6.1);
0x3E80 (6.2);
0x3EC0 (6.3);
0x3F40
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
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
PVOID VirtualApicAssist;
late 6.0 and higher  
0x1FA0 (late 6.0);
0x34D0 (6.1);
0x3E90 (6.2);
0x3ED0 (6.3);
0x3F50
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
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
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
KAFFINITY PackageProcessorSet;
6.0 only  
KAFFINITY_EX PackageProcessorSet;
6.1 and higher  
0x3EE0 (6.2);
0x3F20 (6.3);
0x3FA0
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
ULONG PrcbPad91 [1];
6.1 only  
ULONG ScanSiblingMask;
6.2 only next at 0x3F2C
KSHARED_READY_QUEUE *SharedReadyQueue;
6.3 and higher  
0x3FA8
ULONG SharedQueueScanOwner;
10.0 and higher  
0x1F94 (early 6.0);
0x2000 (late 6.0);
0x353C (6.1);
0x3EF8 (6.2);
0x3F28 (6.3);
0x3FAC
ULONG CoreProcessorSet;
6.0 and higher last member in 6.0

Inserted For Windows 8

Offset Definition Applicable Versions Remarks
0x3F2C (6.3);
0x3FB0
ULONG ScanSiblingMask;
6.3 and higher previously at 0x3EF4
0x3F30 (6.3);
0x3FB4
ULONG LLCMask;
6.3 and higher  
0x3F34 (6.3);
0x3FB8
ULONG CacheProcessorMask [5];
6.3 and higher previously at 0x3EE0
0x3EFC (6.2);
0x3F48 (6.3);
0x3FCC
ULONG ScanSiblingIndex;
6.2 and higher  
0x3F00
ULONG LLCLevel;
6.2 only  
0x3F04 (6.2);
0x3F4C (6.3);
0x3FD0
PVOID WheaInfo;
6.2 and higher previously at 0x3430
0x3F08 (6.2);
0x3F50 (6.3);
0x3FD4
PVOID EtwSupport;
6.2 and higher previously at 0x34B4
0x3F10 (6.2);
0x3F58 (6.3);
0x3FD8
SLIST_HEADER InterruptObjectPool;
6.2 and higher previously at 0x34B8
0x3F18 (6.2);
0x3F60 (6.3);
0x3FE0
ULONG PrcbPad92 [8];
6.2 only  
ULONG SharedReadyQueueOffset;
6.3 only  
ULONG PrcbPad92 [3];
10.0 and higher  
0x3F64
ULONG PrcbPad92 [2];
6.3 only  
0x3F6C (6.3);
0x3FEC
ULONG PteBitCache;
6.3 and higher  
0x3F70 (6.3);
0x3FF0
ULONG PteBitOffset;
6.3 and higher  
0x3F74 (6.3);
0x3FF4
ULONG PrcbPad93;
6.3 and higher  
0x3F38 (6.2);
0x3F78 (6.3);
0x3FF8
PROCESSOR_PROFILE_CONTROL_AREA *ProcessorProfileControlArea;
6.2 and higher  
0x3F3C (6.2);
0x3F7C (6.3);
0x3FFC
PVOID ProfileEventIndexAddress;
6.2 and higher  

Appended For Windows 7

Offset Definition Applicable Versions
0x3540 (6.1);
0x3F40 (6.2);
0x3F80 (6.3);
0x4000
KDPC TimerExpirationDpc;
6.1 and higher

Windows 7 appends numerous performance counters for synchronisation.

Offset Definition Applicable 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 Applicable Versions
0x3F60 (6.2);
0x3FA0 (6.3);
0x4020
SYNCH_COUNTERS SynchCounters;
6.2 and higher
0x4018 (6.2);
0x4058 (6.3);
0x40D8
FILESYSTEM_DISK_COUNTERS FsCounters;
6.2 and higher

TO BE DONE

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

Appended For Windows 8

Offset Definition Applicable Versions Remarks
0x4034 (6.2);
0x4074 (6.3);
0x40F4
KENTROPY_TIMING_STATE EntropyTimingState;
6.2 and higher last member in 6.2

Appended For Windows 8.1

Offset Definition Applicable Versions Remarks
0x419C (6.3);
0x421C
PVOID IsrStack;
6.3 and higher  
0x41A0 (6.3);
0x4220
KINTERRUPT *VectorToInterruptObject [0xD0];
6.3 and higher  
0x44E0 (6.3);
0x4560
SINGLE_LIST_ENTRY AbSelfIoBoostsList;
6.3 and higher  
0x44E4 (6.3);
0x4564
SINGLE_LIST_ENTRY AbPropagateBoostsList;
6.3 and higher  
0x44E8 (6.3);
0x4568
KDPC AbDpc;
6.3 and higher last member in 6.3

The VectorToInterruptObject array is for what the kernel starts out regarding as unexpected interrupts, i.e., 0x30 to 0xFF.

Appended For Windows 10

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

Offset Definition Applicable Versions
0x4588
IOP_IRP_STACK_PROFILER IoIrpStackProfilerCurrent;
10.0 and higher
0x45DC
IOP_IRP_STACK_PROFILER IoIrpStackProfilerPrevious;
10.0 and higher
0x4630
KTIMER_EXPIRATION_TRACE TimerExpirationTrace [0x10];
10.0 and higher
0x4730
ULONG TimerExpirationTraceCount;
10.0 and higher
0x4734
PVOID ExSaPageArray;
10.0 and higher
0x4738
ULONG PrcbPad100 [10];
10.0 and higher

TO BE DONE

Offset Definition Applicable Versions
0x4760
KSHARED_READY_QUEUE LocalSharedReadyQueue;
10.0 and higher
0x4894
UCHAR PrcbPad95 [0x0C];
10.0 and higher

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

Offset Definition Applicable Versions
0x48A0
REQUEST_MAILBOX *Mailbox;
10.0 and higher
0x48A4
UCHAR PrcbPad [0x3C];
10.0 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 to send requests.

Offset Definition Applicable Versions Remarks
0x48E0
REQUEST_MAILBOX RequestMailbox [ANYSIZE_ARRAY];
10.0 and higher last member in 10.0