From 3e2261d83176752f3ea21e0c97f330b4c1757437 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 4 Aug 2026 01:44:05 +0200 Subject: v0.5.9: fix OptiPlex heap crash — EBS retry must not rebuild the free list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: on real hardware SetMode can invalidate the pre-captured ExitBootServices map key, so EBS fails and the retry refreshed the map via capture_memory_map -> init_heap, which REBUILT the free list from the raw map and wiped the allocation state of every pre-EBS live object (the display shadow and the vt grids). Their spans became free while still live, later allocations carved inside the shadow, and the display writes clobbered them — zeroed back-pointers (family A ptr=0x404443e0 block=0x0) and gray glyph pixels (family B 0xaaaaaa00aaaaaa = 0x00AAAAAA = PALETTE[7] fg-7 text colour, not a firmware scrub). Fix: refresh_and_exit now re-captures the map and returns a fresh EBS key without rebuilding the free list (refresh_map_key); the free list is the authoritative live-allocation state. Verified in QEMU under a forced EBS failure (SIMULATE_EBS_FAILURE test hook): verify ok, probe NOT free, flhead outside the shadow — identical to a clean boot, and the full shell sequence is crash-free. Also: removed the crash-family debug markers from the screen (boot-time heapdbg lines, vt verify marker, shadow marker) — the vt grid self-check stays serial-only. Version 0.5.8 -> 0.5.9. --- rsnix/src/vt.rs | 69 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 25 deletions(-) (limited to 'rsnix/src/vt.rs') diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs index 88f8d43..4ffb44d 100644 --- a/rsnix/src/vt.rs +++ b/rsnix/src/vt.rs @@ -138,6 +138,29 @@ pub fn size() -> (usize, usize) { unsafe { (ROWS, COLS) } } +/// DEBUG (crash-family): read one cell's raw word (ch | fg<<16 | bg<<24) +/// from the live cells grid and the rendered grid, for the given text +/// row/col. Returns (raw_cell, raw_rendered). +pub fn cell_raw(r: usize, c: usize) -> (u32, u32) { + unsafe { + if CELLS.is_null() || RENDERED.is_null() { + return (0xFFFF_FFFF, 0xFFFF_FFFF); + } + let raw = |v: &Vec| -> u32 { + if r < ROWS && c < COLS { + let cell = v[r * COLS + c]; + cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24) + } else { + 0xFFFF_FFFE + } + }; + ( + raw(&*(CELLS as *const Vec)), + raw(&*(RENDERED as *const Vec)), + ) + } +} + /// Shell-entry self-check: dump the vt grid's heap structs so a real- /// hardware boot shows whether the cells VecData / data arrays are sane /// at shell entry (the OptiPlex's first banner write #GPs when the @@ -184,9 +207,12 @@ pub fn debug_verify() { // other three probe slots as zero-initialized). A stale cells // data pointer that happens to be in-heap still passes the // range checks (len/cap look fine) but its "cells" read back as - // the underlying memory's leftover fill — the OptiPlex shows - // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs - // scrub (0x00AAAAAA) is cleanly distinguishable. + // the underlying memory's leftover fill. NOTE: the OptiPlex + // dump value 0x00AAAAAA is the VGA GRAY text colour + // (rgb(170,170,170) = PALETTE[7] = fg 7), i.e. rendered shadow + // pixels — the memory read as heap held display text, not a + // firmware scrub. BLANK (0x70020) vs 0x00AAAAAA is cleanly + // distinguishable. let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); let mut content_ok = true; let mut rcontent_ok = true; @@ -269,31 +295,24 @@ pub fn debug_verify() { r_probes_raw[2], r_probes_raw[3] )); - // Bottom-anchored: row 0-1 get overwritten by the shell - // banner, row 2 by the prompt (and a stale crash.log - // replays at rows 2+), so this marker must live at the very - // bottom to stay readable until the user can copy it. - crate::gop::boot_marker_at( - &alloc::format!( - "vt: c {:#x} cap={:#x} {} | r {:#x} {}", - c.as_ptr() as usize, - c.capacity(), - if content_ok { "ok" } else { "GARBAGE" }, - r.as_ptr() as usize, - if rcontent_ok { "ok" } else { "GARBAGE" } - ), - crate::gop::bottom_row(1), + // NOTE: debug markers are serial-only now; the crash family + // was resolved (EBS-retry free-list rebuild) and the screen + // must stay clean for users. + crate::serial_println!( + "vt: c {:#x} cap={:#x} {} | r {:#x} {}", + c.as_ptr() as usize, + c.capacity(), + if content_ok { "ok" } else { "GARBAGE" }, + r.as_ptr() as usize, + if rcontent_ok { "ok" } else { "GARBAGE" } ); } else { msg("verify: ok"); - crate::gop::boot_marker_at( - &alloc::format!( - "vt: c {:#x} cap={:#x} ok | r {:#x} ok", - c.as_ptr() as usize, - c.capacity(), - r.as_ptr() as usize - ), - crate::gop::bottom_row(1), + crate::serial_println!( + "vt: c {:#x} cap={:#x} ok | r {:#x} ok", + c.as_ptr() as usize, + c.capacity(), + r.as_ptr() as usize ); } } -- cgit v1.2.3