summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--shell/src/lib.rs129
1 files changed, 129 insertions, 0 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 175a2c7..0f5ac4b 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -716,6 +716,7 @@ write `{} <path> TIL <marker>`",
716 "hello" => cmd_hello(&parsed), 716 "hello" => cmd_hello(&parsed),
717 "info" => cmd_info(&parsed), 717 "info" => cmd_info(&parsed),
718 "mem" => cmd_mem(&parsed), 718 "mem" => cmd_mem(&parsed),
719 "heapdbg" => cmd_heapdbg(&parsed),
719 "mmap" => cmd_mmap(&parsed), 720 "mmap" => cmd_mmap(&parsed),
720 "stress" => cmd_stress(&parsed), 721 "stress" => cmd_stress(&parsed),
721 "date" => cmd_date(&parsed), 722 "date" => cmd_date(&parsed),
@@ -1571,6 +1572,134 @@ fn cmd_halt() -> ! {
1571 rsnix::power::halt() 1572 rsnix::power::halt()
1572} 1573}
1573 1574
1575/// Debug the crash-family: free-list state + the historical crash
1576/// offset (heap+0x443e0) + the shadow block's header. On the OptiPlex
1577/// this shows whether the shadow span leaked into the free list and
1578/// whether the probe region is zeroed/0xAA before any command runs.
1579fn cmd_heapdbg(_p: &Parsed) {
1580 let h = rsnix::memory::heap_stats();
1581 term_println(&alloc::format!(
1582 "heap: {:#x}..{:#x} cap={} MiB free={} KiB blocks={}",
1583 h.heap_start,
1584 h.heap_end,
1585 h.heap_size / 1048576,
1586 h.free_bytes / 1024,
1587 h.free_blocks
1588 ));
1589 for l in rsnix::memory::freelist_lines(12) {
1590 term_println(&l);
1591 }
1592 // fb vs heap overlap — OptiPlex crash hypothesis: the display's
1593 // framebuffer may sit inside the heap region (GPU BAR not reserved in
1594 // the EFI map), so display writes clobber live heap allocations.
1595 if let Some((fb, fsz)) = rsnix::gop::framebuffer_region() {
1596 let ov = h.heap_start < fb.saturating_add(fsz) && fb < h.heap_end;
1597 term_println(&alloc::format!(
1598 "fb {:#x} size={:#x} overlap-heap={}",
1599 fb, fsz, ov
1600 ));
1601 }
1602 let probe = h.heap_start.wrapping_add(0x443e0); // historical crash offset
1603 match rsnix::memory::freelist_contains(probe) {
1604 Some((b, s, off)) => term_println(&alloc::format!(
1605 "probe {:#x} (+0x443e0) IN free blk {:#x} size={:#x} off={:#x}",
1606 probe, b, s, off
1607 )),
1608 None => term_println(&alloc::format!("probe {:#x} (+0x443e0) NOT free", probe)),
1609 }
1610 let mut s = alloc::string::String::new();
1611 unsafe {
1612 for i in 0..8 {
1613 if i > 0 {
1614 s.push(' ');
1615 }
1616 let v = *(probe as *const u32).add(i);
1617 s.push_str(&alloc::format!("{:08x}", v));
1618 }
1619 }
1620 term_println(&alloc::format!("probe mem: {}", s));
1621 // Scan the live shadow payload for 0x00aaaaaa words. NOTE: this is
1622 // the GRAY text color (rgb(170,170,170) = PALETTE[7] = fg 7), NOT a
1623 // firmware scrub — the "0x00AAAAAA fill" in the OptiPlex crash dumps
1624 // is just rendered text pixels. The scan's real value: untouched
1625 // shadow rows must be 0; non-zero far rows = out-of-bounds writes.
1626 if let Some((b, size, _)) = rsnix::gop::shadow_block_state() {
1627 let pay = b + 32;
1628 let words = (size.saturating_sub(32)) / 4;
1629 let mut n = 0u32;
1630 let mut first: [usize; 8] = [0; 8];
1631 let mut nf = 0usize;
1632 unsafe {
1633 let mut i = 0usize;
1634 let cap = words.min(240_000); // bound the scan to avoid stalls
1635 while i < cap {
1636 let v = *(pay as *const u32).add(i);
1637 if v == 0x00aa_aaaa {
1638 if nf < first.len() {
1639 first[nf] = i;
1640 }
1641 nf += 1;
1642 n += 1;
1643 }
1644 i += 1;
1645 }
1646 }
1647 let mut fs = alloc::string::String::new();
1648 for &f in first[..nf.min(8)].iter() {
1649 fs.push_str(&alloc::format!("@{}(r{}c{}) ", f, f / 800, (f % 800) / 8));
1650 }
1651 term_println(&alloc::format!(
1652 "shadow scan: {}w/{} ({}%) {}",
1653 n,
1654 words,
1655 (n * 1000 / (words as u32).max(1)) / 10,
1656 fs
1657 ));
1658 // Probe far rows (untouched by text) to check the init zeroing.
1659 let mut d = alloc::string::String::new();
1660 unsafe {
1661 for (label, off) in [("r100", 80_000usize), ("r300", 240_000), ("r500", 400_000)] {
1662 let w0 = *(pay as *const u32).add(off);
1663 let w1 = *(pay as *const u32).add(off + 1);
1664 d.push_str(&alloc::format!(" {}={:08x}{:08x}", label, w0, w1));
1665 }
1666 }
1667 term_println(&alloc::format!("shadow far: {}", d));
1668 // Underlying cell/rendered values at the first 0x00aaaaaa (gray
1669 // text pixel) hit, if any: confirms the shadow word is the glyph
1670 // pixel of a fg=7 cell (consistent) vs a shadow write outside
1671 // the vt path (inconsistent).
1672 if nf > 0 {
1673 let f = first[0];
1674 let tr = f / 800 / 16;
1675 let tc = (f / 8) % 100;
1676 let (rc, rr) = rsnix::vt::cell_raw(tr, tc);
1677 term_println(&alloc::format!(
1678 "shadow@{:x} cell={:08x} rendered={:08x}",
1679 f, rc, rr
1680 ));
1681 }
1682 }
1683 match rsnix::gop::shadow_block_state() {
1684 Some((b, size, free)) => {
1685 term_println(&alloc::format!(
1686 "shadow blk {:#x} size={:#x} free={}",
1687 b,
1688 size,
1689 free
1690 ));
1691 match rsnix::memory::freelist_contains(b) {
1692 Some((fb, fs, fo)) => term_println(&alloc::format!(
1693 "shadow base IN free list: blk {:#x} size={:#x} off={:#x}",
1694 fb, fs, fo
1695 )),
1696 None => term_println("shadow base NOT in free list"),
1697 }
1698 }
1699 None => term_println("shadow: NONE (no-shadow mode)"),
1700 }
1701}
1702
1574fn cmd_date(p: &Parsed) { 1703fn cmd_date(p: &Parsed) {
1575 let t = rsnix::rtc::now(); 1704 let t = rsnix::rtc::now();
1576 let mut pipe = Pipeline::new(&p.filters, &p.output); 1705 let mut pipe = Pipeline::new(&p.filters, &p.output);