summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 22:46:59 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 22:46:59 +0200
commite649a3de7d853bf82a9105a2cb3869c7b8c96108 (patch)
treeec4707aad3437aa995129fff34aa5ac81e3cf1c1
parentv0.5.7: heap range on the boot screen — real-hardware geometry without a crash (diff)
downloadrs-dos-e649a3de7d853bf82a9105a2cb3869c7b8c96108.tar.gz
rs-dos-e649a3de7d853bf82a9105a2cb3869c7b8c96108.zip
v0.5.7: guard report 4-words-per-line — 16 stack words survive band hand-copy
The OptiPlex repro copies are truncated at the ~110-char band width, so the old single-line 'stk:' (12 words, >200 chars) lost the return chain after a few words. Split mem/stk dumps into 4-words-per-line blocks: mem+mem2 (8 words around the corruption) and stk..stk4 (16 words) — the drop caller chain now survives hand transcription.
-rw-r--r--rsdos/src/main.rs29
-rw-r--r--rsnix/src/memory.rs84
2 files changed, 89 insertions, 24 deletions
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index cbbf2ba..806f150 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -344,9 +344,38 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
344 } else { 344 } else {
345 rsnix::serial_println!("cursor: no mouse — overlay disabled"); 345 rsnix::serial_println!("cursor: no mouse — overlay disabled");
346 } 346 }
347 // TEST HOOK: reproduce the OptiPlex '0xaaaaaa' uninit-drop crash in
348 // QEMU. The real hardware's stack carries 0xAA fill left by
349 // firmware; any shell value that is dropped without being written
350 // then frees garbage and trips the dealloc guard. Filling the
351 // shell's future stack region with 0xAA here makes the same bug
352 // visible on QEMU (serial + gdb available) instead of only on the
353 // OptiPlex.
354 stack_fill_aa();
347 shell::run(); 355 shell::run();
348} 356}
349 357
358/// Write 0xAA over the stack region the shell's frames will reuse,
359/// leaving the current frames (top 4 KiB) intact. 128 KiB covers the
360/// whole shell frame chain on QEMU and the OptiPlex. Best-effort: the
361/// region is below rsp inside the boot stack mapping.
362fn stack_fill_aa() {
363 let rsp: usize;
364 unsafe {
365 core::arch::asm!("mov {}, rsp", out(reg) rsp);
366 }
367 let top = rsp - 0x1000;
368 let bottom = rsp - 0x21000;
369 unsafe {
370 let mut p = bottom;
371 while p < top {
372 core::ptr::write_volatile(p as *mut u64, 0xAAAAAAAAAAAAAAAA);
373 p += 8;
374 }
375 }
376 rsnix::serial_println!("stack: 0xAA-fill 0x{:x}..0x{:x}", bottom, top);
377}
378
350// --------------------------------------------------------------------------- 379// ---------------------------------------------------------------------------
351// Print a memory summary from the stashed memory map 380// Print a memory summary from the stashed memory map
352// --------------------------------------------------------------------------- 381// ---------------------------------------------------------------------------
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
index 3362d94..ecaf806 100644
--- a/rsnix/src/memory.rs
+++ b/rsnix/src/memory.rs
@@ -405,58 +405,94 @@ fn heap_guard_report(ptr: usize, rsp: usize) -> ! {
405 // Words around the block header — the corruption lives in the 405 // Words around the block header — the corruption lives in the
406 // header's size/free slots or the payload. Only when the addresses 406 // header's size/free slots or the payload. Only when the addresses
407 // are inside the mapped heap (reading a garbage pointer would fault 407 // are inside the mapped heap (reading a garbage pointer would fault
408 // mid-report). 408 // mid-report). Four words per line: the fault band is ~110 chars
409 let mut b4 = [0u8; 216]; 409 // wide, so one long line would truncate under hand-copy.
410 let n4 = { 410 let mut b4 = [0u8; 96];
411 let mut w = crate::strbuf::FmtBuf::new(&mut b4); 411 let mut b4b = [0u8; 96];
412 let (n4, n4b) = {
412 let focus = if block >= hs && block < he { block } else { ptr }; 413 let focus = if block >= hs && block < he { block } else { ptr };
413 if focus >= hs + 8 && focus < he { 414 if focus >= hs + 8 && focus < he {
414 let lo = (focus - 16).max(hs); 415 let lo = (focus - 16).max(hs);
415 let hi = (focus + 40).min(he - 8); 416 let hi = (focus + 40).min(he - 8);
417 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
416 let _ = fmt::write(&mut w, core::format_args!("mem:")); 418 let _ = fmt::write(&mut w, core::format_args!("mem:"));
417 let mut a = lo & !7; 419 let mut a = lo & !7;
418 while a <= hi { 420 let mut n = 0;
421 while a <= hi && n < 4 {
419 let _ = fmt::write( 422 let _ = fmt::write(
420 &mut w, 423 &mut w,
421 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }), 424 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
422 ); 425 );
423 a += 8; 426 a += 8;
427 n += 1;
424 } 428 }
429 let mut w2 = crate::strbuf::FmtBuf::new(&mut b4b);
430 let _ = fmt::write(&mut w2, core::format_args!("mem2:"));
431 let mut n = 0;
432 while a <= hi && n < 4 {
433 let _ = fmt::write(
434 &mut w2,
435 core::format_args!(" {:x}={:#x}", a, unsafe { *(a as *const usize) }),
436 );
437 a += 8;
438 n += 1;
439 }
440 (w.len(), w2.len())
425 } else { 441 } else {
442 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
426 let _ = fmt::write(&mut w, core::format_args!("mem: out of heap — no dump")); 443 let _ = fmt::write(&mut w, core::format_args!("mem: out of heap — no dump"));
444 (w.len(), 0)
427 } 445 }
428 w.len()
429 }; 446 };
430 447
431 // Live return-address chain on the stack above our rsp (dealloc ← 448 // Live return-address chain on the stack above our rsp (dealloc ←
432 // drop glue ← shell::run); raw stack words, resolves against the PDB. 449 // drop glue ← shell::run); raw stack words, resolves against the PDB.
433 let mut b5 = [0u8; 216]; 450 // 16 words in 4 lines of 4 — enough to capture the drop caller
434 let n5 = { 451 // chain on the band even when the copy truncates a line.
435 let mut w = crate::strbuf::FmtBuf::new(&mut b5); 452 let mut b5 = [0u8; 96];
436 let _ = fmt::write(&mut w, core::format_args!("stk:")); 453 let mut b6 = [0u8; 96];
454 let mut b7 = [0u8; 96];
455 let mut b8 = [0u8; 96];
456 let (n5, n6, n7, n8) = {
437 let mut a = rsp.wrapping_add(8); 457 let mut a = rsp.wrapping_add(8);
438 for _ in 0..12 { 458 let bufs: [&mut [u8]; 4] = [&mut b5, &mut b6, &mut b7, &mut b8];
439 let v = unsafe { *(a as *const usize) }; 459 let mut lens = [0usize; 4];
440 let _ = fmt::write(&mut w, core::format_args!(" {:x}={:#x}", a, v)); 460 for li in 0..4 {
441 a += 8; 461 let mut w = crate::strbuf::FmtBuf::new(bufs[li]);
462 let lbl = match li {
463 0 => "",
464 1 => "2",
465 2 => "3",
466 _ => "4",
467 };
468 let _ = fmt::write(
469 &mut w,
470 core::format_args!("stk{}:", lbl),
471 );
472 for _ in 0..4 {
473 let v = unsafe { *(a as *const usize) };
474 let _ = fmt::write(&mut w, core::format_args!(" {:x}={:#x}", a, v));
475 a += 8;
476 }
477 lens[li] = w.len();
442 } 478 }
443 w.len() 479 (lens[0], lens[1], lens[2], lens[3])
444 }; 480 };
445 481
446 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!"); 482 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!");
447 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or(""); 483 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or("");
448 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or(""); 484 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or("");
449 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or(""); 485 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or("");
486 let l4b = if n4b > 0 { core::str::from_utf8(&b4b[..n4b]).unwrap_or("") } else { "" };
450 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or(""); 487 let l5 = core::str::from_utf8(&b5[..n5]).unwrap_or("");
451 crate::serial::write_raw(l2); 488 let l6 = core::str::from_utf8(&b6[..n6]).unwrap_or("");
452 crate::serial::write_raw("\r\n"); 489 let l7 = core::str::from_utf8(&b7[..n7]).unwrap_or("");
453 crate::serial::write_raw(l3); 490 let l8 = core::str::from_utf8(&b8[..n8]).unwrap_or("");
454 crate::serial::write_raw("\r\n"); 491 for l in [l2, l3, l4, l4b, l5, l6, l7, l8] {
455 crate::serial::write_raw(l4); 492 crate::serial::write_raw(l);
456 crate::serial::write_raw("\r\n"); 493 crate::serial::write_raw("\r\n");
457 crate::serial::write_raw(l5); 494 }
458 crate::serial::write_raw("\r\n"); 495 let lines = [l1, l2, l3, l4, l4b, l5, l6, l7, l8];
459 let lines = [l1, l2, l3, l4, l5];
460 crate::gop::dump_lines(&lines); 496 crate::gop::dump_lines(&lines);
461 crate::fs::write_crash_log(&lines); 497 crate::fs::write_crash_log(&lines);
462 crate::interrupts::halt() 498 crate::interrupts::halt()