Geoff Chappell - Software Analyst
The LOADER_PARAMETER_BLOCK (formally _LOADER_PARAMETER_BLOCK) is the structure through which the kernel and HAL learn the initialisation data that was gathered by the loader.
Historically, and for most practical purposes even in recent Windows versions, there is only ever the one instance of this structure. It is prepared by the loader as its means of handing over to the kernel. When the loader calls the kernel’s initialisation routine, the address of the loader block is the one argument. The kernel saves the address for a while in the exported variable KeLoaderBlock. At the end of the kernel’s initialisation, the structure gets freed and the variable gets cleared. During the system’s initialisation, however, knowledge of this structure can be very helpful when debugging.
The LOADER_PARAMETER_BLOCK is ancient, being already well established for Windows NT 3.1.
Microsoft is not known ever to have documented the LOADER_PARAMETER_BLOCK.
For many years, Microsoft’s names for the LOADER_PARAMETER_BLOCK members were known from type information in public symbol files for the kernel (and sometimes also the HAL) but only for occasional Windows versions: first for Windows 2000 SP3 and SP4, and then for all releases of Windows Vista and Windows 7. How the type information gets into the public symbols for some versions but not others is not known.
Then Windows 10 brought something new. For the original Windows 10 and its 1511 revision, the Windows Driver Kit (WDK) supplies a header file named arc.h which contains a C-language definition of the LOADER_PARAMETER_BLOCK. This appears to be Microsoft’s first formal disclosure of the structure’s layout. It comes with no conditional compilation blocks for accommodating earlier versions. As supplied, it is immediately useful only for programming that targets a specific release of Windows 10, yet doesn’t say so. Add that the header is beneath a subdirectory named “um”, presumably to mean user-mode, but that the LOADER_PARAMETER_BLOCK is long gone by the time any user-mode software gets to execute, and one might wonder if this structure’s definition was published by mistake.
Still, published it is. Then, just as that seemed to be the end of disclosure, perhaps forever, Microsoft’s names and types returned to the public symbol files for the kernel in the 1803 release of Windows 10.
At the other end of the timeline, type information for the LOADER_PARAMETER_BLOCK turns out to have been published by Microsoft even for version 4.0, just not in symbol files but instead in statically linked libraries. One is LIBCNTPR.LIB from the Device Driver Kit (DDK) for Windows NT 4.0. For no reason that is yet understood, this library’s archive of tidtable.obj has unusually much type information (42KB, against 8KB for the next most), including for such otherwise obscure types as the LOADER_PARAMETER_BLOCK. Though this type information predates the kernel for the original Windows NT 4.0 (12th July 1996 versus 5th October), it agrees with the structure’s use as known from inspection of the binary code.
Arguably more interesting is a library that Microsoft supplied with source code for the Dr. Watson sample in a Platform Software Development Kit (SDK) from January 1998. This library is named craShlib.Lib—yes, with this mixture of upper and lower case—and was built on 12th September 1997. Its archived object files have type information for each of the processor architectures that Microsoft supported at the time. Type information for the x86 LOADER_PARAMETER_BLOCK is from 24th August 1997. This is months later than the build date for the Windows NT 4.0 SP3 kernel (11th May 1997) but more than a year earlier than SP4 (13th October 1998). From inspection of these kernels, it looks like type information from this library can be taken as reliable for Windows NT 4.0 SP3. Presumably, this library was distributed with some range of SDK editions, perhaps with type information to find for more versions.
With the archaeology broadened to type information in statically linked libraries as far afield as the SDK—which, remember, is for user-mode programming and thus nothing that anyone would ordinarily think relevant to a structure that isn’t present even for most kernel-mode execution—it turns out that type information for the LOADER_PARAMETER_BLOCK was pubilshed in CLFSMGMT.LIB for all 32-bit versions since Windows Vista and all 64-bit versions since Windows 8. For no reason yet known, this type information is gone from this library in the SDK for Version 2004.
Perhaps because the LOADER_PARAMETER_BLOCK is accessible through an exported variable and is vital as shared data between the loader, kernel and HAL, it was highly stable for many Windows versions, certainly in comparison with other undocumented structures. Version 6.0, however, reworked the loading of Windows, and then each change even of the minor version number brings a change of size:
Version | Size (x86) | Size (x64) |
---|---|---|
3.10 to early 4.0 (before SP3) | 0x64 | |
late 4.0 to 5.2 | 0x68 | 0xC8 |
6.0 | 0x7C | 0xE8 |
6.1 | 0x88 | 0xF0 |
6.2 | 0xA0 | 0x0118 |
6.3 | 0xAC | 0x0128 |
10.0 to 1709 | 0xBC | 0x0148 |
1803 to 2004 | 0xC8 | 0x0160 |
Some of these changes in size overstate the variability. As far as concerns the structure’s own members, the growth within version 4.0 and again from 6.2 to 6.3 involves no additions, removals or rearrangements, just growth within the member that was at the time the structure’s last. Version 6.0 brought a straightforward appending of one member. The change for Version 1803 is similar but of three members. Such growth only at the end allows for backwards compatibility. A new loader can prepare the structure in its new layout but safely pass it to an old kernel. This was indeed depended on for multi-boot configurations in the days when the one NTLDR in the root directory of the bootable partition might load the kernel of any earlier Windows version (if not of a later one).
Version 6.0 separated the booting of Windows into a boot manager, e.g., BOOTMGR, and a boot loader, e.g., WINLOAD. The boot manager selects which of the installed Windows versions (or different configurations) to proceed with. Each Windows version provides its own boot loader. This, not the boot manager, is what prepares the LOADER_PARAMETER_BLOCK and it only has to do so for the matching kernel. Backwards compatibility is no longer an issue for the structure. Changes for versions 6.1, 6.2 and 10.0 are not just from growing at the end: members are inserted and removed without regard for continuity. Notably, version 6.1 inserted version numbers and a size at the structure’s very start, surely to provide some easy and reliable means for future kernels to validate that the structure received from the loader is plausibly what that kernel expects. This defence was apparently thought important enough to warrant its own bug-check, LOADER_BLOCK_MISMATCH (0x0100).
The following table of offsets, names, types for the LOADER_PARAMETER_BLOCK is from the published C-language definition in ARC.H for the original and 1511 releases of Windows 10. For these and other versions, the layout is confirmed or augmented from type information in public symbols and libraries, if available, as described above. Names, types and offsets for all other versions are something of a guess from assuming continuity except where inspection of the loader or kernel shows that members have come and gone.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x00 | 0x00 |
ULONG OsMajorVersion; |
6.1 and higher |
0x04 | 0x04 |
ULONG OsMinorVersion; |
6.1 and higher |
0x08 | 0x08 |
ULONG Size; |
6.1 and higher |
0x0C | 0x0C |
ULONG Reserved; |
6.1 to 10.0 |
ULONG OsLoaderSecurityVersion; |
1511 and higher | ||
0x00 (3.10 to 6.0); 0x10 |
0x00 (5.2 to 6.0); 0x10 |
LIST_ENTRY LoadOrderListHead; |
all |
0x08 (3.10 to 6.0); 0x18 |
0x10 (5.2 to 6.0); 0x20 |
LIST_ENTRY MemoryDescriptorListHead; |
all |
0x10 (3.10 to 6.0); 0x20 |
0x20 (5.2 to 6.0); 0x30 |
LIST_ENTRY BootDriverListHead; |
all |
0x28 | 0x40 |
LIST_ENTRY EarlyLaunchListHead; |
6.2 and higher |
0x30 | 0x50 |
LIST_ENTRY CoreDriverListHead; |
6.2 and higher |
0x38 | 0x60 |
LIST_ENTRY CoreExtensionsDriverListHead; |
10.0 and higher |
0x40 | 0x70 |
LIST_ENTRY TpmCoreDriverListHead; |
10.0 and higher |
0x18 (3.10 to 6.0); 0x28 (6.1); 0x38 (6.2 to 6.3); 0x48 |
0x30 (5.2 to 6.0); 0x40 (6.1); 0x60 (6.2 to 6.3); 0x80 |
ULONG_PTR KernelStack; |
all |
0x1C (3.10 to 6.0); 0x2C (6.1); 0x3C (6.2 to 6.3); 0x4C |
0x38 (5.2 to 6.0); 0x48 (6.1); 0x68 (6.2 to 6.3); 0x88 |
ULONG_PTR Prcb; |
all |
0x20 (3.10 to 6.0); 0x30 (6.1); 0x40 (6.2 to 6.3); 0x50 |
0x40 (5.2 to 6.0); 0x50 (6.1); 0x70 (6.2 to 6.3); 0x90 |
ULONG_PTR Process; |
all |
0x24 (3.10 to 6.0); 0x34 (6.1); 0x44 (6.2 to 6.3); 0x54 |
0x48 (5.2 to 6.0); 0x58 (6.1); 0x78 (6.2 to 6.3); 0x98 |
ULONG_PTR Thread; |
all |
0x48 (6.2 to 6.3); 0x58 |
0x80 (6.2 to 6.3); 0xA0 |
ULONG KernelStackSize; |
6.2 and higher |
0x28 (3.10 to 6.0); 0x38 (6.1); 0x4C (6.2 to 6.3); 0x5C |
0x50 (5.2 to 6.0); 0x60 (6.1); 0x84 (6.2 to 6.3); 0xA4 |
ULONG RegistryLength; |
all |
0x2C (3.10 to 6.0); 0x3C (6.1); 0x50 (6.2 to 6.3); 0x60 |
0x58 (5.2 to 6.0); 0x68 (6.1); 0x88 (6.2 to 6.3); 0xA8 |
PVOID RegistryBase; |
all |
0x30 (3.10 to 6.0); 0x40 (6.1); 0x54 (6.2 to 6.3); 0x64 |
0x60 (5.2 to 6.0); 0x70 (6.1); 0x90 (6.2 to 6.3); 0xB0 |
CONFIGURATION_COMPONENT_DATA *ConfigurationRoot; |
all |
0x34 (3.10 to 6.0); 0x44 (6.1); 0x58 (6.2 to 6.3); 0x68 |
0x68 (5.2 to 6.0); 0x78 (6.1); 0x98 (6.2 to 6.3); 0xB8 |
PSTR ArcBootDeviceName; |
all |
0x38 (3.10 to 6.0); 0x48 (6.1); 0x5C (6.2 to 6.3); 0x6C |
0x70 (5.2 to 6.0); 0x80 (6.1); 0xA0 (6.2 to 6.3); 0xC0 |
PSTR ArcHalDeviceName; |
all |
0x3C (3.10 to 6.0); 0x4C (6.1); 0x60 (6.2 to 6.3); 0x70 |
0x78 (5.2 to 6.0); 0x88 (6.1); 0xA8 (6.2 to 6.3); 0xC8 |
PSTR NtBootPathName; |
all |
0x40 (3.10 to 6.0); 0x50 (6.1); 0x64 (6.2 to 6.3); 0x74 |
0x80 (5.2 to 6.0); 0x90 (6.1); 0xB0 (6.2 to 6.3); 0xD0 |
PSTR NtHalPathName; |
all |
0x44 (3.10 to 6.0); 0x54 (6.1); 0x68 (6.2 to 6.3); 0x78 |
0x88 (5.2 to 6.0); 0x98 (6.1); 0xB8 (6.2 to 6.3); 0xD8 |
PSTR LoadOptions; |
all |
0x48 (3.10 to 6.0); 0x58 (6.1); 0x6C (6.2 to 6.3); 0x7C |
0x90 (5.2 to 6.0); 0xA0 (6.1); 0xC0 (6.2 to 6.3); 0xE0 |
NLS_DATA_BLOCK *NlsData; |
all |
0x4C (3.10 to 6.0); 0x5C (6.1); 0x70 (6.2 to 6.3); 0x80 |
0x98 (5.2 to 6.0); 0xA8 (6.1); 0xC8 (6.2 to 6.3); 0xE8 |
ARC_DISK_INFORMATION *ArcDiskInformation; |
all |
0x50 (3.10 to 6.0); 0x60 (6.1) |
0xA0 (5.2 to 6.0); 0xB0 (6.1) |
PVOID OemFontFile; |
3.10 to 6.1 |
0x54 (3.10 to 6.0) | 0xA8 (5.2 to 6.0) |
SETUP_LOADER_BLOCK *SetupLoaderBlock; |
3.10 to 6.0 |
0x58 (3.10 to 6.0); 0x64 (6.1); 0x74 (6.2 to 6.3); 0x84 |
0xB0 (5.2 to 6.0); 0xB8 (6.1); 0xD0 (6.2 to 6.3); 0xF0 |
ULONG Spare1; |
3.10 to 4.0 |
LOADER_PARAMETER_EXTENSION *Extension; |
5.0 and higher | ||
0x5C (3.10 to 6.0); 0x68 (6.1); 0x78 (6.2 to 6.3); 0x88 |
0xB8 (5.2 to 6.0); 0xC0 (6.1); 0xD8 (6.2 to 6.3); 0xF8 |
union { /* changing members, see below */ } u; |
all |
0x68 (6.0); 0x74 (6.1); 0x84 (6.2 to 6.3); 0x94 |
0xC8 (6.0); 0xD0 (6.1); 0xE8 (6.2 to 6.3); 0x0108 |
FIRMWARE_INFORMATION_LOADER_BLOCK FirmwareInformation; |
6.0 and higher |
0xBC | 0x0148 |
PSTR OsBootstatPathName; |
1803 and higher |
0xC0 | 0x0150 |
PSTR ArcOSDataDeviceName; |
1803 and higher |
0xC4 | 0x0158 |
PSTR ArcWindowsSysPartName; |
1803 and higher |
The only OsLoaderSecurityVersion that is yet known is 1. The briefly published ARC.H shows that Microsoft defines this symbolically as OSLOADER_SECURITY_VERSION_CURRENT.
The name Spare1, known with certainty from type information for version 4.0, invites the question of whether there was a Spare0 or Spare2. The latter looks impossible: it surely would follow Spare1, not precede it, but all eight bytes that follow in version 4.0 have the same use all the way back to version 3.10. If a Spare0 ever was defined, it may have been in the space that is later given to SetupLoaderBlock. This certainly is defined for version 4.0, but unlike for all other members no use is yet known of it in version 3.10 (by NTLDR, SETUPLDR or the kernel).
The three new members for the 1803 release elaborate the ancient ArcBootDeviceName and ArcHalDeviceName members, now adding ARC paths for the Boot Status Data (BSD) log and for whatever devices are given by the osdatadevice and windowssyspart boot options.
The u union is of small structures for each of the different processor architectures that were supported at the time. This support was wider in the early versions, but which processors had explicit support in this union—or even if it was originally a union—cannot be known from any inspection of only the x86 and x64 binaries, and is therefore left alone for these notes. It is even a guess that there must always have been an I386 member. Other members are known with certainty only for the incomplete range of versions for which type information is available:
Definition | Versions |
---|---|
I386_LOADER_BLOCK I386; |
all |
MIPS_LOADER_BLOCK Mips; |
4.0 |
ALPHA_LOADER_BLOCK Alpha; |
4.0, 5.0, 6.0 |
PPC_LOADER_BLOCK Ppc; |
4.0 |
ARM_LOADER_BLOCK Arm; |
6.2 and higher |
IA64_LOADER_BLOCK Ia64; |
5.0, 6.0 to 6.2 |