Geoff Chappell - Software Analyst
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.
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.
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!
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.
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.
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.
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.
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.
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.
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 |
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.
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 |
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.
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.
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.
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 |
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.
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.
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 |
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 |
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.
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 |
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 |
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 |
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 |
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 |
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 |
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.
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 |