diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-04 01:44:05 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-04 01:44:05 +0200 |
| commit | 3e2261d83176752f3ea21e0c97f330b4c1757437 (patch) | |
| tree | 8bbc92f917121fafaf193f1af1c42fba624abda9 /rsnix/src/vt.rs | |
| parent | v0.5.8: heap-free QR encoder + crash-dump QR on framebuffer (diff) | |
| download | rs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.tar.gz rs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.zip | |
v0.5.9: fix OptiPlex heap crash — EBS retry must not rebuild the free list
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.
Diffstat (limited to 'rsnix/src/vt.rs')
| -rw-r--r-- | rsnix/src/vt.rs | 69 |
1 files changed, 44 insertions, 25 deletions
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) { | |||
| 138 | unsafe { (ROWS, COLS) } | 138 | unsafe { (ROWS, COLS) } |
| 139 | } | 139 | } |
| 140 | 140 | ||
| 141 | /// DEBUG (crash-family): read one cell's raw word (ch | fg<<16 | bg<<24) | ||
| 142 | /// from the live cells grid and the rendered grid, for the given text | ||
| 143 | /// row/col. Returns (raw_cell, raw_rendered). | ||
| 144 | pub fn cell_raw(r: usize, c: usize) -> (u32, u32) { | ||
| 145 | unsafe { | ||
| 146 | if CELLS.is_null() || RENDERED.is_null() { | ||
| 147 | return (0xFFFF_FFFF, 0xFFFF_FFFF); | ||
| 148 | } | ||
| 149 | let raw = |v: &Vec<Cell>| -> u32 { | ||
| 150 | if r < ROWS && c < COLS { | ||
| 151 | let cell = v[r * COLS + c]; | ||
| 152 | cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24) | ||
| 153 | } else { | ||
| 154 | 0xFFFF_FFFE | ||
| 155 | } | ||
| 156 | }; | ||
| 157 | ( | ||
| 158 | raw(&*(CELLS as *const Vec<Cell>)), | ||
| 159 | raw(&*(RENDERED as *const Vec<Cell>)), | ||
| 160 | ) | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 141 | /// Shell-entry self-check: dump the vt grid's heap structs so a real- | 164 | /// Shell-entry self-check: dump the vt grid's heap structs so a real- |
| 142 | /// hardware boot shows whether the cells VecData / data arrays are sane | 165 | /// hardware boot shows whether the cells VecData / data arrays are sane |
| 143 | /// at shell entry (the OptiPlex's first banner write #GPs when the | 166 | /// at shell entry (the OptiPlex's first banner write #GPs when the |
| @@ -184,9 +207,12 @@ pub fn debug_verify() { | |||
| 184 | // other three probe slots as zero-initialized). A stale cells | 207 | // other three probe slots as zero-initialized). A stale cells |
| 185 | // data pointer that happens to be in-heap still passes the | 208 | // data pointer that happens to be in-heap still passes the |
| 186 | // range checks (len/cap look fine) but its "cells" read back as | 209 | // range checks (len/cap look fine) but its "cells" read back as |
| 187 | // the underlying memory's leftover fill — the OptiPlex shows | 210 | // the underlying memory's leftover fill. NOTE: the OptiPlex |
| 188 | // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs | 211 | // dump value 0x00AAAAAA is the VGA GRAY text colour |
| 189 | // scrub (0x00AAAAAA) is cleanly distinguishable. | 212 | // (rgb(170,170,170) = PALETTE[7] = fg 7), i.e. rendered shadow |
| 213 | // pixels — the memory read as heap held display text, not a | ||
| 214 | // firmware scrub. BLANK (0x70020) vs 0x00AAAAAA is cleanly | ||
| 215 | // distinguishable. | ||
| 190 | let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); | 216 | let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); |
| 191 | let mut content_ok = true; | 217 | let mut content_ok = true; |
| 192 | let mut rcontent_ok = true; | 218 | let mut rcontent_ok = true; |
| @@ -269,31 +295,24 @@ pub fn debug_verify() { | |||
| 269 | r_probes_raw[2], | 295 | r_probes_raw[2], |
| 270 | r_probes_raw[3] | 296 | r_probes_raw[3] |
| 271 | )); | 297 | )); |
| 272 | // Bottom-anchored: row 0-1 get overwritten by the shell | 298 | // NOTE: debug markers are serial-only now; the crash family |
| 273 | // banner, row 2 by the prompt (and a stale crash.log | 299 | // was resolved (EBS-retry free-list rebuild) and the screen |
| 274 | // replays at rows 2+), so this marker must live at the very | 300 | // must stay clean for users. |
| 275 | // bottom to stay readable until the user can copy it. | 301 | crate::serial_println!( |
| 276 | crate::gop::boot_marker_at( | 302 | "vt: c {:#x} cap={:#x} {} | r {:#x} {}", |
| 277 | &alloc::format!( | 303 | c.as_ptr() as usize, |
| 278 | "vt: c {:#x} cap={:#x} {} | r {:#x} {}", | 304 | c.capacity(), |
| 279 | c.as_ptr() as usize, | 305 | if content_ok { "ok" } else { "GARBAGE" }, |
| 280 | c.capacity(), | 306 | r.as_ptr() as usize, |
| 281 | if content_ok { "ok" } else { "GARBAGE" }, | 307 | if rcontent_ok { "ok" } else { "GARBAGE" } |
| 282 | r.as_ptr() as usize, | ||
| 283 | if rcontent_ok { "ok" } else { "GARBAGE" } | ||
| 284 | ), | ||
| 285 | crate::gop::bottom_row(1), | ||
| 286 | ); | 308 | ); |
| 287 | } else { | 309 | } else { |
| 288 | msg("verify: ok"); | 310 | msg("verify: ok"); |
| 289 | crate::gop::boot_marker_at( | 311 | crate::serial_println!( |
| 290 | &alloc::format!( | 312 | "vt: c {:#x} cap={:#x} ok | r {:#x} ok", |
| 291 | "vt: c {:#x} cap={:#x} ok | r {:#x} ok", | 313 | c.as_ptr() as usize, |
| 292 | c.as_ptr() as usize, | 314 | c.capacity(), |
| 293 | c.capacity(), | 315 | r.as_ptr() as usize |
| 294 | r.as_ptr() as usize | ||
| 295 | ), | ||
| 296 | crate::gop::bottom_row(1), | ||
| 297 | ); | 316 | ); |
| 298 | } | 317 | } |
| 299 | } | 318 | } |
