fix(discovery): derive the below-4G split robustly from fragmented mtree

host_probe derived the guest's below-4G split (vmie `low`) by taking the first
GPA-0 RAM run in `info mtree -f`. When low RAM is fragmented by overlay pages
(Hyper-V SynIC) and blackhole holes (smbase/tseg), that first run is a tiny
fragment, so the split came out far too small and host_bootstrap could never
recover the System DTB — the memctx context was never published.

Extract a pure parser, mtree_low_split(): anchor on the system flatview, take
`low` from the @file-offset of the high-RAM region at GPA >= 4 GiB (which equals
the split by construction), cross-validate against the PCI-hole base, and fail
closed when it can't be derived. QMP-reply un-escaping moves to the transport
boundary so the parser works on plain text. Unit-tested against a synthetic
fragmented flatview including a decoy non-system address space.

postinst also hints to restart the daemon after an upgrade (a running instance
keeps the old build until restarted).

Bump 0.3.6.
This commit is contained in:
2026-06-24 14:26:50 +03:00
parent 26e5ab4709
commit 3142337e62
7 changed files with 372 additions and 25 deletions
+83
View File
@@ -0,0 +1,83 @@
/* test_mtree.c — unit tests for mtree_low_split (the below-4G split parser). Pure text in,
* number out; no QMP/transport. The fragmented fixture reproduces the structural traps the
* old heuristic tripped on (Hyper-V synic overlays, smbase/tseg blackhole holes, rom holes)
* plus a decoy non-system flatview that carries its OWN GPA-0 stub and a DIFFERENT @offset,
* proving the system address space is selected (not "first match in the text"). */
#define _GNU_SOURCE
#include "mtree.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef FIXTURE_DIR
#define FIXTURE_DIR "."
#endif
static int g_fail = 0;
#define CHECK(cond, msg) do { if (!(cond)) { printf(" FAIL: %s\n", (msg)); g_fail = 1; } } while (0)
/* Slurp a whole text file into a heap buffer (NUL-terminated). NULL on error. */
static char* slurp(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) return NULL;
if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; }
long sz = ftell(f);
if (sz < 0) { fclose(f); return NULL; }
rewind(f);
char* buf = malloc((size_t)sz + 1);
if (!buf) { fclose(f); return NULL; }
size_t got = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[got] = 0;
return buf;
}
/* Case B: a minimal, NON-fragmented system flatview — one big GPA-0 ram run plus high-RAM
* carrying @<low>. Must not be broken by the new parser. */
static const char* k_happy =
"FlatView #0\n"
" AS \"memory\", root: system\n"
" Root memory region: system\n"
" 0000000000000000-000000007fffffff (prio 0, ram): ram0\n"
" 0000000080000000-0000000081ffffff (prio 0, i/o): vfio-pci-bar3\n"
" 0000000100000000-000000017fffffff (prio 0, ram): ram0 @0000000080000000\n";
/* Case C: text without any system flatview => fail-closed. */
static const char* k_no_system =
"FlatView #0\n"
" AS \"I/O\", root: io\n"
" Root memory region: io\n"
" 0000000000000000-0000000000000007 (prio 0, i/o): dma-chan\n";
int main(void) {
printf("test_mtree\n");
/* Cases A and E: the fragmented fixture (decoy first, system second). */
char path[1024];
snprintf(path, sizeof path, "%s/mtree_split_fragmented.txt", FIXTURE_DIR);
char* frag = slurp(path);
CHECK(frag != NULL, "fragmented fixture loaded");
if (frag) {
uint64_t low = mtree_low_split(frag);
/* A: fragmented low-RAM must NOT yield the GPA-0 stub end (0x18000) — the bug. */
CHECK(low == 0x80000000ull, "A: fragmented split == 0x80000000");
CHECK(low != 0x18000ull, "A: not the GPA-0 stub end (0x18000)");
/* E: the decoy (non-system) flatview comes FIRST and carries @0x40000000; the
* function must select the SYSTEM flatview (@0x80000000), not the decoy. */
CHECK(low != 0x40000000ull, "E: decoy flatview @offset rejected (system AS chosen)");
free(frag);
}
/* Case B: happy path (non-fragmented) still resolves to the high-RAM @offset. */
CHECK(mtree_low_split(k_happy) == 0x80000000ull, "B: non-fragmented happy path == 0x80000000");
/* Case C: no system flatview => 0. */
CHECK(mtree_low_split(k_no_system) == 0, "C: no system flatview => fail-closed 0");
/* Case D: garbage / empty => 0. */
CHECK(mtree_low_split("") == 0, "D: empty text => 0");
CHECK(mtree_low_split("not an mtree at all\n") == 0, "D: junk text => 0");
printf("mtree tests: %s\n", g_fail ? "FAIL" : "PASS");
return g_fail ? 1 : 0;
}