diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-02 15:21:34 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-02 15:21:34 +0200 |
| commit | c7d92071e1dd11c45999457b16554b4abaa73b28 (patch) | |
| tree | aff7c7f19adcffe1dd91e1c2514af02c77fd524e /rsnix/src/memory.rs | |
| parent | v0.5.5: fix real-hardware boot crash — shadow back in heap as first allocation (diff) | |
| download | rs-dos-c7d92071e1dd11c45999457b16554b4abaa73b28.tar.gz rs-dos-c7d92071e1dd11c45999457b16554b4abaa73b28.zip | |
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/<uid>/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
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/memory.rs | 88 |
1 files changed, 67 insertions, 21 deletions
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; | |||
| 76 | /// Total heap capacity — the original region plus pages added by growth. | 76 | /// Total heap capacity — the original region plus pages added by growth. |
| 77 | static mut HEAP_CAPACITY: usize = 0; | 77 | static mut HEAP_CAPACITY: usize = 0; |
| 78 | 78 | ||
| 79 | /// Kernel image bounds (base..end) captured from the UEFI LoadedImage | ||
| 80 | /// protocol before ExitBootServices. Both the heap free list and the | ||
| 81 | /// page-allocator bitmap reserve this range so no allocation can ever | ||
| 82 | /// land on the running kernel's own code/data/bss pages. | ||
| 83 | static mut KERNEL_IMAGE_START: usize = 0; | ||
| 84 | static mut KERNEL_IMAGE_END: usize = 0; | ||
| 85 | |||
| 86 | /// Record the loaded-image memory range (from EFI_LOADED_IMAGE_PROTOCOL). | ||
| 87 | pub fn set_kernel_image_range(base: usize, size: usize) { | ||
| 88 | unsafe { | ||
| 89 | KERNEL_IMAGE_START = base; | ||
| 90 | KERNEL_IMAGE_END = base + size; | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | fn kernel_image_range() -> Option<(usize, usize)> { | ||
| 95 | unsafe { | ||
| 96 | if KERNEL_IMAGE_END > KERNEL_IMAGE_START { | ||
| 97 | Some((KERNEL_IMAGE_START, KERNEL_IMAGE_END)) | ||
| 98 | } else { | ||
| 99 | None | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 79 | /// Minimum pages `grow_heap` takes from the page allocator per growth. | 104 | /// Minimum pages `grow_heap` takes from the page allocator per growth. |
| 80 | const HEAP_GROW_PAGES: usize = 64; | 105 | const HEAP_GROW_PAGES: usize = 64; |
| 81 | 106 | ||
| @@ -354,11 +379,16 @@ pub unsafe fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Resul | |||
| 354 | 379 | ||
| 355 | fn init_heap() { | 380 | fn init_heap() { |
| 356 | unsafe { | 381 | unsafe { |
| 357 | // Region the firmware maps for the GOP framebuffer, if known — | 382 | // Regions that must never be handed out as heap memory, even when |
| 358 | // exclude it even when the memory map lists it as conventional | 383 | // the firmware lists them as conventional: |
| 359 | // (some real firmware does exactly that, which would let heap | 384 | // * the GOP framebuffer (heap allocations overwriting the display), |
| 360 | // allocations overwrite the display). | 385 | // * the kernel image itself (a heap allocation landing on the |
| 361 | let fb = crate::gop::framebuffer_region(); | 386 | // kernel's .bss would silently corrupt statics — observed as a |
| 387 | // garbage ACTIVE_VOL and a dead filesystem in the fault path). | ||
| 388 | let holes: [(usize, usize); 2] = [ | ||
| 389 | crate::gop::framebuffer_region().unwrap_or((0, 0)), | ||
| 390 | kernel_image_range().unwrap_or((0, 0)), | ||
| 391 | ]; | ||
| 362 | let mut best_start = 0usize; | 392 | let mut best_start = 0usize; |
| 363 | let mut best_size = 0usize; | 393 | let mut best_size = 0usize; |
| 364 | for i in 0..MAP_ENTRIES { | 394 | for i in 0..MAP_ENTRIES { |
| @@ -366,26 +396,25 @@ fn init_heap() { | |||
| 366 | if d.kind == MEMORY_TYPE_CONVENTIONAL { | 396 | if d.kind == MEMORY_TYPE_CONVENTIONAL { |
| 367 | let s = d.physical_start as usize; | 397 | let s = d.physical_start as usize; |
| 368 | let e = s + d.number_of_pages as usize * 4096; | 398 | let e = s + d.number_of_pages as usize * 4096; |
| 369 | // Split at the framebuffer hole so the free list NEVER | 399 | // Split at each hole so the free list NEVER contains |
| 370 | // contains fb pages: a coarse size subtraction leaves one | 400 | // reserved pages: a coarse size subtraction leaves one |
| 371 | // block *spanning* the hole, letting a large allocation | 401 | // block *spanning* the hole, letting a large allocation |
| 372 | // (e.g. the 1.9 MB display shadow) straddle the display. | 402 | // (e.g. the 1.9 MB display shadow) straddle it. |
| 373 | let mut lo = s; | 403 | let mut lo = s; |
| 374 | if let Some((b, sz)) = fb | 404 | for (b, sz) in holes { |
| 375 | && e > b | 405 | if sz > 0 && e > b && s < b + sz { |
| 376 | && s < b + sz | 406 | let h0 = b.max(s); |
| 377 | { | 407 | let h1 = (b + sz).min(e); |
| 378 | let h0 = b.max(s); | 408 | if h0 > lo { |
| 379 | let h1 = (b + sz).min(e); | 409 | // Segment before the hole. |
| 380 | if h0 > lo { | 410 | let seg = h0 - lo; |
| 381 | // Segment before the hole. | 411 | if seg > best_size { |
| 382 | let seg = h0 - lo; | 412 | best_start = lo; |
| 383 | if seg > best_size { | 413 | best_size = seg; |
| 384 | best_start = lo; | 414 | } |
| 385 | best_size = seg; | ||
| 386 | } | 415 | } |
| 416 | lo = h1; | ||
| 387 | } | 417 | } |
| 388 | lo = h1; | ||
| 389 | } | 418 | } |
| 390 | if e > lo { | 419 | if e > lo { |
| 391 | let seg = e - lo; | 420 | let seg = e - lo; |
| @@ -664,6 +693,23 @@ pub fn init_page_allocator() -> bool { | |||
| 664 | } | 693 | } |
| 665 | } | 694 | } |
| 666 | 695 | ||
| 696 | // Reserve the kernel image so the page allocator never hands out | ||
| 697 | // the running kernel's own code/data/bss pages (DMA buffers and | ||
| 698 | // task stacks landing on the kernel would corrupt it — a heap | ||
| 699 | // allocation over the .bss already broke the FAT ACTIVE_VOL). | ||
| 700 | if let Some((ks, ke)) = kernel_image_range() | ||
| 701 | && ke > ks | ||
| 702 | { | ||
| 703 | let s = ks / PAGE_SIZE; | ||
| 704 | let e = ke.div_ceil(PAGE_SIZE); | ||
| 705 | for p in s..e { | ||
| 706 | let idx = p - base_page; | ||
| 707 | if idx < span_pages { | ||
| 708 | *bm.add(idx / 8) |= 1 << (idx % 8); | ||
| 709 | } | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 667 | // Reserve the GOP framebuffer region even if the map lists it as | 713 | // Reserve the GOP framebuffer region even if the map lists it as |
| 668 | // conventional — kernel allocations (stacks, DMA buffers) must | 714 | // conventional — kernel allocations (stacks, DMA buffers) must |
| 669 | // never land on the display. | 715 | // never land on the display. |
