diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-02 22:46:59 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-02 22:46:59 +0200 |
| commit | e649a3de7d853bf82a9105a2cb3869c7b8c96108 (patch) | |
| tree | ec4707aad3437aa995129fff34aa5ac81e3cf1c1 | |
| parent | v0.5.7: heap range on the boot screen — real-hardware geometry without a crash (diff) | |
| download | rs-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.rs | 29 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 84 |
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. | ||
| 362 | fn 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() |
