summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-03 00:21:52 +0200
committersalmonik <salmonik@tuta.io>2026-08-03 00:21:52 +0200
commit834b931d068a9a76e2c2a85267f24a22e93276cf (patch)
tree55fec8d47398a51a0c40dec8fba4f9664a12233a
parentv0.5.7: vt verify also as a boot marker (real hardware has no serial) (diff)
downloadrs-dos-834b931d068a9a76e2c2a85267f24a22e93276cf.tar.gz
rs-dos-834b931d068a9a76e2c2a85267f24a22e93276cf.zip
v0.5.7: guard dumps block-header math + 16-word window; vt content verify; shadow marker
The klog -t 2 crash drops a 126-byte String (the vt verify klog line) whose data ptr 0x40444970 points into the first-heap-block region whose content is the firmware 0x00AAAAAA scrub — a stale pointer, not a real allocation (back-pointer slot never written). Upgrades so the next real-hardware crash shows the full picture: - guard: explicit candidate block bases (b=ptr-24 / b16=ptr-32) with their size/free fields, plus a 16-word window (mem..mem4) from ptr-0x30 instead of the old 8 words - vt::debug_verify: content probe of the cells grid (must be all-BLANK at shell entry) — catches a stale in-heap cells pointer that passes the range checks - gop: on-screen marker for shadow carved vs NO-SHADOW fallback (the scrub content at the crash ptr suggests the shadow was rejected and its block stayed free + firmware-scrubbed)
-rw-r--r--rsnix/src/gop.rs7
-rw-r--r--rsnix/src/memory.rs120
-rw-r--r--rsnix/src/vt.rs33
3 files changed, 117 insertions, 43 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index 7aeb224..8e804e5 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -293,6 +293,13 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
293 .map(|c| core::str::from_utf8(c).unwrap_or("?")) 293 .map(|c| core::str::from_utf8(c).unwrap_or("?"))
294 .collect(); 294 .collect();
295 dump_lines(&lines); 295 dump_lines(&lines);
296 crate::gop::boot_marker("gop: NO-SHADOW (fallback mode)");
297 } else {
298 crate::gop::boot_marker(&alloc::format!(
299 "gop: shadow {:#x} {:.2} MiB",
300 SHADOW as usize,
301 swords as f64 * 4.0 / 1048576.0
302 ));
296 } 303 }
297 } 304 }
298 } 305 }
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
index e0f0142..f16f943 100644
--- a/rsnix/src/memory.rs
+++ b/rsnix/src/memory.rs
@@ -480,47 +480,84 @@ fn heap_guard_report(ptr: usize, rsp: usize, size: usize, align: usize) -> ! {
480 w.len() 480 w.len()
481 }; 481 };
482 482
483 // Words around the block header — the corruption lives in the 483 // Candidate block bases for this ptr (payload sits at block+24 for
484 // header's size/free slots or the payload. Only when the addresses 484 // align-1 allocs, block+32 for align-16) with their size/free
485 // are inside the mapped heap (reading a garbage pointer would fault 485 // fields, plus a longer raw window around ptr so the next crash
486 // mid-report). Four words per line: the fault band is ~110 chars 486 // shows the header region and neighbours, not just the payload.
487 // wide, so one long line would truncate under hand-copy.
488 let mut b4 = [0u8; 96]; 487 let mut b4 = [0u8; 96];
489 let mut b4b = [0u8; 96]; 488 let mut b4b = [0u8; 96];
490 let (n4, n4b) = { 489 let mut b4c = [0u8; 96];
491 let focus = if block >= hs && block < he { block } else { ptr }; 490 let mut b4d = [0u8; 96];
492 if focus >= hs + 8 && focus < he { 491 let focus = if block >= hs && block < he { block } else { ptr };
493 let lo = (focus - 16).max(hs); 492 let mut a = (focus.wrapping_sub(48)).max(hs) & !7;
494 let hi = (focus + 40).min(he - 8); 493 let (mut n4, mut n4b, mut n4c, mut n4d) = (0usize, 0usize, 0usize, 0usize);
495 let mut w = crate::strbuf::FmtBuf::new(&mut b4); 494 if focus >= hs + 8 && focus < he {
496 let _ = fmt::write(&mut w, core::format_args!("mem:")); 495 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
497 let mut a = lo & !7; 496 let _ = fmt::write(&mut w, core::format_args!("mem:"));
498 let mut n = 0; 497 for _ in 0..4 {
499 while a <= hi && n < 4 { 498 let _ = fmt::write(
500 let _ = fmt::write( 499 &mut w,
501 &mut w, 500 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
502 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }), 501 );
503 ); 502 a += 8;
504 a += 8; 503 }
505 n += 1; 504 n4 = w.len();
506 } 505 let mut w = crate::strbuf::FmtBuf::new(&mut b4b);
507 let mut w2 = crate::strbuf::FmtBuf::new(&mut b4b); 506 let _ = fmt::write(&mut w, core::format_args!("mem2:"));
508 let _ = fmt::write(&mut w2, core::format_args!("mem2:")); 507 for _ in 0..4 {
509 let mut n = 0; 508 let _ = fmt::write(
510 while a <= hi && n < 4 { 509 &mut w,
511 let _ = fmt::write( 510 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
512 &mut w2, 511 );
513 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }), 512 a += 8;
514 );
515 a += 8;
516 n += 1;
517 }
518 (w.len(), w2.len())
519 } else {
520 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
521 let _ = fmt::write(&mut w, core::format_args!("mem: out of heap — no dump"));
522 (w.len(), 0)
523 } 513 }
514 n4b = w.len();
515 let mut w = crate::strbuf::FmtBuf::new(&mut b4c);
516 let _ = fmt::write(&mut w, core::format_args!("mem3:"));
517 for _ in 0..4 {
518 let _ = fmt::write(
519 &mut w,
520 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
521 );
522 a += 8;
523 }
524 n4c = w.len();
525 let mut w = crate::strbuf::FmtBuf::new(&mut b4d);
526 let _ = fmt::write(&mut w, core::format_args!("mem4:"));
527 for _ in 0..4 {
528 let _ = fmt::write(
529 &mut w,
530 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
531 );
532 a += 8;
533 }
534 n4d = w.len();
535 } else {
536 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
537 let _ = fmt::write(&mut w, core::format_args!("mem: out of heap — no dump"));
538 n4 = w.len();
539 };
540 // Explicit candidate-block header fields (size/free at +0/+8).
541 let mut b4e = [0u8; 96];
542 let ne = if ptr >= hs + 24 && ptr <= he {
543 let hb = ptr - 24;
544 let hb2 = ptr - 32;
545 let mut w = crate::strbuf::FmtBuf::new(&mut b4e);
546 let _ = fmt::write(
547 &mut w,
548 core::format_args!(
549 "hdr: b={:#x} s={:#x} f={:#x} b16={:#x} s={:#x} f={:#x}",
550 hb,
551 unsafe { *(hb as *const usize) },
552 unsafe { *((hb + 8) as *const usize) },
553 hb2,
554 unsafe { *(hb2 as *const usize) },
555 unsafe { *((hb2 + 8) as *const usize) },
556 ),
557 );
558 w.len()
559 } else {
560 0
524 }; 561 };
525 562
526 // Live return-address chain on the stack above our rsp (dealloc ← 563 // Live return-address chain on the stack above our rsp (dealloc ←
@@ -563,15 +600,18 @@ fn heap_guard_report(ptr: usize, rsp: usize, size: usize, align: usize) -> ! {
563 let l3b = core::str::from_utf8(&b3b[..n3b]).unwrap_or(""); 600 let l3b = core::str::from_utf8(&b3b[..n3b]).unwrap_or("");
564 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or(""); 601 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or("");
565 let l4b = if n4b > 0 { core::str::from_utf8(&b4b[..n4b]).unwrap_or("") } else { "" }; 602 let l4b = if n4b > 0 { core::str::from_utf8(&b4b[..n4b]).unwrap_or("") } else { "" };
603 let l4c = if n4c > 0 { core::str::from_utf8(&b4c[..n4c]).unwrap_or("") } else { "" };
604 let l4d = if n4d > 0 { core::str::from_utf8(&b4d[..n4d]).unwrap_or("") } else { "" };
605 let l4e = if ne > 0 { core::str::from_utf8(&b4e[..ne]).unwrap_or("") } else { "" };
566 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or(""); 606 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or("");
567 let l6 = core::str::from_utf8(&b6[..n6]).unwrap_or(""); 607 let l6 = core::str::from_utf8(&b6[..n6]).unwrap_or("");
568 let l7 = core::str::from_utf8(&b7[..n7]).unwrap_or(""); 608 let l7 = core::str::from_utf8(&b7[..n7]).unwrap_or("");
569 let l8 = core::str::from_utf8(&b8[..n8]).unwrap_or(""); 609 let l8 = core::str::from_utf8(&b8[..n8]).unwrap_or("");
570 for l in [l2, l3, l3b, l4, l4b, l5, l6, l7, l8] { 610 for l in [l2, l3, l3b, l4, l4b, l4c, l4d, l4e, l5, l6, l7, l8] {
571 crate::serial::write_raw(l); 611 crate::serial::write_raw(l);
572 crate::serial::write_raw("\r\n"); 612 crate::serial::write_raw("\r\n");
573 } 613 }
574 let lines = [l1, l2, l3, l3b, l4, l4b, l5, l6, l7, l8]; 614 let lines = [l1, l2, l3, l3b, l4, l4b, l4c, l4d, l4e, l5, l6, l7, l8];
575 crate::gop::dump_lines(&lines); 615 crate::gop::dump_lines(&lines);
576 crate::fs::write_crash_log(&lines); 616 crate::fs::write_crash_log(&lines);
577 crate::interrupts::halt() 617 crate::interrupts::halt()
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 2d8ece6..2906258 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -172,13 +172,40 @@ pub fn debug_verify() {
172 p >= hs && p + bytes <= he 172 p >= hs && p + bytes <= he
173 }) 173 })
174 .unwrap_or(false); 174 .unwrap_or(false);
175 if c.len() != total || r.len() != total || !heap_ok { 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
177 // pointer that happens to be in-heap still passes the range
178 // checks (len/cap look fine) but its "cells" read back as the
179 // underlying memory's leftover fill — the OptiPlex shows the
180 // firmware 0x00AAAAAA scrub there. This catches that.
181 let mut content_ok = true;
182 if heap_ok && c.len() == total && total > 0 {
183 let mut i = 0usize;
184 // Sample the first cell, the last cell, and a few interior
185 // cells — reading the whole grid here would be ~60 KiB of
186 // checks at every boot for no real benefit.
187 let probes = [0usize, total / 3, (2 * total) / 3, total - 1];
188 for &p in probes.iter() {
189 if i >= probes.len() {
190 break;
191 }
192 i += 1;
193 let cell = *c.as_ptr().add(p);
194 let raw = cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24);
195 if raw != 0 {
196 content_ok = false;
197 break;
198 }
199 }
200 }
201 if c.len() != total || r.len() != total || !heap_ok || !content_ok {
176 msg("verify: FAILED — cells/rendered not sane at shell entry"); 202 msg("verify: FAILED — cells/rendered not sane at shell entry");
177 crate::gop::boot_marker(&alloc::format!( 203 crate::gop::boot_marker(&alloc::format!(
178 "vt: cells {:#x} {}/{} FAILED", 204 "vt: cells {:#x} {}/{} {}",
179 c.as_ptr() as usize, 205 c.as_ptr() as usize,
180 c.len(), 206 c.len(),
181 total 207 total,
208 if content_ok { "range" } else { "CONTENT-GARBAGE" }
182 )); 209 ));
183 } else { 210 } else {
184 msg("verify: ok"); 211 msg("verify: ok");