Geoff Chappell - Software Analyst
SKETCH OF HOW RESEARCH MIGHT CONTINUE AND RESULTS BE PRESENTED
From the beginning, Windows separates virtual address space into a low part for user mode and a high part for kernel mode. The user space varies with the process and can be accessed by both user-mode and kernel-mode code. The system space is mostly global but is accessible only to code that executes in kernel mode. This still serves as a rough understanding, despite elaborations. An important one is that the division was originally equal, with 2GB each, but was made configurable as long ago as Windows NT 4.0 SP3 so that the user space could be as large as 3GB. In 32-bit Windows, then, system address space typically starts at 0x80000000 but may start as high as 0xC0000000.
In 64-bit Windows the processor architecture itself separates the address space since the mapping to physical addresses through page tables does not support a continuous 64-bit address space but instead defines low and high 47-bit address spaces in which an address can be valid only if the high 17 bits are either all clear or all set. Still, the user and system spaces are the low and high halves: the 128TB user space runs up to and including 0x00007FFF`FFFFFFFF and the 128TB system address space starts at 0xFFFF8000`00000000.
Historically, the Windows kernel divided the system virtual address space at startup into different regions for different uses. One notable region has long been hard-coded: all known versions of 32-bit Windows prepare the top level of the page-table mapping so that 0xC0000000 is the address of an array of page table entries for the whole 4GB of linear address space. Other regions are not so much hard-coded as preset: their start addresses are computed during initialisation and are then kept in internal variables whose values never can change. In this way, for instance, versions 3.10 to 5.0 have the paged pool at 0xE1000000 and versions 3.10 to 5.2 have the system cache at 0xC1000000.
Not until Windows Vista does the 32-bit system address space get allocated to the different uses dynamically. The allocation unit is the amount of address space that is mapped through one page directory entry, thus 4MB in what was once the ordinary page-table mapping but 2MB if the kernel uses the slightly different mapping that allows for Physical Address Extension (PAE).
For each allocation unit of system address space, the type of use, if any, is represented by the MI_SYSTEM_VA_TYPE enumeration. Before version 10.0, the 32-bit kernel’s record of the types for successive allocation units from the start of the system address space upwards is kept as an internal variable named MiSystemVaType. Windows 10 reworked this as the SystemVaType member of an MI_VISIBLE_STATE structure that is in turn the Vs member of an MI_SYSTEM_INFORMATION structure that is the type of an internal variable named MiState.
Microsoft’s names for MI_SYSTEM_VA_TYPE itself and for its possible values are known from public symbol files for the kernel, starting with Windows Vista:
Value (x86) | Value (x64) | Name | Versions | Remarks |
---|---|---|---|---|
0x00 | 0x00 | MiVaUnused | 6.0 and higher | |
0x01 | 0x01 | MiVaSessionSpace | 6.0 and higher | |
0x02 | 0x02 | MiVaProcessSpace | 6.0 and higher | |
0x03 | 0x03 | MiVaBootLoaded | 6.0 and higher | |
0x04 | 0x04 | MiVaPfnDatabase | 6.0 and higher | |
0x05 | 0x05 | MiVaNonPagedPool | 6.0 and higher | |
0x06 | 0x06 | MiVaPagedPool | 6.0 and higher | |
0x07 | 0x07 | MiVaSpecialPool | 6.0 only | |
MiVaSpecialPoolPaged | 6.1 and higher | |||
0x08 | 0x08 | MiVaSystemCache | 6.0 and higher | |
0x09 | 0x09 | MiVaSystemPtes | 6.0 and higher | |
0x0A | 0x0A | MiVaHal | 6.0 and higher | |
0x0B | 0x0B | MiVaSessionGlobalSpace | 6.0 and higher | |
0x0C | 0x0C | MiVaDriverImages | late 6.0 and higher | |
0x0D (6.1 to 1809) | 0x0D (6.1 to 1809) | MiVaSpecialPoolNonPaged | 6.1 to 1809 | |
0x0E (6.2 to 1709) | MiVaPagedProtoPool | 6.2 to 1709 | ||
0x0F (1709); 0x0E (1803 to 1809); 0x0D |
0x0E (1709 to 1809); 0x0D |
MiVaSystemPtesLarge | 1709 and higher | previously after MiVaMaximumType |
0x10 (1709); 0x0F (1803 to 1809); 0x0E |
0x0F (1709 to 1809); 0x0E |
MiVaKernelStacks | 1709 and higher | |
0x0F | 0x0F | MiVaSecureNonPagedPool | 2004 and higher | |
0x0C (early 6.0); 0x0D (late 6.0); 0x0E (6.1); 0x0F (6.2 to 1703); 0x11 (1709); 0x10 (1803 to 1809); 0x0F (1903); 0x10 |
0x0C (early 6.0); 0x0D (late 6.0); 0x0E (6.1 to 1703): 0x10 (1803 to 1809); 0x0F (1903); 0x10 |
MiVaMaximumType | 6.0 and higher | |
0x10 (6.3 to 1703) | 0x0F (6.3 to 1703) | MiVaSystemPtesLarge | 6.3 to 1703 | next at 0x0F and 0x0E |
For most versions, MiVaMaximumType ends the enumeration and acts conventionally not as a defined type but as counting the defined types. Addition of MiVaSystemPtesLarge for Windows 8.1 disturbed this. Whether this was by intention or oversight, it persisted for years: only when the 1709 release of Windows 10 added MiVaKernelStacks was MiVaMaximumType restored to counting all the defined types.