summaryrefslogtreecommitdiffstats
path: root/rsnix/src/memory.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-04 01:44:05 +0200
committersalmonik <salmonik@tuta.io>2026-08-04 01:44:05 +0200
commit3e2261d83176752f3ea21e0c97f330b4c1757437 (patch)
tree8bbc92f917121fafaf193f1af1c42fba624abda9 /rsnix/src/memory.rs
parentv0.5.8: heap-free QR encoder + crash-dump QR on framebuffer (diff)
downloadrs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.tar.gz
rs-dos-3e2261d83176752f3ea21e0c97f330b4c1757437.zip
v0.5.9: fix OptiPlex heap crash — EBS retry must not rebuild the free list
Root cause: on real hardware SetMode can invalidate the pre-captured ExitBootServices map key, so EBS fails and the retry refreshed the map via capture_memory_map -> init_heap, which REBUILT the free list from the raw map and wiped the allocation state of every pre-EBS live object (the display shadow and the vt grids). Their spans became free while still live, later allocations carved inside the shadow, and the display writes clobbered them — zeroed back-pointers (family A ptr=0x404443e0 block=0x0) and gray glyph pixels (family B 0xaaaaaa00aaaaaa = 0x00AAAAAA = PALETTE[7] fg-7 text colour, not a firmware scrub). Fix: refresh_and_exit now re-captures the map and returns a fresh EBS key without rebuilding the free list (refresh_map_key); the free list is the authoritative live-allocation state. Verified in QEMU under a forced EBS failure (SIMULATE_EBS_FAILURE test hook): verify ok, probe NOT free, flhead outside the shadow — identical to a clean boot, and the full shell sequence is crash-free. Also: removed the crash-family debug markers from the screen (boot-time heapdbg lines, vt verify marker, shadow marker) — the vt grid self-check stays serial-only. Version 0.5.8 -> 0.5.9.
Diffstat (limited to 'rsnix/src/memory.rs')
-rw-r--r--rsnix/src/memory.rs107
1 files changed, 105 insertions, 2 deletions
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
index 3ba53fc..099f607 100644
--- a/rsnix/src/memory.rs
+++ b/rsnix/src/memory.rs
@@ -624,6 +624,20 @@ fn heap_guard_report(ptr: usize, rsp: usize, size: usize, align: usize) -> ! {
624/// .bss working buffer so no pool allocation (and thus no memory-map change) 624/// .bss working buffer so no pool allocation (and thus no memory-map change)
625/// occurs between GetMemoryMap and ExitBootServices. 625/// occurs between GetMemoryMap and ExitBootServices.
626pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> { 626pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> {
627 capture_memory_map_impl(bs, true)
628}
629
630/// Re-capture the memory map and return a FRESH ExitBootServices key
631/// WITHOUT rebuilding the heap free list. Used by the EBS retry path:
632/// a rebuild from the raw map wipes the allocation state of every
633/// pre-EBS live allocation (the display shadow, the vt grids) — their
634/// spans then become free while still in use, and later allocations
635/// land inside the live shadow (the OptiPlex crash family).
636pub fn refresh_map_key(bs: &BootServices) -> Result<usize, Status> {
637 capture_memory_map_impl(bs, false)
638}
639
640fn capture_memory_map_impl(bs: &BootServices, rebuild: bool) -> Result<usize, Status> {
627 let mut map_key: usize = 0; 641 let mut map_key: usize = 0;
628 let mut desc_size: usize = 0; 642 let mut desc_size: usize = 0;
629 let mut desc_ver: u32 = 0; 643 let mut desc_ver: u32 = 0;
@@ -694,7 +708,9 @@ pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> {
694 // Do NOT free anything — the static buffer stays. Map key is still 708 // Do NOT free anything — the static buffer stays. Map key is still
695 // valid because no pool allocation/free happened after GetMemoryMap. 709 // valid because no pool allocation/free happened after GetMemoryMap.
696 710
697 init_heap(); 711 if rebuild {
712 init_heap();
713 }
698 Ok(map_key) 714 Ok(map_key)
699} 715}
700 716
@@ -705,7 +721,13 @@ pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> {
705/// `image_handle` must be the valid EFI image handle this kernel was 721/// `image_handle` must be the valid EFI image handle this kernel was
706/// started with (opaque firmware pointer; never dereferenced here). 722/// started with (opaque firmware pointer; never dereferenced here).
707pub unsafe fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Result<usize, Status> { 723pub unsafe fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Result<usize, Status> {
708 let key = capture_memory_map(bs)?; 724 // Fresh key WITHOUT rebuilding the heap free list: a rebuild from the
725 // raw map wipes the allocation state of every pre-EBS live allocation
726 // (the display shadow, the vt grids) — their spans then become free
727 // while still in use, and later allocations land inside the live
728 // shadow (the OptiPlex crash family: display writes clobber heap
729 // objects, zeroed back-pointers, gray-text pixels).
730 let key = refresh_map_key(bs)?;
709 let status = unsafe { (bs.exit_boot_services)(image_handle, key) }; 731 let status = unsafe { (bs.exit_boot_services)(image_handle, key) };
710 if status == EFI_SUCCESS { 732 if status == EFI_SUCCESS {
711 Ok(key) 733 Ok(key)
@@ -833,6 +855,87 @@ pub fn heap_stats() -> HeapStats {
833 } 855 }
834} 856}
835 857
858/// DEBUG (crash-family): which free block contains `addr`, if any.
859/// Returns `(block_base, block_size, offset_within_block)`.
860///
861/// The OptiPlex heap-corruption dumps land at deterministic offsets
862/// inside the shadow's would-be span; this tells us whether the shadow
863/// block (or any block spanning that offset) has leaked back into the
864/// free list at shell entry or later.
865pub fn freelist_contains(addr: usize) -> Option<(usize, usize, usize)> {
866 unsafe {
867 let mut cur = FREE_LIST;
868 while !cur.is_null() {
869 let base = cur as usize;
870 let size = (*cur).size;
871 if addr >= base && addr < base.saturating_add(size) {
872 return Some((base, size, addr - base));
873 }
874 cur = (*(*cur).links()).next;
875 }
876 None
877 }
878}
879
880/// DEBUG (crash-family): the free list head block (base, size).
881pub fn freelist_head() -> Option<(usize, usize)> {
882 unsafe {
883 if FREE_LIST.is_null() {
884 None
885 } else {
886 Some((FREE_LIST as usize, (*FREE_LIST).size))
887 }
888 }
889}
890
891/// DEBUG (OptiPlex sim): force the first ExitBootServices call to fail,
892/// exercising the capture_memory_map -> init_heap free-list rebuild that
893/// wiped the pre-EBS shadow on real hardware. Returns true when the
894/// simulation is armed (one shot).
895pub static mut SIMULATE_EBS_FAILURE: bool = false;
896pub fn simulate_ebs_failure() -> bool {
897 unsafe {
898 let r = SIMULATE_EBS_FAILURE;
899 SIMULATE_EBS_FAILURE = false;
900 r
901 }
902}
903
904/// DEBUG (crash-family): walk the free list, formatting at most `max`
905/// blocks as text lines for the screen/serial.
906///
907/// Snapshot the walk into a fixed stack array first: printing the lines
908/// itself allocates, and an interleaved alloc/free would shift the list
909/// under the walk (the shell's tty path allocates per line).
910pub fn freelist_lines(max: usize) -> alloc::vec::Vec<alloc::string::String> {
911 let mut out = alloc::vec::Vec::new();
912 unsafe {
913 let mut cur = FREE_LIST;
914 let cap = max.min(24);
915 let mut snap: [(usize, usize); 24] = [(0, 0); 24];
916 let mut seen = 0usize;
917 while !cur.is_null() && seen < cap {
918 snap[seen] = (cur as usize, (*cur).size);
919 seen += 1;
920 cur = (*(*cur).links()).next;
921 }
922 if seen == 0 {
923 out.push("fl[] EMPTY".into());
924 } else {
925 for (base, size) in snap[..seen].iter() {
926 out.push(alloc::format!(
927 "fl[{}] {:#x} size={:#x} end={:#x}",
928 out.len(),
929 base,
930 size,
931 base.wrapping_add(*size)
932 ));
933 }
934 }
935 }
936 out
937}
938
836/// Allocator smoke test — exercises alloc/free/coalesce without touching 939/// Allocator smoke test — exercises alloc/free/coalesce without touching
837/// the heap itself (fixed-size stack arrays only). Returns true on success. 940/// the heap itself (fixed-size stack arrays only). Returns true on success.
838pub fn self_test() -> bool { 941pub fn self_test() -> bool {