Geoff Chappell, Software Analyst
The name KPRCB stands for (Kernel) Processor Control Block. The kernel keeps one for each logical processor, embedded in the 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.
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 and gs registers in 32-bit and 64-bit Windows respectively. Its Prcb or CurrentPrcb member, again in 32-bit and 64-bit Windows respectively, points to the KPRCB without depending on it to be embedded in the KPCR. Beware, though, that this is the address of the KPRCB for the processor that the thread was running 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. As an aside, I suspect that more than a few things go very slightly wrong in kernel-mode Windows because this point is insufficiently respected.
Finding the KPRCB for an arbitrary processor is also easy on 64-bit Windows but is practically impossible on 32-bit Windows. The kernel does it by keeping an array of pointers to the KPRCB structures for all processors, but the address of this array is of course not exported. (That it is named KiProcessorBlock can be useful to know when debugging with symbols.) The 64-bit kernel exports a function, named KeQueryPrcbAddress, that looks up the array to locate the KPRCB for a given processor, but the closest that the 32-bit kernel lets code outside the kernel get to an arbitrary processor’s KPRCB is that a few functions copy from that KPRCB.
Any access that kernel-mode code running on one processor does obtain to a KPRCB for another processor should be used very carefully. Some such use may be safe while holding one or another sort of lock. Some seems likely to be intended as read-only to other processors: much of the point to having per-processor data that can change while Windows runs is that if the data is updated only by code that’s running on that processor then the updating can be done both quickly and safely without concern for synchronisation. As an aside, I suspect that more than a few things go very slightly wrong in kernel-mode Windows because of insufficient care when accessing per-processor data from a different processor.
Neither the KPRCB nor its containing KPCR is formally documented, but the existence of both is disclosed in header files from as far back as the Device Driver Kit (DDK) for Windows NT 3.51. Even in that DDK, the NTDDK.H file provides C-language definitions for what comments say is an “architecturally defined section of the PRCB” for the Alpha processor. The Windows 2000 DDK did the same for the IA64. Both were gone by the time of the Windows Driver Kit (WDK) for Windows Vista. The KPRCB for the i386 and amd64 was always left as opaque. For these, NTDDK.H presented an “architecturally defined section” of the KPCR, showing a pointer to the KPRCB, but giving no definition.
Ever since Windows 2000 SP3, however, the practical equivalent of C-language definitions in headers has been readily available as type information in symbol files. Symbol files for the kernel have type information for the whole KPRCB. Starting with Windows XP, these turn up in other symbol files too, notably for NTDLL.DLL—yes, from user-mode, with no access to the KPRCB—and ACPI.SYS. Intriguingly, though the HAL is the primary external user of the KPRCB, type information does not appear in symbol files for HALs until 64-bit Windows Vista and 32-bit Windows 7, and only then for a reduced KPRCB. The reductions turn up in other symbol files and are presumably for that part of the KPRCB that counts as architecturally defined.
In a recent development, a header file named NTOSP.H in the Enterprise edition of the Windows Driver Kit (WDK) for the 1511 release of Windows 10 supplies C-language definitions of the KPRCB for the i386, amd64 and ARM processors (and also provides an inline function, named KeGetCurrentPrcb, that does the segment-based lookup described above). This appears to be Microsoft’s first formal disclosure of the structure’s layout for these processors. It is, of course, just of the architecturally defined section. Even for this, its value is lessened by having no conditional compilation blocks for accommodating earlier versions—and, worse, by a comment that states the section “will not change from version to version of NT” despite giving a layout that is immediately applicable only to programming that targets Windows 10 specifically. Since the same comment about not changing is in the NTDDK.H from the Windows NT 3.51 DDK, it is plausibly just an age-old statement of intention that never was honoured but also has never got cleaned up.
Being shared between the kernel and HAL, the “architecturally defined section” of the KPRCB, and even in some ways the whole structure, is more stable than it might be if internal to one module. Yet it plainly has been intended all along as the highly variable portion of what the kernel and HAL from the same build of Windows share about each processor. Some of the variability can be sensed just from the changing size:
|Version||Whole Structure||Architecturally Defined Section|
|Size (x86)||Size (x64)||Size (x86)||Size (x64)|
|early 5.2 (before Windows Server 2003 SP1)||0x0DD0|
|late 5.2 (Windows Server 2003 SP1 and higher)||0x0EC0||0x2480||0x0520||0x0670|
|early 6.0 (before windows Vista SP1)||0x1F98||0x3A20|
|late 6.0 (Windows Vista SP1 and higher)||0x2008||0x3B20||0x05A0|
|10.0 to 1607||0x4900||0x6900|
See that the size changes not just with each version but even within some versions—especially now that all versions are Windows 10. What is not clear just from the sizes is that the change from one version to another is not just by growth. Members have frequently been inserted, deleted and moved around—even from one end of the structure to the other and even after being moved into the “architecturally defined section” that’s supposed not to “change from version to version”.
Do not suppose for a minute that the KPRCB has its own version numbering to account for the variation within Windows versions. The KPRCB does have members named MajorVersion and MinorVersion, and these are at the same offsets in all known x86 versions, and in all known x64 versions until 2017. In such a highly variable structure, the stable positions of two members that might be consulted to check the version might be commendable. For the KPRCB, however, the version numbering has been entirely pointless: these two members are reliably both 1 for all known versions from Windows NT 3.10 at least to the 1803 release of Windows 10.
It ought not need to be said explicitly, but the KPRCB is certainly not a structure to use in any programming outside of the few components for which Microsoft intends it. Yet it is just as certainly one of the most important structures in all of Windows. Large amounts of the detail of how Windows really works down at the nitty-gritty, especially for making the most of the CPU, has at least some trace in the KPRCB. Nobody could credibly claim kernel-level knowledge of Windows without knowing some detail of the KPRCB.
The KPRCB differs enough between 32-bit and 64-bit Windows that detailed layouts are better presented separately as KPRCB (i386) and KPRCB (amd64).