Post-reset Longhorn builds (roughly 5048 through early 5308) often die on modern hardware with BlInitializeLibrary failed 0xc0000017 — STATUS_NO_MEMORY — even when the machine clearly has RAM to spare. The message is misleading; the real story is stranger. Here’s how that was chased down, and a small patch for bootmgr / winload.exe that fixes it.
On the machine, it looks like this:
BlInitializeLibrary failed 0xc0000017
The boot library couldn’t grab the low conventional memory it wanted — true in a narrow sense. But machines that fail this way usually have hundreds of kilobytes of usable RAM below 1 MiB according to the BIOS. The problem isn’t “not enough RAM.” It’s “the bootloader looked at the map and decided none of that RAM was available for use.”
Summary
Early post-reset boot applications (bootmgr and winload.exe) enumerate system memory with BIOS INT 15h AX=E820h, then re-tag each entry into an internal descriptor type that the boot allocator understands.
That classifier has a latent ACPI 3.0 bug. If the E820h extended-attribute dword (ExtAttr) is non-zero, the real Type field is ignored and the entry is forced to an internal “reserved / unclassified” tag (0xF0000003). Only when ExtAttr == 0 does the code apply the real mapping — Type=1 usable becomes 0xD0000001 below 1 MiB or 0xF0000001 above, and so on.
ACPI 3.0 made a 24-byte E820h record the standard form and defined bit 0 of ExtAttr as “enabled / valid.” Virtually every modern BIOS sets that bit on every entry. On those machines the classifier therefore discards all usable memory at ingestion time. The allocator later fails with STATUS_NO_MEMORY because its pool of usable descriptors is empty.
The fix is to stop treating non-zero ExtAttr as a reason to discard Type. In the common on-disk shape that means NOP-ing the two-byte ja short that diverts to the forced-reserved path; an earlier build needs a one-byte jz → jmp instead. Microsoft removed the ExtAttr check themselves by build 5308.17.
Why write an E820h enumerator?
Before diving into theories about low-memory heaps, paging, or BCD allocation, the first question was simpler: what does the firmware actually report?
BlInitializeLibrary failed 0xc0000017 only tells you the boot library couldn’t get memory. It doesn’t tell you whether:
- the machine truly has too little conventional RAM below 1 MiB for the bootloader’s heap,
- the BIOS map is fragmented into unusable scraps,
- or something else is wrong and the whole “low memory” story is a false lead.
A real-mode tool that calls the same INT 15h AX=E820h interface the bootloader uses answers that directly. The E820h enumerator dumps every descriptor the BIOS returns — base, length, type, and a friendly decode of ACPI ExtAttr when present — then lists each usable range below 1 MiB and the single largest contiguous block.
That dump does two jobs:
- Narrow the problem. If the map shows almost no Type=1 memory below 1 MiB, the failure is environmental — firmware, option ROMs, or prior boot stages ate conventional memory — and patching classification won’t help.
- Rule out a false lead. If the map shows a large contiguous Type=1 block below 1 MiB and the bootloader still fails with
STATUS_NO_MEMORY, the bug isn’t “no RAM.” It’s “the bootloader isn’t seeing the RAM the BIOS reported.” That’s the signal that points at misclassification rather than genuine shortage.
On the Dell test machine, an early run of the tool produced this map. The output format has since been cleaned up (see below), but the numbers are what mattered:
E820 memory map (verdict threshold = 00010000 bytes)
Base=---------------- Len=---------------- T Name XAttr
Base=0000000000000000 Len=000000000009D400 Type=1 Usable XAttr=00000001
Base=000000000009D400 Len=0000000000002C00 Type=2 Reserved XAttr=00000001
Base=00000000000E0000 Len=0000000000020000 Type=2 Reserved XAttr=00000001
Base=0000000000100000 Len=00000000774A6000 Type=1 Usable XAttr=00000001
Base=00000000775A6000 Len=0000000000048000 Type=4 ACPI NVS XAttr=00000001
Base=00000000775EE000 Len=000000000000B000 Type=3 ACPI Reclaim XAttr=00000001
Base=00000000775F9000 Len=0000000000003000 Type=4 ACPI NVS XAttr=00000001
Base=00000000775FC000 Len=0000000000024000 Type=2 Reserved XAttr=00000001
Base=0000000077620000 Len=0000000000009000 Type=4 ACPI NVS XAttr=00000001
Base=0000000077629000 Len=0000000000003000 Type=2 Reserved XAttr=00000001
Base=000000007762C000 Len=0000000000005000 Type=4 ACPI NVS XAttr=00000001
Base=0000000077631000 Len=0000000000020000 Type=2 Reserved XAttr=00000001
Base=0000000077651000 Len=0000000000043000 Type=4 ACPI NVS XAttr=00000001
Base=0000000077694000 Len=000000000016C000 Type=1 Usable XAttr=00000001
Base=0000000079C00000 Len=0000000002400000 Type=2 Reserved XAttr=00000001
Base=00000000E0000000 Len=0000000010000000 Type=2 Reserved XAttr=00000001
Base=00000000FED1C000 Len=0000000000004000 Type=2 Reserved XAttr=00000001
Base=00000000FF000000 Len=0000000001000000 Type=2 Reserved XAttr=00000001
--
Entries : 18
E820 supported : yes
Low mem (<1MiB) : Usable=629 KiB LargestFreeBlock=629 KiB
Bootmgr heap fit (>=00010000 contig <1MiB) : YES
So the firmware was offering 629 KiB of contiguous usable RAM below 1 MiB, with ExtAttr bit 0 set (XAttr=00000001) on every entry. Plenty of low memory, ExtAttr present everywhere, and bootmgr still claimed there was nothing to allocate. That contradiction is what made the ExtAttr misclassification theory worth chasing — instead of treating low-RAM as a dead end or writing the whole thing off as a red herring.
Early analysis had focused on the consumer of low memory — the path that tries to commit a below-1 MiB block for boot-library state when /MAXMEM= / 3GB options are absent. That path is real, and later bootmgr builds (around 5308) do stop needing a dedicated low-memory heap by building early page tables and mapping memory more freely. But it wasn’t the root cause of the Dell failure: you can’t allocate from a pool that was empty before the allocator even ran.
How the bootloader reads E820h
Both bootmgr and winload.exe issue INT 15h AX=E820h with a fixed real-mode buffer at 0x30040 in the classic 24-byte ACPI 3.0 layout:
Base @ 0x30040 (8 bytes)
Length @ 0x30048 (8 bytes)
Type @ 0x30050 (4 bytes)
ExtAttr @ 0x30054 (4 bytes)
The buffer is zeroed before each call. The BIOS is asked for 24 bytes (ECX = 24) with signature 'SMAP'. The continuation cookie lives in EBX across iterations, as usual.
After each successful call, a classifier turns the raw BIOS entry into an internal memory-descriptor type. The same shared boot-library logic is compiled into both PE images.
The bug
Simplified classification — same shape in bootmgr and winload:
cmp ds:30054h, ecx ; ExtAttr vs 0 (ecx is 0)
mov eax, ds:30050h ; eax = Type
pop ebx
ja short force_reserved ; ExtAttr != 0 → type = 0xF0000003, Type ignored
jb short type_table ; dead: CF never set by cmp mem, 0
cmp eax, 4
ja short force_reserved ; Type > 4 → reserved
; Type table only reached when ExtAttr == 0:
; Type=1, end <= 1MB → 0xD0000001 (usable low)
; Type=1, end > 1MB → 0xF0000001 (usable high)
; Type=2 → 0xF0000003 (reserved)
; Type=3 → 0xF0000008 (ACPI reclaim)
; Type=4 → 0xF0000009 (ACPI NVS)
The trap is the first ja. If ExtAttr != 0, the real Type is never inspected. Everything becomes 0xF0000003.
That tag is a dead end. The allocator matches usable descriptors by exact type equality against 0xD0000001 / 0xF0000001. There’s no later reclassification or promotion path — in the whole bootmgr binary, 0xF0000003 only appears at the classify site and one gap-filler. Once an entry is mis-tagged, it’s gone for good.
Modern firmware sets ExtAttr bit 0 on every entry. So on the Dell box (and any similar ACPI-3.0-era BIOS):
- BIOS reports Type=1 usable ranges correctly, with ExtAttr=1.
- The enumerator (which trusts Type and only counts ExtAttr-Enabled Type=1 toward the low-memory summary) reports 629 KiB usable.
- bootmgr collapses every entry to
0xF0000003. - The low-memory pool is empty.
BlInitializeLibraryreturns0xC0000017.
That’s the entire bug. Downstream paging and probe machinery is real, but it’s irrelevant if the usable pool is empty at the source.
Same bug in winload.exe
bootmgr and winload.exe are separate PE files. Patching one does not fix the other.
winload’s classifier uses the same buffer layout, the same SMAP/E820h setup, the same constants, and the same broken ExtAttr branch. On the analyzed 5112 build, the instruction sequence around that branch is even byte-identical to bootmgr’s — both were compiled from the same shared source for this function. 0xC0000017 is live in winload’s memory subsystem as well, so without a winload patch you can clear bootmgr and still die later in the OS loader for the same reason.
The patch
The intent is simple: always fall through to Type-based classification. Treat ExtAttr as irrelevant for type selection — which matches modern ACPI practice, where bit 0 means “this entry is valid,” not “ignore Type.”
Common shape (builds 5098.0 – 5308.6)
NOP the two-byte ja short <forced-reserved> immediately after the ExtAttr comparison:
77 xx → 90 90
Offsets are build-specific. The patcher never hardcodes them; it searches for a unique byte signature and refuses to touch the file if the pattern is missing or not unique.
Earlier shape (build 5048.0 winload)
5048 predates the Type-table scheme. Its largest-range E820h summary only trusts Type==1 when ExtAttr==0. The fix is a one-byte change:
74 xx (jz short <mark usable>) → EB xx (jmp short <mark usable>)
Same effect: ExtAttr is ignored; Type=1 is trusted unconditionally.
Later shape (builds 5284.0 – 5308.6)
The E820h entry is copied onto the stack before classification, so ExtAttr is compared via a stack local instead of ds:30054h. Still a ja short to the forced-reserved path; still NOP-ed to two 0x90s. Different signature, same intent.
Fixed natively
As of winload build 5308.17, the classifier only ever reads the Type dword and never loads ExtAttr. That build already behaves as if patched and needs no binary edit.
Applying the fix
The easiest path is to grab pre-patched binaries from dl.longhorn.ms/post-reset-boot-patch and drop them onto your boot media in place of the stock bootmgr / winload.exe.
To patch your own copies, use the PowerShell patcher. It:
- accepts one or more files (
bootmgr,winload.exe, or any name — detection is by signature, not filename) - tries four known on-disk shapes covering free x86 builds from 5048.0 through 5308.6
- writes
<file>.origon first patch (never overwrites an existing backup) - is idempotent (already-patched files are reported and left alone)
- supports
-WhatIf/-Confirm - refuses to patch if a signature is missing or matches more than once
# Defaults: bootmgr and winload.exe next to the script
.\Patch-E820ExtAttr.ps1
# Specific files
.\Patch-E820ExtAttr.ps1 -Path .\bootmgr, .\winload.exe
# Dry run
.\Patch-E820ExtAttr.ps1 -Path .\bootmgr -WhatIf
Not covered: AMD64 winload variants, and debug/checked builds of either architecture — those produce different compiler output, and against them the script reports “signature not found” and exits rather than guessing. Also not covered: EFI booting. EFI enumerates available memory through EFI boot services rather than BIOS INT 15h AX=E820h, so this ExtAttr classifier path isn’t involved.
Rollback is just restoring the backup:
Copy-Item .\bootmgr.orig .\bootmgr -Force
Copy-Item .\winload.exe.orig .\winload.exe -Force
Replace every copy of bootmgr and winload.exe, not just the one on the USB root. That includes:
- the ISO / media root
- inside
boot.wim(all indexes) - inside
install.wim(all indexes)
A partial replace is a common failure mode: setup still pulls an unpatched winload.exe from a WIM index, or a later boot stage reintroduces stock bootmgr, and you get the same 0xC0000017 again. Editing a working copy that never gets written into those locations produces the classic “I patched it and nothing changed” experience.
Diagnosing other machines
Before assuming this is your bug, run the E820h enumerator on the affected hardware (or in the same firmware environment you boot from). Build with NASM (nasm -f bin E820.ASM -o E820.COM), then:
E820 ; Base/Length as 64-bit hex (default)
E820 /H ; Base/Length in human-readable units
E820 > MAP.TXT ; redirect full output to a file
E820 /? ; usage
Current hex-mode output looks like this (illustrative):
E820 memory map
Base Length T Name XAttr
0000000000000000 000000000009FC00 1 Usable Enabled
000000000009FC00 0000000000000400 2 Reserved Enabled
...
--
Entries : 12
E820 supported : yes
Low-memory usable ranges (<1MiB):
Base=00000000 Len=636 KiB <== largest
Base=000A0000 Len=3 KiB
Low mem (<1MiB) : Usable=639 KiB LargestFreeBlock=636 KiB
With /H, Base and Length are unit-scaled (bytes/KiB/MiB/GiB) instead of raw hex. The XAttr column is a friendly decode of the ACPI 3.0 extended-attribute dword when the BIOS returned a 24-byte entry (Enabled / Disabled, optionally +NV, +Slow, +Err). If no entry supplies ExtAttr, that column is omitted entirely. The low-memory summary only counts Type=1 ranges with ExtAttr Enabled (when present), clips them to below 1 MiB, lists each clipped range (marking the largest), then prints aggregate Usable and LargestFreeBlock.
How to read the result:
- Little or no Type=1 below 1 MiB, or LargestFreeBlock too small — genuine low-memory shortage / fragmentation; different problem.
- Large Type=1 below 1 MiB, no XAttr column (20-byte responses only) — stock bootmgr may work; ExtAttr path not exercised.
- Large Type=1 below 1 MiB and XAttr shows
Enabledon entries — classic conditions for this bug; patch both bootmgr and winload.
It runs under MS-DOS, FreeDOS, a Windows 9x DOS box, or 32-bit NTVDM. On 64-bit Windows there’s no 16-bit subsystem, so use DOSBox or a FreeDOS boot USB / floppy.
After the patch
With ExtAttr ignored:
- Type=1 below 1 MiB becomes
0xD0000001again - Type=1 above 1 MiB becomes
0xF0000001 - the boot library can acquire its low-memory block
- boot proceeds past the
0xC0000017failure
Scope
- 5048.0 — winload needs the 1-byte
jz→jmpform - 5098.0 – 5231.2 — both binaries, “with pop ebx” signature
- 5259.0 – 5270.9 — both binaries, “without pop ebx” signature
- 5284.0 – 5308.6 — both binaries, stack-local ExtAttr compare
- 5308.17+ — fixed in source; no patch required
Conclusion
The post-reset bootloader’s 0xC0000017 on modern firmware isn’t a shortage of conventional memory. It’s a twenty-year-old interaction between ACPI 3.0’s ExtAttr bit and a classifier that treated any non-zero ExtAttr as “throw this entry away.” A two-byte (or one-byte) patch restores Type-based classification; Microsoft eventually removed the ExtAttr check entirely around 5308.17.
The E820h enumerator made diagnosis possible: by showing that low memory was available and ExtAttr was set, it ruled out a true memory shortage and pointed straight at the classifier. Patch both bootmgr and winload.exe — they’re separate binaries with the same bug.
Credits
- Claude Sonnet 5 — static analysis to determine the cause of the bug, and creation of the E820h enumerator
- Grok 4.5 — this write-up
- Mainnn (BetaWiki) — E820h output from a known-affected system, and testing the patch to confirm it worked