Geoff Chappell - Software Analyst
The SYSTEM_BASIC_INFORMATION structure (formally _SYSTEM_BASIC_INFORMATION ) is what a successful call to ZwQuerySystemInformation or NtQuerySystemInformation produces in its output buffer when given the information classes SystemBasicInformation (0x00), SystemEmulationBasicInformation (0x3E) or SystemNativeBasicInformation (0x72).
The 32-bit kernel fills the SYSTEM_BASIC_INFORMATION exactly the same for all three information classes. The x64 builds treat SystemEmulationBasicInformation differently. This allows 64-bit code, notably in WOW64.DLL, to obtain the slightly different information that’s to be seen by a process’s 32-bit code.
The primary use by Microsoft of the SystemBasicInformation case of NtQuerySystemInformation is to support the KERNEL32 function GetSystemInfo, specifically to obtain values for the following members of that function’s SYSTEM_INFO structure:
If these are all that is wanted, then use GetSystemInfo instead.
The SYSTEM_BASIC_INFORMATION structure is defined in WINTERNL.H from the Software Development Kit (SDK). The definition there provides only for NumberOfProcessors, with padding to put it at the right offset. Documentation of NtQuerySystemInformation describes the SystemBasicInformation case as returning the number of processors in the system, and directs that GetSystemInfo be used instead.
This note is instead concerned with what might reasonably be called the structure’s true definition. Since at least WIndows 8—and, for specificity, certainly in the original Windows 10—this definition that Microsoft itself works with is in an unpublished header named ntexapi.h.
Microsoft does publish the practical equivalent of a C-language definition as type information in symbol files—not for the kernel, where the structure is prepared, nor even for low-level user-mode DLLs that interpret the structure, but for a handful of higher-level DLLs that might ordinarily be thought very distant from any involvement with the structure. Perhaps only by oversight, but starting as long ago as Windows 8, Microsoft’s downloadable packages of public symbols have included a smattering of private symbol files and these continue to be available through the public symbol server. Precisely which DLLs have the type information varies between versions. COMBASE.DLL is among the more reliable nowadays. Disclosure in symbol files for URLMON.DLL stopped for the 1803 release of Windows 10 but is specially notable because of this DLL’s origins in Internet Explorer and thence for the strong suggestion that Microsoft’s programmers of Internet Explorer had access to more details of low-level Windows programming than Microsoft publishes for wider use (including by the programmers of competing web browsers).
Type information for the structure has also seeped out at the other end of the Windows timeline, though not in symbol files but in statically linked libraries: GDISRVL.LIB from the Device Driver Kit (DDK) for Windows NT 3.51; and SHELL32.LIB from the DDK for Windows NT 4.0.
The SYSTEM_BASIC_INFORMATION is 0x2C or 0x40 bytes in 32-bit and 64-bit Windows, respectively, in all known versions. For 32-bit Windows, the structure from 64-bit Windows is defined separately as SYSTEM_BASIC_INFORMATION64. This starts at least with version 6.2 but may be older. No definition is known of a SYSTEM_BASIC_INFORMATION32 for 64-bit Windows.
Offset (x86) | Offset (x64) | Definition |
---|---|---|
0x00 | 0x00 |
ULONG Reserved; |
0x04 | 0x04 |
ULONG TimerResolution; |
0x08 | 0x08 |
ULONG PageSize; |
0x0C | 0x0C |
ULONG NumberOfPhysicalPages; |
0x10 | 0x10 |
ULONG LowestPhysicalPageNumber; |
0x14 | 0x14 |
ULONG HighestPhysicalPageNumber; |
0x18 | 0x18 |
ULONG AllocationGranularity; |
0x1C | 0x20 |
ULONG_PTR MinimumUserModeAddress; |
0x20 | 0x28 |
ULONG_PTR MaximumUserModeAddress; |
0x24 | 0x30 |
KAFFINITY ActiveProcessorsAffinityMask; |
0x28 | 0x38 |
CHAR NumberOfProcessors; |
The Reserved member is not so much reserved as obsolete. It is originally the source of the dwOemId member of the user-mode SYSTEM_INFO. In that structure, dwOemId has been marked by a comment as “Obsolete field...do not use” ever since version 3.51 repurposed its low word as the wProcessorArchitecture. Whether the original higher-level interpretation was ever modelled by a different name for Reserved is not known. Even the version 3.10 kernel sets Reserved to zero.
The TimerResolution is what kernel-mode programmers know as the time increment in the sense of its being what the KeQueryTimeIncrement function returns. It is the period, in the usual kernel-mode time-keeping unit of 100ns, of an idealised timer interrupt. Through the course of the kernel’s execution, the interrupt period is allowed to vary but the kernel still keeps count of idealised timer ticks that have this TimerResolution.
Initially, it happened to be true that the basic information is constant—given that the kernel has completed its initialisation—for as long as the system keeps running. When Windows 2000 provided the MmAddPhysicalMemory function, the NumberOfPhysicalPages and the HighestPhysicalPageNumber became capable of seemingly instantaneous change: even the SYSTEM_BASIC_INFORMATION is among the many reports that callers can rely on only as having been correct at some indeterminate time before receipt.
The MinimumUserModeAddress and MaximumUserModeAddress are intended as the lowest and highest addresses that the kernel may ever allow for user-mode access. It need not be that these addresses or any between them are usable, only that all addresses outside the range are not usable and never will be. Possible exclusion at either or both the start and end of what might otherwise be thought of as the platform’s theoretical allowance for user-mode address space is plainly architectural. Kernel-mode programmers have always had this minimum and maximum defined for them in the DDK as MM_LOWEST_USER_ADDRESS and MM_HIGHEST_USER_ADDRESS. User-mode programmers learn of them as basic system information.
The MaximumUserModeAddress is ordinarily from the exported variable MmHighestUserAddress and is constant after the kernel has completed its initialisation, but what’s produced as the MaximumUserModeAddress for the 64-bit SystemEmulationBasicInformation depends on who asks. The necessarily 64-bit caller does not seek the maximum address for its own user-mode access. For this it has plain old SystemBasicInformation or even the new SystemNativeBasicInformation. What’s wanted instead is the maximum for 32-bit code in the same process, but this depends on what executable image started the 32-bit process. The function gets its answer as one byte less than the HighestUserAddress in the current process’s EPROCESS.
In version 6.1 and higher, the ActiveProcessorsAffinityMask and NumberOfProcessors also depend on who asks. The ActiveProcessorsAffinityMask is only of the active processors in the current processor group and NumberOfProcessors counts only those active processors. The precise intention to the different handling for the 64-bit SystemEmulationBasicInformation must presently wait for this note’s next revision.