KUSER_SHARED_DATA

The KUSER_SHARED_DATA structure defines the layout of a data area that the kernel places at a pre-set address for sharing with user-mode software. The original intention seems to have been to enable user-mode software to get frequently needed global data, notably the time, without the overhead of calling kernel mode.

Access

Of course, kernel-mode and user-mode access is through different addresses, and the user-mode address provides only for reading the data, not writing.

The pre-set address for access from kernel mode is defined symbolically in WDM.H as KI_USER_SHARED_DATA. It helps when debugging to remember that this is 0xFFDF0000 or 0xFFFFF780`00000000, respectively, in 32-bit and 64-bit Windows. Also defined is a convenient symbol, SharedUserData, which casts this constant address to a KUSER_SHARED_DATA pointer.

The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows. The only formal definition among headers in the WDK or the Software Development Kit (SDK) is in assembly language headers: KS386.INC from the WDK and KSAMD64.INC from the SDK both define MM_SHARED_USER_DATA_VA for the user-mode address (and USER_SHARED_DATA for the kernel-mode). Presumably, this much reduced exposure of the user-mode address is because the intended use is by low-level modules of Microsoft’s to support API functions. Well-written user-mode software would call these API functions, certainly if documented, rather than inspect the KUSER_SHARED_DATA directly.

Documentation Status

Though the KUSER_SHARED_DATA is not formally documented, a C-language definition has been available in NTDDK.H ever since the Device Driver Kit (DDK) for Windows 2000. It is tabulated here for two reasons. First, the programmer who debugs low-level Windows code or the reverse engineer who studies Windows is likely to encounter references to this structure’s members, typically through the pre-set address, and may usefully be spared from calculating the offsets of members from the formal definition.

Second, but having very much the greater importance, the structure changes between Windows versions but this is not tracked in Microsoft’s headers. Admittedly, the changes look to be close to inconsequential to higher-level user-mode code—indeed, to any code much above NTDLL. Close to inconsequential, however, is not ignorably inconsequential. The difference sometimes matters, and has even affected security. Cases exist where things have slipped into the KUSER_SHARED_DATA but might better not have been exposed so easily to user-mode software and notably not to malware.

For instance, through many versions of 32-bit Windows before Windows 8, this structure’s involvement in user-mode calls to kernel mode had as a side-effect that all such calls go through one or two very predictable locations, thus greatly aiding software (sadly not limited just to malware) that seeks to intercept those calls. At first, this structure’s SystemCall member held the code to call. Later, all calls to kernel mode go through the exported NTDLL functions KiFastSystemCall and KiIntSystemCall after passing through SystemCall as a pointer. Another example that was removed for Windows 8 is that the protectiveness of Address Space Layout Randomization (ASLR), as far as it concerned predicting the run-time addresses of known sites in NTDLL, was reduced by this structure’s SystemDllNativeRelocation and SystemDllWowRelocation members. Examples such as these are pretty serious blunders, especially given the context that both came about as implementation details for features that were announced at the time as increasing security. However much oversight and even outright mistakes are inevitable in system software, some record is better kept for history.

Layout

Among relatively large structures, the KUSER_SHARED_DATA is unusual in that it has exactly the same layout for 32-bit and 64-bit Windows. This is because the one instance is simultaneously accessible by both 32-bit and 64-bit user-mode code in all processes, and it’s desired that 32-bit user-mode code can run unchanged on both 32-bit and 64-bit Windows.

Large tracts of the structure either do not change or barely change between Windows versions. Changes to the KUSER_SHARED_DATA have come mostly from growing at the end. Yet there have been changes within the structure, including to move members from one offset to another between builds, no matter that a comment in NTDDK.H says “The layout itself cannot change since this structure has been exported in ntddk, ntifs.h, and nthal.h for some time.” The reality is that the structure has changed enough that presenting the structure over a range of versions is certainly not simple! The following sizes are known (with caveats that follow the table):

Version Size
3.50 0x2C
3.51 0x0238
early 4.0 (before Windows NT 4.0 SP3) 0x02B4
mid 4.0 (Windows NT 4.0 SP3) 0x02BC
late 4.0 (Windows NT 4.0 SP4 and higher) 0x02D4
5.0 0x02D8
early 5.1 (before Windows XP SP2) 0x0320
late 5.1 (Windows XP SP2 and higher) 0x0338
early 5.2 (before Windows Server 2003 SP1) 0x0330
late 5.2 (Windows Server 2003 SP1 and higher) 0x0378
6.0 0x03B8
6.1 to 6.3 0x05F0
10.0 0x0708

These sizes, and the offsets, types and names in the tables that follow, are from Microsoft’s symbol files for either or both of the kernel and NTDLL for Windows 2000 SP3 and higher. Put aside that type information somehow survived in two statically linked libraries that Microsoft published with the DDKs for Windows NT 3.51 and Windows NT 4.0, and what’s known of Microsoft’s names and types for early versions is instead inferred from what use NTOSKRNL, NTDLL and KERNEL32 are seen to make of the shared data at its known addresses. Even the size is not known with certainty for version 3.50 and for some service packs of version 4.0 since memory for the KUSER_SHARED_DATA is allocated (and zeroed) by the loader as a whole page.

Original (Windows NT 3.50)

Some variations are as simple as a change of type or name, as shown by the structure’s very first member. The ordinary-seeming Windows API function named GetTickCount used to be implemented as simply as a 64-bit multiplication of the volatile 32-bit TickCountLow (being the low 32 bits of the kernel’s 64-bit tick count) by the constant TickCountMultiplier and then a 64-bit shift right by 24 bits as a speedy way to convert from kernel-mode tick counts in whatever unit of measurement the kernel uses to user-mode tick counts in milliseconds. That the user-mode tick count can be read by executing a handful of instructions in user mode without the expense of transitions to and from kernel mode is perhaps the original motivation of the shared data area. However, using only the low 32 bits of the kernel’s tick count means that the user-mode tick count in milliseconds resets to zero not only when it wraps around as a DWORD every 49 days or so but also whenever the low 32 bits of the kernel’s count wrap around. This second wrap-around is a whole extra problem, even if its real-world occurrence is made very much less likely by needing to leave Windows to run for approximately 2 years. When Microsoft got round to fixing it for Windows Server 2003, the KUSER_SHARED_DATA needed the whole 64 bits of the kernel’s count. It got this as a new member at what was then the end of the structure—see offset 0x0320—not by replacing the old and moving anything else. Thus did TickCountLow turn to TickCountLowDeprecated:

Offset Definition Versions Remarks
0x00
ULONG volatile TickCountLow;
3.50 to 5.1  
ULONG TickCountLowDeprecated;
5.2 and higher  
0x04
ULONG TickCountMultiplier;
3.50 and higher  
0x08
KSYSTEM_TIME volatile InterruptTime;
3.50 and higher  
0x14
KSYSTEM_TIME volatile SystemTime;
3.50 and higher  
0x20
KSYSTEM_TIME volatile TimeZoneBias;
3.50 and higher last member in 3.50

Getting the time from user mode seems to have exercised Microsoft more than a little in the very early years of Windows. The exact chronology is not known, given incomplete holdings of version 3.50, but roughly contemporaneous with the introduction of the KUSER_SHARED_DATA for direct access to the kernel-mode tick count via TickCountLow is the introduction of a dedicated interrupt, number 0x2A, for getting the user-mode tick count—still by calling the kernel but with nothing like the overhead of going through interrupt 0x2E (which handles the generality of system calls, of which NtGetTickCount is just one).

Much as the GetTickCount function does little more than read the kernel’s latest updates of the TickCount, the GetSystemTimeAsFileTime function does nothing but get the kernel’s adjustments to the SystemTime.

Appended For Windows NT 3.51

Microsoft’s next choice of data to expose to user mode at fixed addresses gives our first example of members that still have this exposure despite having no known user-mode use. The x86 and x64 kernels set both ImageNumberLow and ImageNumberHigh to IMAGE_FILE_MACHINE_I386 (0x014C) and IMAGE_FILE_MACHINE_AMD64 (0x8664), respectively. At first, that was the whole of the kernel’s involvement with these members: they were set just to be read from user mode. When the KERNEL32 function CreateProcessW in versions 3.51 to 4.0 and then CreateProcessInternalW in versions 5.0 to 5.2 inspects the executable file that is proposed for the new process, it checks that the machine type from the file’s PE header lies between ImageNumberLow and ImageNumberHigh inclusive (or, in the wow64 builds only, is equal to IMAGE_FILE_MACHINE_I386). Version 6.0 moved this checking from KERNEL32 to the kernel: no user-mode use of these members is known in any later version.

Offset Definition Versions Remarks
0x2C
USHORT ImageNumberLow;
3.51 and higher  
0x2E
USHORT ImageNumberHigh;
3.51 and higher  
0x30
WCHAR NtSystemRoot [0x0104];
3.51 and higher last member in 3.51

The NtSystemRoot is the path to the Windows directory. Before version 3.51 it was discovered from user mode by calling the NtQuerySystemInformation function and giving SystemPathInformation (0x04) as the information class. That the path could instead be read from shared memory was sufficiently important that for several versions more, up to and including 5.0, calling the kernel function to get the path got a break to the kernel-mode debugger to tell the programmer

EX: SystemPathInformation now available via SharedUserData

Appended For Windows NT 4.0

Two additions for version 4.0 were re-implemented by Windows 2000 as the DriveMap and DriveType members of the DEVICE_MAP structure. Microsoft’s names for them in the KUSER_SHARED_DATA are preserved for archaeologists in type information in the SHELL32 import library from the DDK for Windows NT 4.0. The kernel sets a bit in the DosDeviceMap to indicate that the corresponding DOS drive is defined: bit 0 for A, 1 for B, etc, and that the drive type is set in the corresponding element of the DosDeviceDriveType array. In Windows NT 4.0, the KERNEL32 function GetLogicalDrives is nothing but a retrieval of the DosDeviceMap from the KUSER_SHARED_DATA. The drive types are the same as returned by the KERNEL32 function GetDriveType. Learning a little about DOS drives without having to call the kernel may have seemed important once but it arguably was from the start an efficiency that was taken too far, and Windows 2000 did away with it:

Offset Definition Versions
0x0238
ULONG DosDeviceMap;
4.0 only
ULONG MaxStackTraceDepth;
5.0 and higher
0x023C
ULONG CryptoExponent;
4.0 and higher
0x0240
ULONG TimeZoneId;
4.0 and higher
0x0244 (4.0)
UCHAR DosDeviceDriveType [0x20];
4.0 only

The DosDeviceMap was soon repurposed (not that any use of its replacement, MaxStackTraceDepth, is yet known for any version) but the relatively substantial space taken by the DosDeviceDriveType array was left as reserved. It then shifts and shrinks as portions get redefined for use in Windows Server 2003 and then in Windows 8 until it disappears when fully used for Windows 10.

Offset Definition Versions Remarks
0x0244
ULONG LargePageMinimum;
5.2 and higher  
0x0248
ULONG AitSamplingValue;
6.2 and higher previously ULONG volatile at 0x03C8
0x024C
ULONG AppCompatFlag;
6.2 and higher previously ULONG volatile at 0x03CC
0x0250
ULONGLONG RNGSeedVersion;
6.2 and higher  
0x0258
ULONG GlobalValidationRunLevel;
6.2 and higher  
0x025C
LONG volatile TimeZoneBiasStamp;
6.2 and higher  
0x0244 (5.0 to 5.1);
0x0248 (5.2 to 6.1);
0x0260
ULONG Reserved2 [8];
5.0 to 5.1 from removing DosDeviceDriveType
ULONG Reserved2 [7];
5.2 to 6.1  
ULONG Reserved2;
6.2 to 6.3  
ULONG NtBuildNumber;
10.0 and higher  

What Windows 10 did with what remained of Reserved2 was to flesh out an area that version 4.0 added for easy, well-defined information about the kernel and the processors that it runs on. This addition for version 4.0 has been stable except that Windows 8 squeezed a new member into previously undefined space that had been left by an alignment requirement.

Offset Definition Versions Remarks
0x0264
NT_PRODUCT_TYPE NtProductType;
4.0 and higher  
0x0268
BOOLEAN ProductTypeIsValid;
4.0 and higher  
0x0269
BOOLEAN Reserved0 [1];
6.2 and higher  
0x026A
USHORT NativeProcessorArchitecture;
6.2 and higher  
0x026C
ULONG NtMajorVersion;
4.0 and higher  
0x0270
ULONG NtMinorVersion;
4.0 and higher  
0x0274
BOOLEAN ProcessorFeatures [0x40];
4.0 and higher last member in early 4.0

The ProcessorFeatures array supports the documented kernel-mode and user-mode functions ExIsProcessorFeaturePresent and IsProcessorFeaturePresent. These functions provide an abstracted report of which processor features the kernel has enabled for use.

Additions for Windows NT 4.0 Service Packs

All known symbol files that define KUSER_SHARED_DATA have it that the members at offsets 0x02B4 and 0x02B8 are reserved. Perhaps they were at first named MmHighestUserAddress and MmSystemRangeStart, these being the names of the kernel variables that they are initialised from. Both variables were introduced for Windows NT 4.0 SP3 in conjunction with the BOOT.INI switch, named /3GB, that enabled the expansion of the user address space from 2GB to 3GB.

Offset Definition Versions Remarks
0x02B4
ULONG Reserved1;
mid 4.0 and higher  
0x02B8
ULONG Reserved3;
mid 4.0 and higher last member in mid 4.0

No use of Reserved1 to learn the highest user address is yet known. This boundary had long been discoverable from user mode as the MaximumUserModeAddress member of the SYSTEM_BASIC_INFORMATION, such as filled in by the NtQuerySystemInformation function when given SystemBasicInformation (0x00) as the information class.

In contrast, Reserved3 did see action. Where earlier versions of the KERNEL32 functions GlobalLock and LocalLock reject a handle for being a system address, they compare against the hard-coded 0x80000000. In the applicable Windows NT 4.0 service packs, they compare against Reserved3 instead. This didn’t last long, however. The lowest system address becomes discoverable through the SystemRangeStartInformation (0x32) information class in version 5.0. KERNEL32 changes to this in version 5.0 and no use of Reserved3 to learn the lowest system address is known in any later version. It seems at least plausible that the name Reserved3 actually dates from then. This has the merit of possibly explaining the numbering: Reserved1, with no user-mode use, was named first; Reserved2 and Reserved3 lost their user-mode use concurrently and were named in ascending order.

Reserved or not, both the preceding members are still set by the kernel at least until the 1803 release of Windows 10.

Offset Definition Versions
0x02BC
ULONG volatile TimeSlip;
5.0 and higher
0x02C0
ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture;
5.0 and higher
0x02C4
ULONG AltArchitecturePad [1];
6.1 and higher
ULONG BootId;
10.0 and higher
0x02C8
LARGE_INTEGER SystemExpirationDate;
5.0 and higher

No use is known of the preceding 0x14 bytes until Windows 2000. Space left by the 8-byte alignment of SystemExpirationDate after AlternativeArchitecture got formally defined as padding in Windows 7 and eventually got used in Windows 10.

Offset Definition Versions Remarks
0x02BC (late 4.0) unaccounted 0x14 bytes late 4.0 only  
0x02D0
ULONG SuiteMask;
late 4.0 and higher last member in late 4.0

Late builds of Windows NT 4.0 set one byte of the SuiteMask, presumably because not enough product suites were yet supported for a second byte. The SuiteMask was introduced concurrently with the GetVersionEx function’s acceptance of an OSVERSIONINFOEX structure to fill. For that function filling that structure, the suite mask is the low 16 bits of 32 bits read from here.

Appended For Windows 2000

Though all known C-language definitions of KdDebuggerEnabled in NTDDK.H have it as a BOOLEAN, it is in fact a pair of bit flags.

Offset Definition Versions Remarks
0x02D4
BOOLEAN KdDebuggerEnabled;
5.0 and higher last member in 5.0

Appended For Windows XP and Windows Server 2003

The KUSER_SHARED_DATA ends at offset 0x02D8 in version 5.0 but the last three bytes are just for alignment and are undefined. One byte in this space then got defined by the builds of version 5.1 starting with Windows XP SP2 and of version 5.2 starting with Windows Server 2003 SP1. This NXSupportPolicy can validly range only from 0x00 to 0x03. Windows 8 redefined it as a two-bit field and squeezed some more into the byte (and formally defined the rest of the space left by alignment as reserved).

Offset Definition Versions
0x02D5
UCHAR NXSupportPolicy;
late 5.1;
late 5.2 to 6.1
union {
    UCHAR MitigationPolicies;
    struct {
        /*  bit fields, follow link  */
    };
};
6.2 and higher
0x02D6
UCHAR Reserved6 [2];
6.2 and higher

Actual extension of the KUSER_SHARED_DATA for version 5.1 begins with a set of members that are retained forever.

Offset Definition Versions
0x02D8
ULONG volatile ActiveConsoleId;
5.1 and higher
0x02DC
ULONG volatile DismountCount;
5.1 and higher
0x02E0
ULONG ComPlusPackage;
5.1 and higher
0x02E4
ULONG LastSystemRITEventTickCount;
5.1 and higher
0x02E8
ULONG NumberOfPhysicalPages;
5.1 and higher
0x02EC
BOOLEAN SafeBootMode;
5.1 and higher

The ComPlusPackage member caches a registry value for the KERNEL32 function GetComPlusPackageInstallStatus to return to whoever’s interested. The kernel reads it as REG_DWORD data from the Enable64Bit value in HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework, defaulting to zero. Microsoft documents 1 as COMPLUS_ENABLE_64BIT.

Version 6.1 found some use for more space left by alignment. By version 6.2 this was not nearly enough for what was wanted, and so the space returned to being reserved.

Offset Definition Versions Remarks
0x02ED
union {
    UCHAR TscQpcData;
    struct {
        UCHAR TscQpcEnabled : 1;        // 0x01
        UCHAR TscQpcSpareFlag : 1;      // 0x02
        UCHAR TscQpcShift : 6;          // 0xFC
    };
};
6.1 only next as 16-bit union at 0x03C6
UCHAR VirtualizationFlags;
1607 and higher  
0x02EE (6.1);
0x02ED (6.2 to 1511);
0x02EE
UCHAR TscQpcPad [2];
6.1 only  
UCHAR Reserved12 [3];
6.2 to 1511  
UCHAR Reserved12 [2];
1607 and higher  

Version 5.1 defines one ULONG for TraceLogging but elaboration of support for tracing in version 6.0 made this member available for reuse:

Offset Definition Versions
0x02F0
ULONG TraceLogging;
5.1 to 5.2
union {
    ULONG SharedDataFlags;
    struct {
        /*  slightly changing bit fields, follow link  */
    };
};
6.0 and higher

Version 6.1 continues its programme of formally defining padding that follows a member because of alignment.

Offset Definition Versions
0x02F4
ULONG DataFlagsPad [1];
6.1 and higher

Use of the SYSENTER and SYSEXIT instructions for getting to and from kernel mode first had version 5.1 set aside the 32-byte SystemCall (below) as space in which the kernel assembles code. A rethink when Windows XP SP2 and Windows Server 2003 SP1 introduced Data Execution Prevention (DEP) meant that much of this space returned to being unused.

Offset Definition Versions Remarks
0x02F8
ULONGLONG Fill0;
early 5.1;
early 5.2
 
ULONGLONG TestRetInstruction;
late 5.1;
late 5.2 and higher
 
0x0300
ULONGLONG SystemCall [4];
early 5.1;
early 5.2
last member in early 5.1
ULONG SystemCall;
late 5.1;
late 5.2 to 6.1
 
LONGLONG QpcFrequency;
6.2 and higher  
0x0304 (late 5.1, late 5.2 to 6.1)
ULONG SystemCallReturn;
late 5.1;
late 5.2 to 6.1
 
0x0308
ULONG SystemCall;
1511 and higher  
0x030C
ULONG SystemCallPad0;
1511 and higher  
0x0308 (late 5.1, late 5.2 to 10.0);
0x0310
ULONGLONG SystemCallPad [3];
late 5.1;
late 5.2 to 10.0
 
ULONGLONG SystemCallPad [2];
1511 and higher  

The build of version 5.1 for Windows XP SP2 picks up the new TickCount that was added chronologically earlier for version 5.2 to fix a defect in the arithmetic of the GetTickCount function. However, this new tick count at offset 0x0320 has no known use in any build of version 5.1. It appears to be in the definition, as known from the symbol files for Windows XP SP2 and SP3, only because it’s on the way to the Cookie, which was introduced jointly for Windows XP SP2 and the version 5.2 for Windows Server 2003 SP1 to support the EncodeSystemPointer and DecodeSystemPointer functions.

Offset Definition Versions Remarks
0x0320
union {
    KSYSTEM_TIME volatile TickCount;
    ULONG64 volatile TickCountQuad;
};
late 5.1 to 6.0 last member in early 5.2
union {
    KSYSTEM_TIME volatile TickCount;
    ULONG64 volatile TickCountQuad;
    struct {
        ULONG ReservedTickCountOverlay [3];
        ULONG TickCountPad [1];
    };
};
6.1 and higher  
0x0330
ULONG Cookie;
late 5.1;
late 5.2 and higher
last member in late 5.1

No build of version 5.1 continues the KUSER_SHARED_DATA beyond the structure’s 8-byte alignment after the Cookie. The version 5.2 from Windows Server 2003 SP1 uses the alignment space to start a relatively large array of Wow64SharedInformation. When Windows Vista inserted the ConsoleSessionForegroundProcessId ahead of that array, it created the first example of a defined member (i.e., not reserved or for padding) that changes offsets between versions. The Wow64SharedInformation was reassigned for Windows 8, defining nine new members and a reservation. Windows 10 deleted two of the new members, thus creating two more examples of members that shift between versions, and started using the reservation.

Offset Definition Versions Remarks
0x0334
ULONG CookiePad [1];
6.1 and higher  
0x0338
LONGLONG ConsoleSessionForegroundProcessId;
6.0 and higher  
0x0334 (late 5.2);
0x0340
ULONG Wow64SharedInformation [0x10];
late 5.2 to 6.1 last member in late 5.2
ULONGLONG volatile TimeUpdateSequence;
6.2 only  
ULONGLONG TimeUpdateLock;
6.3 and higher  
0x0348
ULONGLONG BaselineSystemTimeQpc;
6.2 and higher  
0x0350
ULONGLONG BaselineInterruptTimeQpc;
6.2 and higher  
0x0358
ULONGLONG QpcSystemTimeIncrement;
6.2 and higher  
0x0360
ULONGLONG QpcInterruptTimeIncrement;
6.2 and higher  
0x0368 (6.2 to 6.3)
ULONG QpcSystemTimeIncrement32;
6.2 to 6.3  
0x036C (6.2 to 6.3)
ULONG QpcInterruptTimeIncrement32;
6.2 to 6.3  
0x0370 (6.2 to 6.3);
0x0368
UCHAR QpcSystemTimeIncrementShift;
6.2 and higher  
0x0371 (6.2 to 6.3);
0x0369
UCHAR QpcInterruptTimeIncrementShift;
6.2 and higher  
0x036A
USHORT UnparkedProcessorCount;
10.0 and higher  
0x036C
ULONG EnclaveFeatureMask [4];
1511 and higher  
0x037C
ULONG TelemetryCoverageRound;
1709 and higher  
0x0372 (6.2 to 6.3);
0x036C (10.0;
0x037C (1511 to 1703)
UCHAR Reserved8 [0x0E];
6.2 to 6.3  
UCHAR Reserved8 [0x14];
10.0 only  
ULONG Reserved8;
1511 to 1703  

Appended For Windows Vista

Offset Definition Versions Remarks
0x0380
USHORT UserModeGlobalLogger [8];
6.0 only  
USHORT UserModeGlobalLogger [16];
6.1 and higher  
0x0390
ULONG HeapTracingPid [2];
6.0 only  
0x0398
ULONG CritSecTracingPid [2];
6.0 only  
0x03A0
ULONG ImageFileExecutionOptions;
6.0 and higher  
0x03A4
ULONG LangGenerationCount;
6.1 and higher  
0x03A8
union {
    ULONGLONG AffinityPad;
    ULONG ActiveProcessorAffinity;
};
6.0 only  
ULONGLONG Reserved5;
6.1 only  
ULONGLONG Reserved4;
6.2 and higher  
0x03B0
ULONGLONG volatile InterruptTimeBias;
6.0 and higher last member in 6.0

Appended For Windows 7

Offset Definition Versions Remarks
0x03B8
ULONGLONG volatile TscQpcBias;
6.1 to 6.2  
ULONGLONG volatile QpcBias;
6.3 and higher  
0x03C0
ULONG volatile ActiveProcessorCount;
6.1 to 6.3  
ULONG ActiveProcessorCount;
10.0 and higher  
0x03C4
USHORT volatile ActiveGroupCount;
6.1 only  
UCHAR volatile ActiveGroupCount;
6.2 and higher  
0x03C5
UCHAR Reserved9;
6.2 and higher  
0x03C6
USHORT Reserved4;
6.1 only  
union {
    USHORT TscQpcData;
    struct {
        BOOLEAN volatile TscQpcEnabled;
        UCHAR TscQpcShift;
    };
};
6.2 only previously 8-bit union at 0x02ED
union {
    USHORT QpcData;
    struct {
        BOOLEAN volatile QpcBypassEnabled;
        UCHAR QpcShift;
    };
};
6.3 to 1607  
union {
    USHORT QpcData;
    struct {
        UCHAR volatile QpcBypassEnabled;
        UCHAR QpcShift;
    };
};
1709 and higher  

Version 1709 changes QpcBypassEnabled from a UCHAR that is intended to be either TRUE or FALSE to one whose meaning is taken in bits. Microsoft’s C-language definition in the contemporaneous WDK defines:

Offset Definition Versions Remarks
0x03C8 (6.1)
ULONG volatile AitSamplingValue;
6.1 only next without volatile at 0x0248
0x03CC (6.1)
ULONG volatile AppCompatFlag;
6.1 only next without volatile at 0x024C
0x03D0 (6.1)
ULONGLONG SystemDllNativeRelocation;
6.1 only  
0x03D8 (6.1)
ULONG SystemDllWowRelocation;
6.1 only  
0x03DC (6.1)
ULONG XStatePad [1];
6.1 only  
0x03C8
LARGE_INTEGER TimeZoneBiasEffectiveStart;
6.2 and higher  
0x03D0
LARGE_INTEGER TimeZoneBiasEffectiveEnd;
6.2 and higher  
0x03E0 (6.1);
0x03D8
XSTATE_CONFIGURATION XState;
6.1 and higher last member in 6.1 and higher

All growth of the KUSER_SHARED_DATA after Windows 7 is a side-effect of changes within the XSTATE_CONFIGURATION at the end.