Login
Phase 4: Traps and the guest’s system calls
Login

xv6 §4, "Traps and system calls". "There are three kinds of event which cause the CPU to set aside ordinary execution of instructions and force a transfer of control to special kernel code that handles the event." Those three kinds are a system call, an exception and a device interrupt.

Phase 4 makes Glint a hypervisor. It enters a guest, takes control back when the guest traps (a VM exit: the CPU leaving the guest and returning to Glint with a reason), and serves the guest's calls. It starts from Phase 3's guest address space (3.5) and loaded image (3.6). Work is divided between phases as follows:

Every boot already leaves boot services and runs native by default (2.10), so nothing in this phase is typed at a prompt to get there. Hypervisor mode joins Phase 2's mode choice (2.6) in 4.3a, as one more request: a boot asks for it with the word hypervisor in the ESP file glint-mode.txt, which glint-run --mode hypervisor writes (2.10), and gets it when 2.6's checks for native mode pass and 2.2 found the virtualization level (EL2, or SVM). Otherwise the boot stays native, or falls back to hosted, and its Mode: line says why. Slices whose code is shared across architectures land as one check-in; the run loop, which is different machinery on Arm and on AMD x86, is split into an aarch64 check-in followed by an x86_64 one.

Provisional slices. Each is refined, with its quoted sentence, when the phase starts.

Order while x86-1 can't build aarch64. Until BQ2's toolchain with AArch64 is x86-1's system, slices that land both architectures together land their x86_64 half first, and the aarch64 half follows as its own check-in when the toolchain arrives. 4.3b may precede 4.3a. The maintainer asked for the book first (ocx-console 363063f81e), and this is the route that keeps it moving; the plan's aarch64-first rule resumes once both build.

Phase 3, as refined, constrains these slices, and their refinement must honour it. On aarch64, 3.3 runs Glint on SP_EL0 and takes exceptions on SP_ELx, so 4.1's vectors and 4.3a's guest entry and exit save and restore Glint's SP_EL0 with the rest of its state. 3.6 loads guest images as PE32+ for Glint's own machine, so 4.2's test guest is built as Glint's images are (tools/glint_build.cr, boot/efi.lds), and the guest firmware of 6.5 is loaded through 3.6's parser too. On x86_64, 3.3's TSS with an IST stack for #DF is the one 4.1 extends.

Slices

4.1 Full exception vectors. Glint gets complete exception vectors on Arm, at the level it runs at (EL2 for hypervisor mode, EL1 in native mode without virtualization), and an IDT (the x86 table of interrupt and exception handlers). They save the interrupted state into a frame and dispatch on the cause, replacing 2.7's panic-only vectors. It opens the phase because every later slice handles some kind of trap. One dispatcher for every trap, rather than one per cause, is Parsimony in Tambassi's sense [39]. Its passage is xv6 §4.1 (trap machinery). Both architectures land together: the dispatcher is shared and only the vector-table format differs.

4.2 A bare-metal test guest. guest/test/ holds a tiny program that runs as a guest. It knows no Linux; it can only print through calls to Glint and power itself off. It comes before the run loop (4.3a, 4.3b) so the run loop has something small to enter. A guest this small isolates Glint's behaviour from a large guest's, so any failure in this phase points at Glint, which is Demonstrability. It is built for both architectures in one check-in.

4.3a The run loop, aarch64. Glint loads a guest's state, enters it at EL1 with eret (the instruction that returns to a lower exception level), takes the VM exit through 4.1's vectors and decodes its reason. It also tracks the guest's virtual kernel and user mode, as Hailperin §7.5.2 describes: "the VMM keeps track of each virtual machine's simulated mode." It comes after 4.1 and 4.2 because it needs both a dispatcher and a guest to run. Hypervisor mode becomes selectable in 2.6's mode choice here: the request file gains hypervisor, and the decision gains the virtualization level as a requirement for it. Keeping all knowledge of guest entry and exit in this one loop, behind a vCPU interface the rest of Glint calls, is Loose Coupling. Its passage is xv6 §4.2 ("Traps from user space").

4.3b The run loop, x86_64. The same vCPU interface on AMD x86: Glint fills the VMCB (the per-guest control block AMD's SVM uses), enters the guest with VMRUN and decodes the exit code SVM returns, including port I/O exits, which Phase 5's UART (5.3) relies on. It follows 4.3a because the plan's architecture rule lands aarch64 first. Putting SVM behind the same interface as 4.3a, so nothing above the run loop knows which CPU it is on, is Loose Coupling.

4.4 The guest's system calls. Glint dispatches HVC (Arm) and VMMCALL (x86) by SMCCC function number (the standard numbering of calls a guest can make to a hypervisor), then implements PSCI VERSION and SYSTEM_OFF. It comes after 4.3a/b because a call arrives as a decoded VM exit. Using the standard numbering that Linux already speaks, rather than a Glint-specific call interface, keeps the guest ABI replaceable and documented by others, which is Loose Coupling. Its passage is xv6 §4.3 ("Code: Calling system calls"); MOS4/5 §7.2 explains why such calls trap at all. Both architectures land together; only the trapping instruction differs.

4.5 Checking guest arguments. Before Glint uses any address or length a guest passes in 4.4's calls, it checks them against the guest address space built in 3.5. Specs try the hostile values the threat model (2.8) anticipates. It comes after 4.4 because there are now calls with arguments to check, and it is where the threat model starts being enforced. Checking every argument against a written boundary, with specs that show the hostile cases rejected, is Demonstrability. Its passage is xv6 §4.4 ("Code: System call arguments").

4.6 Traps from Glint itself. A fault in Glint's own code now produces a report with a backtrace (the chain of calls that led to the fault), through 4.1's dispatcher, extending 2.7's panic. It comes last because the guest-facing paths (4.3a–4.5) are the phase's purpose, and a good report of Glint's own faults is what makes debugging them practical. A backtrace turns a crash into evidence, which is Demonstrability. Its passage is xv6 §4.5 ("Traps from kernel space"); both architectures land together.

Phase demo: booted with bin/glint-run aarch64 --mode hypervisor (and x86_64), with the test guest's image on the ESP for the preload (2.9), Glint reports Mode: hypervisor, and the test guest calls PSCI VERSION, prints the answer through Glint and powers off with SYSTEM_OFF, on both architectures. Nothing is typed.

References