diff options
Diffstat (limited to 'rsnix/src/vt.rs')
| -rw-r--r-- | rsnix/src/vt.rs | 132 |
1 files changed, 104 insertions, 28 deletions
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs index 2906258..6e1f12f 100644 --- a/rsnix/src/vt.rs +++ b/rsnix/src/vt.rs | |||
| @@ -173,48 +173,124 @@ pub fn debug_verify() { | |||
| 173 | }) | 173 | }) |
| 174 | .unwrap_or(false); | 174 | .unwrap_or(false); |
| 175 | // Content check: at shell entry nothing has been printed yet, so | 175 | // Content check: at shell entry nothing has been printed yet, so |
| 176 | // every cell must still be BLANK (all-zero). A stale cells data | 176 | // every cell must still hold the grid's init fill. NOTE: that |
| 177 | // pointer that happens to be in-heap still passes the range | 177 | // fill is BLANK (space + fg 7 + bg 0 = raw 0x70020), NOT zero — |
| 178 | // checks (len/cap look fine) but its "cells" read back as the | 178 | // `vec![BLANK; n]` fills the grid with BLANK, and all boot |
| 179 | // underlying memory's leftover fill — the OptiPlex shows the | 179 | // output to this point is serial-only (boot markers are a |
| 180 | // firmware 0x00AAAAAA scrub there. This catches that. | 180 | // direct-FB blit, never the vt grid). The v0.5.7 check |
| 181 | // compared against 0 and therefore tripped on every healthy | ||
| 182 | // boot (the "0x70020 at grid(0,0)" mystery — the grid's own | ||
| 183 | // init value, with the probe loop's early `break` leaving the | ||
| 184 | // other three probe slots as zero-initialized). A stale cells | ||
| 185 | // data pointer that happens to be in-heap still passes the | ||
| 186 | // range checks (len/cap look fine) but its "cells" read back as | ||
| 187 | // the underlying memory's leftover fill — the OptiPlex shows | ||
| 188 | // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs | ||
| 189 | // scrub (0x00AAAAAA) is cleanly distinguishable. | ||
| 190 | let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); | ||
| 181 | let mut content_ok = true; | 191 | let mut content_ok = true; |
| 192 | let mut rcontent_ok = true; | ||
| 193 | let mut c_probes_raw: [u32; 4] = [0; 4]; | ||
| 194 | let mut r_probes_raw: [u32; 4] = [0; 4]; | ||
| 182 | if heap_ok && c.len() == total && total > 0 { | 195 | if heap_ok && c.len() == total && total > 0 { |
| 183 | let mut i = 0usize; | 196 | // Sample the first cell, the last cell, and two interior |
| 184 | // Sample the first cell, the last cell, and a few interior | ||
| 185 | // cells — reading the whole grid here would be ~60 KiB of | 197 | // cells — reading the whole grid here would be ~60 KiB of |
| 186 | // checks at every boot for no real benefit. | 198 | // checks at every boot for no real benefit. All four |
| 199 | // probes are recorded unconditionally (no early break) so | ||
| 200 | // a serial log shows every value. | ||
| 187 | let probes = [0usize, total / 3, (2 * total) / 3, total - 1]; | 201 | let probes = [0usize, total / 3, (2 * total) / 3, total - 1]; |
| 188 | for &p in probes.iter() { | 202 | for (pi, &p) in probes.iter().enumerate() { |
| 189 | if i >= probes.len() { | ||
| 190 | break; | ||
| 191 | } | ||
| 192 | i += 1; | ||
| 193 | let cell = *c.as_ptr().add(p); | 203 | let cell = *c.as_ptr().add(p); |
| 194 | let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); | 204 | let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); |
| 195 | if raw != 0 { | 205 | c_probes_raw[pi] = raw; |
| 206 | if raw != blank_raw { | ||
| 196 | content_ok = false; | 207 | content_ok = false; |
| 197 | break; | 208 | } |
| 209 | } | ||
| 210 | // Same probe on the rendered grid: it is a *separate* | ||
| 211 | // allocation, so its content discriminates "only the cells | ||
| 212 | // VecData's ptr field is stale" (rendered ok) from "both | ||
| 213 | // grids' memory was clobbered / both VecData ptrs are | ||
| 214 | // stale" (rendered garbage too). | ||
| 215 | for (pi, &p) in probes.iter().enumerate() { | ||
| 216 | let cell = *r.as_ptr().add(p); | ||
| 217 | let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); | ||
| 218 | r_probes_raw[pi] = raw; | ||
| 219 | if raw != blank_raw { | ||
| 220 | rcontent_ok = false; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | // Full-grid summary (cheap: a few thousand reads, serial-only). | ||
| 225 | // Definitive health signal on QEMU: healthy = every cell is | ||
| 226 | // BLANK (nz == blanks == total); the probes above are just the | ||
| 227 | // tripwire, this catches a partial clobber that the four | ||
| 228 | // samples happen to miss. | ||
| 229 | let (mut c_nz, mut c_blank) = (0usize, 0usize); | ||
| 230 | let (mut r_nz, mut r_blank) = (0usize, 0usize); | ||
| 231 | if heap_ok && c.len() == total && r.len() == total && total > 0 { | ||
| 232 | for i in 0..total { | ||
| 233 | let cell = *c.as_ptr().add(i); | ||
| 234 | let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); | ||
| 235 | if raw != 0 { | ||
| 236 | c_nz += 1; | ||
| 237 | } | ||
| 238 | if raw == blank_raw { | ||
| 239 | c_blank += 1; | ||
| 240 | } | ||
| 241 | let cell = *r.as_ptr().add(i); | ||
| 242 | let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24); | ||
| 243 | if raw != 0 { | ||
| 244 | r_nz += 1; | ||
| 245 | } | ||
| 246 | if raw == blank_raw { | ||
| 247 | r_blank += 1; | ||
| 198 | } | 248 | } |
| 199 | } | 249 | } |
| 200 | } | 250 | } |
| 201 | if c.len() != total || r.len() != total || !heap_ok || !content_ok { | 251 | msg(&alloc::format!( |
| 252 | "verify: grids c nz={} blank={} r nz={} blank={} (of {})", | ||
| 253 | c_nz, c_blank, r_nz, r_blank, total | ||
| 254 | )); | ||
| 255 | if c.len() != total || r.len() != total || !heap_ok || !content_ok || !rcontent_ok { | ||
| 202 | msg("verify: FAILED — cells/rendered not sane at shell entry"); | 256 | msg("verify: FAILED — cells/rendered not sane at shell entry"); |
| 203 | crate::gop::boot_marker(&alloc::format!( | 257 | msg(&alloc::format!( |
| 204 | "vt: cells {:#x} {}/{} {}", | 258 | "verify: probes c=[{:#x},{:#x},{:#x},{:#x}] r=[{:#x},{:#x},{:#x},{:#x}]", |
| 205 | c.as_ptr() as usize, | 259 | c_probes_raw[0], |
| 206 | c.len(), | 260 | c_probes_raw[1], |
| 207 | total, | 261 | c_probes_raw[2], |
| 208 | if content_ok { "range" } else { "CONTENT-GARBAGE" } | 262 | c_probes_raw[3], |
| 263 | r_probes_raw[0], | ||
| 264 | r_probes_raw[1], | ||
| 265 | r_probes_raw[2], | ||
| 266 | r_probes_raw[3] | ||
| 209 | )); | 267 | )); |
| 268 | // Bottom-anchored: row 0-1 get overwritten by the shell | ||
| 269 | // banner, row 2 by the prompt (and a stale crash.log | ||
| 270 | // replays at rows 2+), so this marker must live at the very | ||
| 271 | // bottom to stay readable until the user can copy it. | ||
| 272 | crate::gop::boot_marker_at( | ||
| 273 | &alloc::format!( | ||
| 274 | "vt: c {:#x} cap={:#x} {} | r {:#x} {}", | ||
| 275 | c.as_ptr() as usize, | ||
| 276 | c.capacity(), | ||
| 277 | if content_ok { "ok" } else { "GARBAGE" }, | ||
| 278 | r.as_ptr() as usize, | ||
| 279 | if rcontent_ok { "ok" } else { "GARBAGE" } | ||
| 280 | ), | ||
| 281 | crate::gop::bottom_row(1), | ||
| 282 | ); | ||
| 210 | } else { | 283 | } else { |
| 211 | msg("verify: ok"); | 284 | msg("verify: ok"); |
| 212 | crate::gop::boot_marker(&alloc::format!( | 285 | crate::gop::boot_marker_at( |
| 213 | "vt: cells {:#x} {}/{} ok", | 286 | &alloc::format!( |
| 214 | c.as_ptr() as usize, | 287 | "vt: c {:#x} cap={:#x} ok | r {:#x} ok", |
| 215 | c.len(), | 288 | c.as_ptr() as usize, |
| 216 | total | 289 | c.capacity(), |
| 217 | )); | 290 | r.as_ptr() as usize |
| 291 | ), | ||
| 292 | crate::gop::bottom_row(1), | ||
| 293 | ); | ||
| 218 | } | 294 | } |
| 219 | } | 295 | } |
| 220 | } | 296 | } |
