From c7d92071e1dd11c45999457b16554b4abaa73b28 Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 2 Aug 2026 15:21:34 +0200 Subject: v0.5.5: crash reports go to a dedicated CRASHLOG USB stick The fault handler now writes crash.log to the volume labeled CRASHLOG (label-selected via fat::crash_vol), falling back to slot 0. On real hardware the boot USB may be unwritable, so a second stick labeled CRASHLOG survives the write; the shell auto-prints the previous crash report from it at next boot (read_crash_log resolves /disk//crash.log through the FAT path, since Disk nodes hold no cached children). - fat.rs: volume_label() + crash_vol() (no allocation) - fs.rs: write_crash_log targets crash_vol(); read_crash_log prefers the CRASHLOG disk node; drop now-unused first_disk() - mkdisk.sh: --label option - test-fault.sh: boots with a dedicated CRASHLOG stick (mkfs -F 32 -n CRASHLOG), verifies crash.log lands there and NOT on the ESP --- rsnix/src/memory.rs | 88 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 21 deletions(-) (limited to 'rsnix/src/memory.rs') diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs index 605125a..e6896f2 100644 --- a/rsnix/src/memory.rs +++ b/rsnix/src/memory.rs @@ -76,6 +76,31 @@ static mut HEAP_END: usize = 0; /// Total heap capacity — the original region plus pages added by growth. static mut HEAP_CAPACITY: usize = 0; +/// Kernel image bounds (base..end) captured from the UEFI LoadedImage +/// protocol before ExitBootServices. Both the heap free list and the +/// page-allocator bitmap reserve this range so no allocation can ever +/// land on the running kernel's own code/data/bss pages. +static mut KERNEL_IMAGE_START: usize = 0; +static mut KERNEL_IMAGE_END: usize = 0; + +/// Record the loaded-image memory range (from EFI_LOADED_IMAGE_PROTOCOL). +pub fn set_kernel_image_range(base: usize, size: usize) { + unsafe { + KERNEL_IMAGE_START = base; + KERNEL_IMAGE_END = base + size; + } +} + +fn kernel_image_range() -> Option<(usize, usize)> { + unsafe { + if KERNEL_IMAGE_END > KERNEL_IMAGE_START { + Some((KERNEL_IMAGE_START, KERNEL_IMAGE_END)) + } else { + None + } + } +} + /// Minimum pages `grow_heap` takes from the page allocator per growth. const HEAP_GROW_PAGES: usize = 64; @@ -354,11 +379,16 @@ pub unsafe fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Resul fn init_heap() { unsafe { - // Region the firmware maps for the GOP framebuffer, if known — - // exclude it even when the memory map lists it as conventional - // (some real firmware does exactly that, which would let heap - // allocations overwrite the display). - let fb = crate::gop::framebuffer_region(); + // Regions that must never be handed out as heap memory, even when + // the firmware lists them as conventional: + // * the GOP framebuffer (heap allocations overwriting the display), + // * the kernel image itself (a heap allocation landing on the + // kernel's .bss would silently corrupt statics — observed as a + // garbage ACTIVE_VOL and a dead filesystem in the fault path). + let holes: [(usize, usize); 2] = [ + crate::gop::framebuffer_region().unwrap_or((0, 0)), + kernel_image_range().unwrap_or((0, 0)), + ]; let mut best_start = 0usize; let mut best_size = 0usize; for i in 0..MAP_ENTRIES { @@ -366,26 +396,25 @@ fn init_heap() { if d.kind == MEMORY_TYPE_CONVENTIONAL { let s = d.physical_start as usize; let e = s + d.number_of_pages as usize * 4096; - // Split at the framebuffer hole so the free list NEVER - // contains fb pages: a coarse size subtraction leaves one + // Split at each hole so the free list NEVER contains + // reserved pages: a coarse size subtraction leaves one // block *spanning* the hole, letting a large allocation - // (e.g. the 1.9 MB display shadow) straddle the display. + // (e.g. the 1.9 MB display shadow) straddle it. let mut lo = s; - if let Some((b, sz)) = fb - && e > b - && s < b + sz - { - let h0 = b.max(s); - let h1 = (b + sz).min(e); - if h0 > lo { - // Segment before the hole. - let seg = h0 - lo; - if seg > best_size { - best_start = lo; - best_size = seg; + for (b, sz) in holes { + if sz > 0 && e > b && s < b + sz { + let h0 = b.max(s); + let h1 = (b + sz).min(e); + if h0 > lo { + // Segment before the hole. + let seg = h0 - lo; + if seg > best_size { + best_start = lo; + best_size = seg; + } } + lo = h1; } - lo = h1; } if e > lo { let seg = e - lo; @@ -664,6 +693,23 @@ pub fn init_page_allocator() -> bool { } } + // Reserve the kernel image so the page allocator never hands out + // the running kernel's own code/data/bss pages (DMA buffers and + // task stacks landing on the kernel would corrupt it — a heap + // allocation over the .bss already broke the FAT ACTIVE_VOL). + if let Some((ks, ke)) = kernel_image_range() + && ke > ks + { + let s = ks / PAGE_SIZE; + let e = ke.div_ceil(PAGE_SIZE); + for p in s..e { + let idx = p - base_page; + if idx < span_pages { + *bm.add(idx / 8) |= 1 << (idx % 8); + } + } + } + // Reserve the GOP framebuffer region even if the map lists it as // conventional — kernel allocations (stacks, DMA buffers) must // never land on the display. -- cgit v1.2.3