This deep dive describes how LamBoot is built: the 10-phase boot flow, its memory and NVRAM models, the protocols it speaks to firmware and kernel, and the eight-layer dependency architecture that the codebase is mechanically held to. At v0.15.2 (June 2026) LamBoot is about 16,000 lines of Rust across 46 modules, producing a binary of about 650 KB on x86_64 (662,016 bytes measured) and about 570 KB on aarch64. It targets x86_64 and aarch64 UEFI, is licensed MIT OR Apache-2.0, and reads ext4, btrfs, and FAT natively, including on LVM, read in place.
Boot Flow
LamBoot executes a 10-phase boot sequence:
Phase 1: Health Assessment
Read previous BootState from NVRAM
If prev=Booting: increment crash counter (previous boot failed)
If prev=BootedOK/Fresh: reset crash counter
Set state=Booting, write timestamp
Set Boot Loader Interface variables (LoaderInfo, LamBootVersion)
Phase 2: Security Initialization
Read SecureBoot EFI variable
Check for ShimLock protocol (shim loaded?)
Log Secure Boot state: disabled / active+shim / active+direct
Initialize TPM context (check TCG2 protocol, tpm_present())
Phase 3: Mount ESP
Get LoadedImage protocol from our image handle
Get device handle from LoadedImage
Open SimpleFileSystem on device handle
Open volume root directory -> EspVolume
Phase 4: Load Policy
Read \EFI\LamBoot\policy.toml
Parse with section-aware TOML parser (qualified keys)
On failure: use defaults (4s timeout, threshold=2)
Measure config into TPM PCR 5
Phase 5: Load Filesystem Drivers
Scan \EFI\LamBoot\drivers\ for *.efi files
For each: LoadImage() + StartImage() (driver registers DriverBinding)
ConnectController(recursive=true) on ALL handles
New SimpleFileSystem handles now available for ext4/btrfs partitions
Phase 6: Enumerate Volumes
find_handles::<SimpleFileSystem>() -> all filesystem handles
Open each as EspVolume
Result: ESP + any newly accessible partitions
Phase 7: Discover Boot Entries
For each volume: scan /loader/entries/*.conf (BLS Type 1)
Parse each .conf file (14 fields, multi-value initrd/options)
Filter by architecture, apply policy allowlist/denylist
Sort by: bad-entries-last, sort-key, machine-id, version (UAPI.10)
ESP fallback: scan for Windows, UKI, GRUB, rEFInd, tools
Legacy distro scanning only if no BLS entries found
Deduplicate by path (BLS entries take precedence)
Phase 8: Crash Loop Check
If crash_counter >= crash_threshold:
Try fallback_order entries from policy
If no fallback found: fall through to menu
Phase 9: Interactive Menu
If GOP available: graphical menu (double-buffered framebuffer)
Render to off-screen Vec<BltPixel> buffer
Single BltOp::BufferToVideo per frame (~60 FPS)
Mouse + keyboard input
If no GOP: text console (SimpleTextOutput)
Numbered entries, 0-9 keys + arrow keys
Auto-boot on timeout (configurable, disabled for tool-only entries)
System actions always visible at bottom of menu:
F2: Reboot to Firmware Setup (sets OsIndications, cold resets)
F12: Cold reboot
If no bootable entries found: show recovery screen with system actions
If crash loop detected: disable auto-boot, wait for manual selection
Phase 10: Boot Handoff
Record boot entry to NVRAM (LamBootLastEntry, LoaderEntrySelected)
Write boot report to ESP (\EFI\LamBoot\reports\boot.json)
If boot-counted entry: decrement tries_left, rename .conf, set LoaderBootCountPath
Measure kernel cmdline into TPM PCR 12
For Linux: search ALL volumes (ESP + driver-exposed) for kernel/initrd
For Linux: read initrd, register LoadFile2 protocol (LINUX_EFI_INITRD_MEDIA_GUID)
Set kernel load options (command line)
start_image() -> kernel takes control
If kernel returns: mark_boot_success(), cleanup initrd handle
If menu returns error: reboot to firmware setup (safety net)
Memory Model
LamBoot uses UEFI Boot Services memory allocation:
global_allocatorfeature provides Rust'sallocvia UEFI pool allocation- Framebuffer:
Vec<BltPixel>of width * height (~8MB at 1920x1080) - Initrd:
Box<[u8]>leaked viaBox::into_raw()for stable addresses during LoadFile2 - Font: Terminus Bold bitmap fonts. 16px (8x16, 4KB) and 32px (16x32, 16KB), compiled into .rodata
Heap fragmentation stays a non-issue because UEFI apps run single-threaded with a flat memory model. The bootloader exits Boot Services before the kernel, so all UEFI memory is reclaimed.
NVRAM Variable Protocol
All LamBoot variables use:
- Vendor GUID:
4C414D42-4F4F-5400-0000-000000000001(ASCII "LAMBOOT") - Attributes:
BOOTSERVICE_ACCESS | RUNTIME_ACCESS - RUNTIME_ACCESS allows the running OS to read/write variables
The Boot Loader Interface variables use the systemd vendor GUID (4a67b082-...) for compatibility with bootctl, systemd-bless-boot, and other tools that expect these standard variables.
LoadFile2 Initrd Protocol
The initrd delivery mechanism follows the same pattern as systemd-boot and Sprout:
1. Read initrd file into Vec<u8>
2. Leak the Vec via Box::into_raw() (stable address)
3. Build VenMedia device path with LINUX_EFI_INITRD_MEDIA_GUID
4. Create InitrdProvider struct (first field = LoadFile2 function pointer)
5. Install DevicePathProtocol on new handle
6. Install LoadFile2Protocol on same handle
7. start_image(kernel) -> kernel EFI stub discovers initrd via LocateDevicePath
8. Kernel calls our callback:
- NULL buffer -> return BUFFER_TOO_SMALL with size
- Valid buffer -> memcpy data, return SUCCESS
9. On cleanup (Drop): uninstall both protocols, reclaim memory
RAII via Rust's Drop trait ensures cleanup even on early returns.
Native PE Loader
LamBoot loads the kernel itself with a native PE loader rather than relying on firmware LoadImage. The Layer-7 boot module dispatches across chainload, UKI, native-PE, and firmware-LoadImage paths, with the native PE loader giving LamBoot direct control over how the kernel image is mapped and started.
Filesystem Driver Loading
The driver loading model follows the UEFI specification's DriverBinding pattern:
1. Read .efi driver file from ESP
2. LoadImage() -> firmware creates image handle
3. StartImage() -> driver's entry point runs, registers DriverBindingProtocol
4. After all drivers loaded:
ConnectController(recursive=true) on ALL handles in the system
5. Firmware matches DriverBinding to block devices
6. New SimpleFileSystem handles appear for supported partitions
This is the same approach used by rEFInd and Sprout. The key insight is that LamBoot does not need to manually match drivers to devices: the UEFI firmware's controller connection logic handles this automatically. LamBoot also reads ext4, btrfs, and FAT natively, including on LVM, read in place, so native partitions are accessible without these legacy filesystem drivers.
BLS Entry Sorting
The sort algorithm implements the full UAPI Group specification:
Tier 1: Boot count state
Bad entries (tries_left == 0) sort LAST
Tier 2: Sort-key presence
Entries WITH sort-key sort BEFORE entries without
Tier 3: Multi-field comparison (both have sort-key)
sort-key: ascending strcmp
machine-id: ascending strcmp
version: DESCENDING UAPI.10 comparison (newest first)
Tier 4: Filename fallback (no sort-key or all fields equal)
Entry ID: DESCENDING UAPI.10 comparison
UAPI.10 Version Comparison
The version comparison algorithm handles:
~creates pre-release:1.0~rc1 < 1.0^creates post-release:1.0 < 1.0^post1- Numeric segments compared as integers (leading zeros stripped)
- Alphabetic segments: uppercase sorts LOWER than lowercase
- Separators (
_,+) are skipped
Security Model
Secure Boot
Three modes of operation:
- Shim 16.1+: Shim overrides SystemTable's LoadImage/StartImage. LamBoot's standard
boot::load_image()calls go through shim's verification hooks transparently, with no bootloader code changes needed. - Legacy shim: LamBoot detects ShimLock protocol and uses
shim_lock.verify()to validate images before loading. - Direct signing: LamBoot binary is signed with
sbsignagainst the machine's db key. Firmware verifies the signature during initial load.
For production Secure Boot deployment today, enrolling LamBoot's key via MOK enrollment is the supported path. See Secure Boot Deployment and MOK Enrollment Guide. LamBoot's first GPG-signed release shipped at v0.10.0, and signing applies from that release onward.
TPM Measured Boot
Measurements follow the Linux TPM PCR Registry:
- PCR 4: Kernel image (using PE_COFF_IMAGE flag for Authenticode hash)
- PCR 5: Boot configuration (policy.toml raw bytes)
- PCR 12: Kernel command line (UTF-16 without trailing NUL)
All measurements use hash_log_extend_event which hashes the data, extends the PCR, and logs the event in the TCG event log. This is compatible with systemd-cryptenroll for TPM-bound disk encryption.
Module Manifest
Diagnostic modules in \EFI\LamBoot\modules\ are discovered via discover_tools(). An optional manifest.toml provides friendly names. The working diagnostic modules today are diag-shell, pci-inventory, and mem-quick:
[modules.pci-inventory]
name = "PCI Inventory"
[modules.mem-quick]
name = "Quick Memory Test"
An nvme-diag module is present as a stub and is not yet implemented. Modules with Icon::Tools are excluded from the auto-boot timeout: only real OS entries (BLS, UKI, Windows, other bootloaders) trigger auto-boot.
Error Handling
LamBoot follows these principles:
- Keep optional features from blocking boot: TPM absent? Skip. No drivers directory? ESP-only mode. ShimLock unavailable? Use standard LoadImage.
- Always leave the user a way out: Recovery options (F2 firmware setup, F12 reboot) stay visible at all times. The no-entries screen shows diagnostic info, and a menu error triggers an automatic reboot to firmware setup.
- Cascade to simpler modes: No GOP? Text console. No BLS entries? Legacy scanning. Crash loop? Fallback entry.
- Search all volumes: Kernels and initrds may live on ext4/btrfs partitions exposed by filesystem drivers, in addition to the FAT ESP.
- Log everything: All errors are logged via the
logcrate, captured by UEFI's debug infrastructure. - Report to ESP: Boot reports with timestamps go to
\EFI\LamBoot\reports\boot.json.
Hardware Detection (Phases 2.5 to 2.8)
Added in v0.2.0, these phases run between security init and ESP mount:
Phase 2.5: SMBIOS. Walks SMBIOS 2.x/3.x structure table for Type 1 (System Information: manufacturer, product, serial) and Type 11 (OEM Strings). OEM strings with lamboot.KEY=VALUE prefix are parsed for VMID, fleet-id, and other host-injected tags.
Phase 2.6: Hypervisor Detection. CPUID leaf 0x40000000 to detect KVM, Hyper-V, VMware, Xen, Parallels, VirtualBox. x86_64 only.
Phase 2.7: IOMMU Detection. Walks ACPI RSDP, then XSDT/RSDT, then DMAR (Intel VT-d) or IVRS (AMD-Vi) tables. Extracts DRHD/IVHD hardware unit descriptions with PCI device scope paths. Reports RMRR reserved memory regions.
Phase 2.8: fw_cfg Data Channel. Reads QEMU fw_cfg I/O ports (0x510/0x511, x86_64 only). Reads VM Generation ID from etc/vmgenid_guid for snapshot detection. Reads optional opt/lamboot/config for host-injected configuration.
Persistent Boot Log
The bootlog.rs module provides crash-safe boot logging to \EFI\LamBoot\reports\boot.log:
- Write-through mode (phases 1 to 8): Every log entry is appended to the ESP file immediately. If LamBoot crashes during init, the log captures how far it got.
- Buffered mode (phase 9, menu): Log entries accumulate in memory. This reduces I/O during the interactive menu where crashes are unlikely.
- Flush: All buffered content is written to ESP before booting the selected entry.
- Size cap: 64 KB maximum. The previous boot's log is overwritten on each boot.
Two-Column GUI Layout
The GUI (gui.rs) uses a two-column layout:
- Left column (55%): Boot entries (kernels, UKIs, EFI loaders). Selection index 0..boot_count.
- Right column (40%): Tools plus system actions. Separate scroll state.
- Navigation: Up/Down within a column, Left/Right to switch columns.
- Header: Logo plus title (left), VMID plus hypervisor plus build info (right).
- Footer: Status message (left), keyboard hints (right).
The GUI opens GOP with open_protocol_exclusive, which disconnects OVMF's GraphicsConsole driver. This is necessary for direct framebuffer access but has a critical side effect.
GraphicsConsole Reconnection
When the GUI opens GOP exclusively, OVMF's GraphicsConsole driver (which renders text via GOP) is disconnected. After the GUI closes, text-mode ConOut becomes invisible: child images appear to hang but are actually running with no visible output.
Fix: Before every start_image call (in chainload_efi, boot_uki, boot_linux), LamBoot calls connect_controller(gop_handle, None, None, true) to reconnect the GraphicsConsole driver. This restores text rendering for child applications.
This pattern is documented in boot.rs::reconnect_console_drivers().
Extra Volume Scanning Scope
After loading filesystem drivers and reconnecting controllers, enumerate_volumes() discovers all SimpleFileSystem handles, including the root filesystem partition. UEFI filesystem drivers for Linux-native formats (ext4, btrfs) are slow at directory traversal on large populated filesystems. Operations like read_dir("\\loader\\entries") on a 37 GB ext4 root partition hang indefinitely under UEFI's single-threaded I/O model.
Current scope: BLS entry scanning is ESP-only, and extra-volume BLS scanning plus OS identification stay off for that reason. This is sufficient for Fedora and Debian, which write BLS entries to the ESP regardless of XBOOTLDR partition presence.
Impact: Kernels and initrds on non-ESP partitions remain accessible for loading, because targeted file reads work fine. Only directory enumeration is affected.
UKI Two-Pass PE Parsing
Unified Kernel Images (UKIs) embed kernel, initrd, and metadata in a single PE binary (60 to 100 MB on Fedora). Reading the entire file into memory for metadata extraction would exhaust UEFI pool memory.
Solution (uki.rs::read_uki_metadata):
- Pass 1: Read 4 KB (PE headers plus section table). Parse section offsets and sizes.
- Pass 2: For each target section (
.osrel,.cmdline,.uname), seek to its offset and read only that section. Sections > 64 KB are skipped (these are.linuxand.initrd, the large binary payloads).
This reads ~5 KB total instead of 60+ MB.
Menu to Boot Loop
When a chainloaded tool (diagnostic module) returns Status::SUCCESS, LamBoot re-enters the menu loop instead of rebooting. The run_bootloader function wraps the menu plus boot sequence in a loop:
- Display menu, wait for selection
- Record selection, write reports, flush boot log
- Call
boot_entry() - If the entry was a tool (
Icon::Tools) andstart_imagereturned: reclaim volumes, loop to step 1 - If the entry was a kernel/UKI: kernel takes over (never returns)
This allows running multiple diagnostic tools in succession without rebooting.
Layer Architecture: the Authoritative Model
Status: normative. Every module declares its layer, and the contract is mechanically enforced by tools/check-layers.py (run in the pre-commit hook and in CI). Audience: LamBoot developers, architecture reviewers, and SDS authors.
Why this model exists
LamBoot is organized into eight dependency layers. Every module declares its layer in a module-level doc comment, and dependencies flow one direction: toward the firmware boundary. This is enforced, not aspirational. tools/layer-map.toml is the machine-readable source of truth, and tools/check-layers.py fails any build where a module lacks its declaration or imports a higher layer.
Rule: higher-numbered layers may depend on lower-numbered layers, and dependencies must always point downward. A module that violates this fails the CI gate.
History. Earlier revisions of this document described a planned structure that the code had outgrown: it listed ~28 modules, placed
fs.rsat Layer 1, namedbls.rsas the Layer-3 pure parser, and asserted the declaration mechanism without any enforcement. The shipped code differed on all of those points. A later revision reconciled the two: the shared boot-entry types were extracted intoboot_types.rs,select_default_entrymoved to the policy layer, every module got its//! Layer:declaration, the map was corrected to match reality (fs.rsis Layer 2;bls_parse.rsis the pure parser;bls.rsis a Layer-4 coordinator), and the whole thing was put under a CI gate. The graph is now a verified acyclic DAG.
The eight layers
The map of every module to its layer lives in tools/layer-map.toml. The layers, low to high:
Layer 0: Platform Introspection
Pure-read discovery of the environment. No side effects, no trust decisions. acpi, hypervisor, smbios, fw_cfg, fw_cfg_config, secure (Secure Boot state query), input (raw key/pointer event source).
Layer 1: Firmware Boundary
Direct UEFI protocol access that carries no policy, parsing, or UI. security_override (Security2/SecurityArch hooks), tpm (TCG2 measured boot), firmware_quirks.
Layer 2: Storage & Filesystems
A filesystem-agnostic read API plus the write path. Consumers above this layer read FAT, ext4, Btrfs, or LVM through one uniform interface. fs_types, fs_backend (the FsBackend trait), the backend family (fs_backend_fat, _ext4, _btrfs, _lvm, _lvm_btrfs, _lvm_dispatch), fs (the coordinator that dispatches to the right backend per volume), fs_writer (the ESP write path), and initrd (the LoadFile2 provider, which reads via fs).
Layer 3: Parsers & Shared Types
Pure parsers (bytes in, structured data out) and the structured types they yield. No I/O, no firmware calls, no state. bls_parse (the pure BLS parser), pe_loader_pure, discovery_pure, boot_types (the shared BootEntry/EntryKind/Icon plus preflight result types), uki, and the I/O shell pe_loader (over pe_loader_pure).
Layer 4: Policy & State
Config-driven decisions and persistent state. policy (parse plus apply policy.toml, including select_default_entry), autodiscovery, preflight, health (NVRAM state machine), partitions (GPT/XBOOTLDR discovery), drivers (policy-gated legacy FS-driver loader), and bls (the BLS discovery coordinator: an I/O shell over the Layer-3 bls_parse that drives the boot counter and autodiscovery, which is why it sits here rather than at Layer 3).
Layer 5: Trust & Audit
Append-only records of decisions. The audit modules (trust_log, trust_log_pure, telemetry, diag, version) are cross-cutting: any layer may write to them, but nothing reads their state to make a decision. They are exempt from the direction rule as dependency targets. report and bootlog are the non-cross-cutting Layer-5 emitters.
Layer 6: Presentation
Everything the user sees or types. gui (GOP double-buffered menu), console (serial/text fallback).
Layer 7: Orchestration
The conductor. Assembles the boot flow from the layers below. Nothing depends on Layer 7. main (the 10-phase boot flow), boot (chainload / UKI / native-PE / firmware-LoadImage dispatch), discovery (cross-backend entry aggregation; an I/O shell over discovery_pure).
Dependency rules (normative, enforced)
- A module may
usefrom its own layer or any lower layer. - A module must not
usefrom a higher layer. - Cross-cutting modules (
diag,version,telemetry,trust_log,trust_log_pure) may be used from any layer. They are written-to and observed from above, and are never read as control state. They are tagged//! Layer: N (cross-cutting). - Pure pairs: a pure half and its I/O shell at the same layer (
pe_loader_purewithpe_loader,trust_log_purewithtrust_log,discovery_purewithdiscovery) may reference each other. The pure half is tagged//! Layer: N (pure). (bls_parsewithblsis a different case:bls.rsis genuinely Layer 4, sobls → bls_parseis an ordinary downward edge.) - Layer 0 modules must import only from Layer 0.
These rules are checked by tools/check-layers.py against tools/layer-map.toml on every commit (pre-commit hook) and every push/PR (CI). The module dependency graph is verified acyclic by check-layers.py --graph.
Introducing new code: where does it go?
Decision tree for any new module:
- Does it touch UEFI protocols directly? Layer 1.
- Does it parse bytes without doing I/O, or define a shared data type? Layer 3.
- Does it read/write files via the FS-agnostic API? Layer 2.
- Does it make a decision based on config plus discovered state? Layer 4.
- Does it record a decision for audit? Write to a Layer-5 cross-cutting module via the trust log; ship a new module only when the record shape is structurally new.
- Does it draw pixels or read keystrokes? Layer 6 (presentation) or Layer 0 (raw input source).
- Does it schedule the boot phases? Layer 7 (
main).
Then: add the module to tools/layer-map.toml, add its //! Layer: N doc comment, and run python3 tools/check-layers.py. The gate tells you immediately if a dependency points the wrong way.
Constraints the gate (and reviewers) enforce
The gate keeps the architecture clean by requiring that:
- UEFI protocol calls live in Layer 1.
- Every module stays within a single layer; a "utility" or "helper" module that crosses layers is rejected (also forbidden by the naming rules below).
- Layer-5 audit modules are written to and observed, and their state is never queried to make a decision.
- Layer-4 policy code reaches UEFI variables through Layer 0/1 rather than reading them directly.
- Boot-phase-sequencing logic lives in
main. - Every module carries a
//! Layer:declaration (the gate fails without it). - An abstraction ships with at least two implementations, or with a second one imminent in the same PR series.
File naming conventions
- Names carry meaning: avoid generic suffixes such as
-manager,-helper,-utility,-common,-core(the one exception islamboot-core/, the crate). - Domain-specific verbs in function names.
- Each module owns one responsibility; helper modules are folded into the module they serve.
- One responsibility per module; split if a module grows past ~500 lines and the responsibilities are separable.
Current module counts and layer totals
46 modules, about 16,000 lines of Rust (lamboot-core). By layer:
| Layer | Name | Modules |
|---|---|---|
| 0 | Platform Introspection | acpi, hypervisor, smbios, fw_cfg, fw_cfg_config, secure, input |
| 1 | Firmware Boundary | security_override, tpm, firmware_quirks |
| 2 | Storage & Filesystems | fs_types, fs_backend, fs_backend_{fat,ext4,btrfs,lvm,lvm_btrfs,lvm_dispatch}, fs, fs_writer, initrd |
| 3 | Parsers & Shared Types | bls_parse, pe_loader_pure, discovery_pure, boot_types, uki, pe_loader |
| 4 | Policy & State | policy, autodiscovery, preflight, health, partitions, drivers, bls |
| 5 | Trust & Audit | trust_log, trust_log_pure, report, bootlog, telemetry, diag, version |
| 6 | Presentation | gui, console |
| 7 | Orchestration | boot, discovery, main |
LamBoot is a medium-sized codebase by bootloader standards (GRUB ~40kLOC of C, systemd-boot ~10kLOC of C, rEFInd ~30kLOC of C). It is smaller than all of them while reading filesystems natively in a way the small ones do not. That compactness is a deliberate property of the design.
This document is normative and machine-enforced. New modules must declare their layer in their module-level doc comment (//! Layer: N — <name>.) and appear in tools/layer-map.toml. tools/check-layers.py checks the declaration and the dependency direction first, in the pre-commit hook and in CI.
See Also
- User Guide: usage reference
- Configuration Guide: all configurable options
- Threat Model: TPM, Secure Boot, and crash loop internals
- Roadmap