summaryrefslogtreecommitdiffstats
path: root/rsnix/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/gop.rs51
-rw-r--r--rsnix/src/memory.rs107
-rw-r--r--rsnix/src/vt.rs69
3 files changed, 181 insertions, 46 deletions
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index ecf8fa9..ccc6509 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -242,16 +242,20 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
242 unsafe { 242 unsafe {
243 FB = Some(fbf); 243 FB = Some(fbf);
244 } 244 }
245 // The RAM backbuffer is the *first* heap allocation. It is allocated
246 // pre-ExitBootServices (before vt::init builds its grids) so it sits at
247 // the front of the free list; the heap free list already excludes the
248 // fb region (init_heap split at the fb hole) and the range-overlap
249 // check below cannot alias the display. An ExitBootServices retry on
250 // real hardware no longer rebuilds the free list (see
251 // memory::refresh_map_key), so this pre-EBS allocation is preserved.
252 shadow_late_init();
253}
245 254
246 // The RAM backbuffer is the *first* heap allocation: allocated here 255/// Allocate the RAM backbuffer as the FIRST heap allocation (called from
247 // (pre-ExitBootServices, before vt::init builds its grids) so no 256/// `init`, pre-ExitBootServices). Degrades to no-shadow mode (never
248 // other heap object can collide with it — the v0.5.3 layout that 257/// panics) when the range would overlap the framebuffer or the alloc fails.
249 // provably boots on real hardware. The heap free list excludes the 258pub fn shadow_late_init() {
250 // fb region (init_heap splits at the fb hole) and we additionally
251 // reject any range overlapping the fb, so the shadow can never
252 // alias the display (an aliased shadow turns every scroll into a
253 // 1.8 MB MMIO→MMIO memcpy on an uncached real fb — a wedge).
254 // Allocation failure degrades to no-shadow mode, never panics.
255 unsafe { 259 unsafe {
256 if let Some(fbinfo) = fb().as_ref() { 260 if let Some(fbinfo) = fb().as_ref() {
257 let words = (fbinfo.pitch * fbinfo.height) / 4; 261 let words = (fbinfo.pitch * fbinfo.height) / 4;
@@ -293,16 +297,6 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
293 .map(|c| core::str::from_utf8(c).unwrap_or("?")) 297 .map(|c| core::str::from_utf8(c).unwrap_or("?"))
294 .collect(); 298 .collect();
295 dump_lines(&lines); 299 dump_lines(&lines);
296 crate::gop::boot_marker_at("gop: NO-SHADOW (fallback)", bottom_row(0));
297 } else {
298 crate::gop::boot_marker_at(
299 &alloc::format!(
300 "gop: shadow {:#x} {:.2} MiB",
301 SHADOW as usize,
302 swords as f64 * 4.0 / 1048576.0
303 ),
304 bottom_row(0),
305 );
306 } 300 }
307 } 301 }
308 } 302 }
@@ -319,6 +313,25 @@ pub fn boot_marker(msg: &str) {
319 boot_marker_at(msg, 0); 313 boot_marker_at(msg, 0);
320} 314}
321 315
316/// DEBUG (crash-family): read the shadow block's header words.
317/// Returns `(block_base, size, free_flag)` if a shadow is live.
318///
319/// The shadow is the first heap allocation: payload at base+32, header
320/// at base. A live shadow shows `free == 0`; if its block ever leaked
321/// back into the free list (while `SHADOW` still points at it), later
322/// allocations carve into the zeroed span and the display writes keep
323/// zeroing them — the OptiPlex crash family signature.
324pub fn shadow_block_state() -> Option<(usize, usize, usize)> {
325 unsafe {
326 if SHADOW.is_null() {
327 return None;
328 }
329 let base = (SHADOW as usize).wrapping_sub(32);
330 let size = *(base as *const usize);
331 let free = *((base + 8) as *const usize);
332 Some((base, size, free))
333 }
334}
322/// boot_marker at an explicit text row. The shell's first paint 335/// boot_marker at an explicit text row. The shell's first paint
323/// occupies grid rows 0 (motd) and 1 (=== banner) and the prompt sits 336/// occupies grid rows 0 (motd) and 1 (=== banner) and the prompt sits
324/// at row 2 — markers whose *values* matter after boot (shadow state, 337/// at row 2 — markers whose *values* matter after boot (shadow state,
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 {
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 88f8d43..4ffb44d 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -138,6 +138,29 @@ pub fn size() -> (usize, usize) {
138 unsafe { (ROWS, COLS) } 138 unsafe { (ROWS, COLS) }
139} 139}
140 140
141/// DEBUG (crash-family): read one cell's raw word (ch | fg<<16 | bg<<24)
142/// from the live cells grid and the rendered grid, for the given text
143/// row/col. Returns (raw_cell, raw_rendered).
144pub fn cell_raw(r: usize, c: usize) -> (u32, u32) {
145 unsafe {
146 if CELLS.is_null() || RENDERED.is_null() {
147 return (0xFFFF_FFFF, 0xFFFF_FFFF);
148 }
149 let raw = |v: &Vec<Cell>| -> u32 {
150 if r < ROWS && c < COLS {
151 let cell = v[r * COLS + c];
152 cell.ch as u32 | ((cell.fg as u32) << 16) | ((cell.bg as u32) << 24)
153 } else {
154 0xFFFF_FFFE
155 }
156 };
157 (
158 raw(&*(CELLS as *const Vec<Cell>)),
159 raw(&*(RENDERED as *const Vec<Cell>)),
160 )
161 }
162}
163
141/// Shell-entry self-check: dump the vt grid's heap structs so a real- 164/// Shell-entry self-check: dump the vt grid's heap structs so a real-
142/// hardware boot shows whether the cells VecData / data arrays are sane 165/// hardware boot shows whether the cells VecData / data arrays are sane
143/// at shell entry (the OptiPlex's first banner write #GPs when the 166/// at shell entry (the OptiPlex's first banner write #GPs when the
@@ -184,9 +207,12 @@ pub fn debug_verify() {
184 // other three probe slots as zero-initialized). A stale cells 207 // other three probe slots as zero-initialized). A stale cells
185 // data pointer that happens to be in-heap still passes the 208 // data pointer that happens to be in-heap still passes the
186 // range checks (len/cap look fine) but its "cells" read back as 209 // range checks (len/cap look fine) but its "cells" read back as
187 // the underlying memory's leftover fill — the OptiPlex shows 210 // the underlying memory's leftover fill. NOTE: the OptiPlex
188 // the firmware 0x00AAAAAA scrub there. BLANK (0x70020) vs 211 // dump value 0x00AAAAAA is the VGA GRAY text colour
189 // scrub (0x00AAAAAA) is cleanly distinguishable. 212 // (rgb(170,170,170) = PALETTE[7] = fg 7), i.e. rendered shadow
213 // pixels — the memory read as heap held display text, not a
214 // firmware scrub. BLANK (0x70020) vs 0x00AAAAAA is cleanly
215 // distinguishable.
190 let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24); 216 let blank_raw = BLANK.ch as u32 | ((BLANK.fg as u32) << 16) | ((BLANK.bg as u32) << 24);
191 let mut content_ok = true; 217 let mut content_ok = true;
192 let mut rcontent_ok = true; 218 let mut rcontent_ok = true;
@@ -269,31 +295,24 @@ pub fn debug_verify() {
269 r_probes_raw[2], 295 r_probes_raw[2],
270 r_probes_raw[3] 296 r_probes_raw[3]
271 )); 297 ));
272 // Bottom-anchored: row 0-1 get overwritten by the shell 298 // NOTE: debug markers are serial-only now; the crash family
273 // banner, row 2 by the prompt (and a stale crash.log 299 // was resolved (EBS-retry free-list rebuild) and the screen
274 // replays at rows 2+), so this marker must live at the very 300 // must stay clean for users.
275 // bottom to stay readable until the user can copy it. 301 crate::serial_println!(
276 crate::gop::boot_marker_at( 302 "vt: c {:#x} cap={:#x} {} | r {:#x} {}",
277 &alloc::format!( 303 c.as_ptr() as usize,
278 "vt: c {:#x} cap={:#x} {} | r {:#x} {}", 304 c.capacity(),
279 c.as_ptr() as usize, 305 if content_ok { "ok" } else { "GARBAGE" },
280 c.capacity(), 306 r.as_ptr() as usize,
281 if content_ok { "ok" } else { "GARBAGE" }, 307 if rcontent_ok { "ok" } else { "GARBAGE" }
282 r.as_ptr() as usize,
283 if rcontent_ok { "ok" } else { "GARBAGE" }
284 ),
285 crate::gop::bottom_row(1),
286 ); 308 );
287 } else { 309 } else {
288 msg("verify: ok"); 310 msg("verify: ok");
289 crate::gop::boot_marker_at( 311 crate::serial_println!(
290 &alloc::format!( 312 "vt: c {:#x} cap={:#x} ok | r {:#x} ok",
291 "vt: c {:#x} cap={:#x} ok | r {:#x} ok", 313 c.as_ptr() as usize,
292 c.as_ptr() as usize, 314 c.capacity(),
293 c.capacity(), 315 r.as_ptr() as usize
294 r.as_ptr() as usize
295 ),
296 crate::gop::bottom_row(1),
297 ); 316 );
298 } 317 }
299 } 318 }