Skip to content

nvx-rs: read OpenVMM's fatal error before a chain of any length, and name repeated CPUID record entries - #1039

Merged
Pedro Henrique Penna (ppenna) merged 3 commits into
devfrom
agents/cpu-surface-review-followups
Oct 30, 2026
Merged

Pedro Henrique Penna (ppenna) merged 3 commits into
devfrom
agents/cpu-surface-review-followups

Conversation

@ppenna

Copy link
Copy Markdown
Contributor

Follows up #1014 and microsoft/openvmm#4804 from their review, for #568. Two independent reviews of #1014 found that OpenVMM's uncapped CPU-check errors can push the line that leads OpenVMM's fatal error out of the 64 KiB at the end of a VMM's log in which nvx-rs looked for it, which then lost a clone's attribution to an incompatible host, and that a snapshot record that lists an entry more than once, or out of order, still hid differences. This pull request makes nvx-rs read that line before a chain of any length, pins microsoft/openvmm#4818 at bcfc47cff6606afadffef97551e538962895b598, which names those differences and tests each register, value, and mask that the error names, and maps the pin in the OpenVMM roadmap.

Based on dev at d34d8ab73. microsoft/openvmm#4818 is two commits directly on e4c741e18, the head of feature/microvm/dev since microsoft/openvmm#4804 merged and the revision that dev pins since #1014 merged, and the pin is that pull request's head commit itself.

Change

Commit Files Change
6856d4769 nvx-rs nvx-rs/src/host/template/clone/records.rs read_fatal reads the VMM's log back from its end, 64 KiB at a time, until it finds the last line that starts with fatal error: , then reads that line and the error's chain after it
30ff24201 openvmm openvmm e4c741e18 → bcfc47cff: the two commits of microsoft/openvmm#4818
0853bdc75 doc doc/openvmm-upstream-roadmap.md Records them as positions 366 and 367, unassigned, with a remark that they change only the first CPU check of the VM worker's time ABI; moves the NVX pin; and records that microsoft/openvmm#4804 merged positions 362-365 into feature/microvm/dev

The protocol vectors and built-in CPU profiles that nvx-rs copies are unchanged at the pin, and no error code changes.

The fatal line before a chain of any length

A clone whose VMM exits before its restore-ready event reads, from the VMM's log, the line with which OpenVMM reported the error that ended it, such as fatal error: [E_CPU_SURFACE] failed to launch vm worker. The failure's message keeps the line, and its time ABI code attributes the failure to an incompatible host, Evidence::IncompatibleHost, whose outcome is Host. OpenVMM writes the line first, then the error's chain (fatal_error_message in openvmm_entry). read_fatal looked for the line among the last 64 KiB of the log (FATAL_TAIL_BYTES), which held it while the CPU checks named at most 16 differences. Since #1014, they name every difference that they find, together, in one cause of the chain:

  • openvmm: name every CPUID difference in CPU-surface errors #1014 put the largest such error at 61,717 bytes, for the Turin profile when every register of every entry differs in both comparisons and the backend supports none of the profile, and only a snapshot record that no OpenVMM writes, with most of the 1024 entries that the decoder admits, above 64 KiB.
  • The reviews found that, on a pass-through backend, the error also names every non-zero entry outside the profile's tables that VP 0 reads at the host's candidates, which the host's enumeration bounds, at 0x100 leaves per range and 64 subleaves per leaf, rather than the profile. By the reviews' count, 32 such entries take that Turin error past 64 KiB, and 416 took a combined error to 107,893 bytes.

Past 64 KiB, the tail began inside the chain, so read_fatal found no line, diagnosed kept Evidence::LostStatus, whose outcome is Unknown, and the failure's message lost OpenVMM reported: ....

read_fatal now finds the line with last_line_start, which reads the log back from its end, a block of FATAL_BLOCK_BYTES (64 KiB) at a time, and checks every line start in a block, last first: after each newline, and at the log's start. A block's read goes on past its end by the prefix's length, so a line that starts at the block's end, after a newline that ends the block, or whose prefix straddles the next block, is found too. It then reads from the line to the log's end: the line, cut to 1024 characters as before, and the chain, in which a cause that names a forward that OpenVMM cannot bind still follows the line. The search holds a block at a time, and the read the chain, which OpenVMM held whole to write it. A log without the line is read whole, a block at a time. The log holds OpenVMM's output of one launch alone; the guest's console has its own log.

The tests read the line before a chain three blocks long, with and without a line before it; with a cause that names a forward after a long one; and wherever a block starts around the line, at each offset from the newline before it to past its prefix. They read none from a long log that only quotes the line. With the 64 KiB tail in place of last_line_start, the first of them fails: read_fatal returns none.

OpenVMM changes

  • Each register, value, and mask (e51ab72d3). Tests only. They check the whole error for a change of the value, and of the mask, of each register alone; for several registers of one entry, named in order on one line; and for an entry that differs among entries that the snapshot lacks, followed by entries that only the snapshot has, named and counted in that order. They pass before and after the next commit.
  • Repeated and unordered entries (bcfc47cff). recorded_cpuid_differences kept only the last listing of an entry that a snapshot's record lists more than once, and named a record out of order only when nothing else differed, though decode_effective_cpuid admits both: a changed listing followed by the entry's own read as "the snapshot lists the same entries in another order", and a record out of order with a changed entry named the changed entry alone. It now compares every listing with the partition's entry and names each that differs, then each entry that the record lists more than once, and, whatever else differs, a record not in order of leaf and subleaf. The error keeps E_CPU_SURFACE, and names the differences of a record that OpenVMM writes as before. With the previous function, the commit's test fails on the review's first case.

Validation

  • nvx-rs, on Windows: cargo fmt --all --check; cargo clippy --locked -- -D warnings with --all-targets --all-features, --lib, and --lib --no-default-features; cargo doc --locked --no-deps --all-features with -D warnings; cargo +1.89 check --locked --all-targets --all-features; and cargo test --locked --all-features, all clean.
  • OpenVMM, on Windows, at each commit of [DO NOT REVIEW] microVM: name repeated and unordered entries of a snapshot's CPUID record openvmm#4818: cargo clippy --all-targets -p openvmm_core -- -D warnings, cargo doc --no-deps -p openvmm_core, cargo nextest run --profile agent -p openvmm_core (122 and 123 tests passed), and cargo xtask fmt --fix, all clean.
  • NVX at 0853bdc75: python scripts\nvx.py verify (consistent); the openvmm_vectors and openvmm_cpu_profiles comparisons with --ignored (3 and 1 passed) against the pin; python scripts\check_markdown_links.py; and git diff --check.
  • Every commit is signed, both these and [DO NOT REVIEW] microVM: name repeated and unordered entries of a snapshot's CPUID record openvmm#4818's.
  • Not run locally: the microVM scenarios and the runtime adapter, on Linux/KVM, Linux/MSHV, and Windows/WHP, which CI runs. The pin changes no step of a CPU check that passes, and names the same differences of a record that OpenVMM writes.

Merge microsoft/openvmm#4818 into feature/microvm/dev, by fast-forwarding that branch to bcfc47cff, before merging this pull request.

A clone whose VMM exits before its restore-ready event reads, from the
VMM's log, the line with which OpenVMM reported the error that ended
it, and attributes the failure to an incompatible host when the line's
time ABI code, such as E_CPU_SURFACE, says so. OpenVMM writes that line
first, then the error's chain. read_fatal looked for the line in the
last 64 KiB of the log alone, which held it while OpenVMM's CPU checks
named at most 16 differences. Since microsoft/openvmm#4804, which #1014
pinned, they name every difference that they find, together, in one
cause of the chain: about 7.5 KB on the Azure test hosts, and 61,717
bytes for the Turin profile when every register of every entry differs
in both comparisons and the backend supports none of the profile, but
more for a pass-through backend whose VP 0 reads non-zero values at
entries outside the profile's tables, each of which the error names,
or for a snapshot record of up to the 1024 entries that the decoder
admits. A chain longer than 64 KiB pushed the line out of the tail, so
the failure kept its lost status, whose outcome is unknown, rather than
the incompatible host, and lost OpenVMM's line from its message.

read_fatal now reads the log back from its end, 64 KiB at a time, until
it finds the last line that starts with "fatal error: ", then reads
that line and the chain that follows it, so that the line keeps its
attribution after a chain of any length. A log without the line is
read whole, a block at a time.

Tests check that the line is read before a chain several blocks long,
whether or not a line precedes it, with a cause that names a forward
that OpenVMM cannot bind after a long one, and wherever a block starts
around the line: after the newline before it, at its start, or within
its prefix; and that a long log that only quotes the line holds none.

Follow-up to #568.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pin microsoft/openvmm#4818 at bcfc47cff6606afadffef97551e538962895b598,
two commits on e4c741e18, the previous pin, which is the head of
feature/microvm/dev.

When the effective CPUID that a restore recomputes differs from the
snapshot's record, OpenVMM's E_CPU_SURFACE error names every entry that
differs. It kept only the last listing of an entry that the record
lists more than once, and named a record out of order only when nothing
else differed, so a record that no OpenVMM writes, but that the decoder
admits, could hide differences: a changed listing followed by the
entry's own was reported as the same entries in another order.

The first commit tests the error's every register, value, and mask, and
the order of the entries that only one side has. The second compares
every listing of an entry and names each that differs, each entry
listed more than once, and a record out of order, whatever else
differs. The error keeps its code, and names the differences of a
record that OpenVMM writes as before.

The protocol vectors and built-in CPU profiles that nvx-rs copies are
unchanged.

Refs #568

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Record the two commits that the previous commit pins as positions 366
and 367 of the microVM chain, and move the NVX pin to bcfc47cff.

Positions 366 and 367 make the restore's comparison of the effective
CPUID with a snapshot's record name every difference of a record that
the decoder admits although no OpenVMM writes it: each entry that the
record lists more than once, each of its listings that differs, and a
record out of order, together with the entries that differ. They also
test each register, value, and mask that the error names. They change
only the first CPU check of the VM worker's time ABI, its tests, and
the snapshot guide's description of it; say so in the table's remarks.
The pin is now 210 commits past the mapped tip.

Positions 362-365 are no longer only a proposal: microsoft/openvmm#4804
merged them, so position 365, e4c741e18, is the head of
feature/microvm/dev. Say so in the scope, and name
microsoft/openvmm#4818 as the proposal of positions 366 and 367.

Refs #568

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A missing negative test for boundary-aligned quoted fatal text leaves false fatal diagnoses caused by incorrect line-start handling unguarded.

1 open issue
What changed in this PR

Preserves NVX clone-failure attribution when OpenVMM emits long CPU-check error chains, following #1014, and incorporates improved CPUID-record diagnostics.

Changes:

  • Search logs backward in 64 KiB blocks instead of limiting fatal-line detection to the final block.
  • Add tests for long error chains, forwarding causes, and fatal-prefix block boundaries.
  • Update the OpenVMM roadmap for pin bcfc47cff and repeated or unordered CPUID-record diagnostics.
File Description
nvx-rs/​src/​host/​template/​clone/​records.rs Adds backward fatal-line searching and regression tests.
doc/​openvmm-upstream-roadmap.md Records the new pin, commit positions, and CPUID diagnostic changes.

Review effort: Max

format!("{filler}warning: fatal error: quoted\n{filler}"),
)
.unwrap();
assert_eq!(read_fatal(&path), None);
@ppenna
Pedro Henrique Penna (ppenna) merged commit 4434b1a into dev Oct 30, 2026
47 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the agents/cpu-surface-review-followups branch October 30, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants