LOADER_PARAMETER_BLOCK

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.

Availability

The LOADER_PARAMETER_BLOCK is ancient, being already well established for Windows NT 3.1.

Documentation Status

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.

Archaeology

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.

Variability

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).

Layout

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.

Processor-Specific Loader Blocks

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