Login
Task 6.5a (ii), split: the guest’s EFI environment in three parts
Login

Why split. The first run of docs/tasks/6.5a-ii.md stopped under rule 3 at 411 changed non-spec lines with part (a) unfinished and nothing committed. Its draft, report and boot log are kept for reference, not to be copied blindly, at /tmp/claude-1000/-workspace-tree/a0ae0c06-f036-4a7e-a6d5-e2cc56695fb6/scratchpad/6.5a-ii-attempt1/ (tracked.diff, untracked.tar, 6.5a-ii-report.md, 6.5a-ii-a-new.log). Part (a) splits along the seam that run found: the firmware standing up on its own, then loading an application. Part (b) is unchanged. Three branches, one after another, each from trunk after the previous one is merged: slice/6.5a-ii-1, slice/6.5a-ii-2, slice/6.5a-ii-3, each one check-in.

docs/tasks/6.5a-ii.md stays the specification for everything not restated here: its Read first, Shape, Constraints and Demonstration apply to every part. Read it first.

The first run's findings, which the parts must avoid.

  1. The firmware never ran. Glint placed it ("Guest image: guest.efi, 44 KiB at 0x40200000, entry 0x40201000, 0 relocations") and the guest faulted on its first fetch at 0x200: the image jumped to an address from its link layout, not where it was placed. The test guest is placed the same way and works, so build the firmware exactly as build_test_guest builds the test guest (same linker script, same position independence), or give it base relocations that 3.6b applies. Show which in the check-in, with llvm-objdump of its entry.
  2. The test application's PE was refused by 3.6's parser with EFI_LOAD_ERROR. Find which check refused it before changing either the build or the parser, and say which. If the parser is right, fix the build; if the parser is too strict for a valid image, fix the parser and add a spec for that header shape.
  3. A function grew to 2,976 bytes (GuestEFI.build). Build tables in small functions from the start.
  4. x86_64 was built inside the seed because a new host spec required spec/image_initialization_spec.cr. Don't require it from a new spec; run it on its own.
  5. One spec expectation was wrong (14 UCS-2 units read for the 15-character console=ttyAMA0). Derive lengths from the string in the spec, not by hand.

Part 1 (slice/6.5a-ii-1): the firmware stands up

The firmware is Glint's guest. Glint loads it as it loads the test guest (bin/glint-run aarch64 --mode hypervisor --guest build/aarch64/guest/efi.efi), with 6.5a (i)'s tree in x0. It builds its system table, ConOut over the PL011 with 4.5's filter rule, ConIn "not ready", the configuration table with the devicetree GUID, and every other boot service returning EFI_UNSUPPORTED. Then, with no application yet, it calls its own ConOut to print guest firmware: system table ok, devicetree in configuration table and powers off through PSCI. Host specs: the table headers (signatures, revisions, sizes, CRC32s) read back; the UCS-2 filter. About 200 non-spec lines.

Part 2 (slice/6.5a-ii-2): it starts an EFI application

Glint places the application's file bytes in guest memory (where, and how the firmware learns it, stated: a /chosen property such as glint,efi-app with address and size is one way). The firmware loads it with 3.6's rules, shared and not copied, installs the loaded-image protocol on its handle (base, size, load options from /chosen/bootargs as UCS-2), and calls its entry. bin/glint-run --guest-efi FILE and bin/glint-build efi-app aarch64 as in the original task; the application prints its three lines (efi app: started, efi app: devicetree found, efi app: load options "<bootargs>"), returns, and the firmware powers off. Specs: the application's PE parsed and loaded on the host; the loaded-image fields; the boot. About 200 non-spec lines.

Part 3 (slice/6.5a-ii-3): memory, ExitBootServices, runtime services

Exactly part (b) of docs/tasks/6.5a-ii.md.

Check-in comments. Rule 2's four parts, one per part.

Part 2, split again (Nov 11): 2a, then 2b

The first run of Part 2 stopped under rule 3 at 402 changed non-spec lines, with the application not yet started and nothing committed. Its draft and report are at /tmp/claude-1000/-workspace-tree/a0ae0c06-f036-4a7e-a6d5-e2cc56695fb6/scratchpad/6.5a-ii-2-attempt1/ (tracked.diff, untracked.tar, 6.5a-ii-2-report.md). It found finding 2's cause: the test application's .data section is all BSS (SizeOfRawData 0), but boot/crt0.S writes its PointerToRawData as _data_rva (16,384), past the end of the 12,800-byte file, and 3.6's parser rightly refuses a section whose raw span starts outside the file. The draft worked around it by adding a data byte to the application; don't. Fix the header instead. Two branches, one after the other, each one check-in.

2a (slice/6.5a-ii-2a): the shared PE loader and the header fix. About 100 non-spec lines.

2b (slice/6.5a-ii-2b): starting the application. Part 2 as above, on 2a's loader, without the data-byte workaround, and with two fixes the draft's report names: the loaded-image protocol answers only for the one handle the firmware installed (compare the handle, don't trust a field inside whatever it points to); and guest_efi_main stays under 2 KiB in the release build (the draft's grew to 4,020 bytes by inlining; keep the pieces as separate functions and check the built sizes, not the source). Run boots one at a time: the draft's two runs collided on the shared ESP image. About 250 non-spec lines.