From 5185fe98d404c6c15a52f513313042ced16c350a Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 2 Aug 2026 01:59:45 +0200 Subject: v0.5.5: self-diagnosing boot — vt refuses 0x0 geometry, panic band shows last stage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user's real-hardware boot showed 'PANIC: index out of bounds (len 0, index 0) at vt.rs:146' — the vt grid was created empty because the framebuffer geometry was 0x0 at vt::init (GOP missing or the active mode reporting zero resolution). The v0.5.4 blank screen was the same panic, previously invisible (no screen dump in the panic handler). - vt::init: rows/cols == 0 now halts with a visible white-on-red 'FATAL: no framebuffer geometry — GOP missing or active mode is 0x0' band instead of building an empty grid that panics at the first cell write. - boot_marker records the last boot stage; the panic band appends it as a 4th line so a crash shows exactly how far boot got. - The gop+vt marker now carries the measured geometry ('rsnix: gop 800x600 vt 100x37 ok') so a good boot is self-confirming on the screen. - All 5 test suites pass; build/clippy clean. --- rsdos/src/main.rs | 16 ++++++++++++++-- rsnix/src/gop.rs | 21 +++++++++++++++++++++ rsnix/src/vt.rs | 20 ++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs index b6393ee..aa116e4 100644 --- a/rsdos/src/main.rs +++ b/rsdos/src/main.rs @@ -5,6 +5,8 @@ use core::panic::PanicInfo; use rsnix::uefi::*; +extern crate alloc; + // --------------------------------------------------------------------------- // Panic handler // --------------------------------------------------------------------------- @@ -47,9 +49,11 @@ fn panic(info: &PanicInfo) -> ! { } // Real hardware has no serial console — put the panic on the screen // too (white-on-red band), so a pre-shell panic is not a silent - // blank screen. + // blank screen. The last boot stage is appended so the user can + // see how far boot got before the crash. let mut loc_buf = [0u8; 96]; let mut msg_buf = [0u8; 128]; + let mut stage_buf = [0u8; 96]; let loc_n = { let mut w = rsnix::strbuf::FmtBuf::new(&mut loc_buf); if let Some(loc) = info.location() { @@ -64,10 +68,17 @@ fn panic(info: &PanicInfo) -> ! { let _ = write!(&mut w, "{}", info.message()); w.len() }; + let stage_n = { + let s = rsnix::gop::last_boot_stage(); + let n = s.len().min(stage_buf.len()); + stage_buf[..n].copy_from_slice(&s.as_bytes()[..n]); + n + }; let lines = [ "*** PANIC ***", core::str::from_utf8(&msg_buf[..msg_n]).unwrap_or("?"), core::str::from_utf8(&loc_buf[..loc_n]).unwrap_or("?"), + core::str::from_utf8(&stage_buf[..stage_n]).unwrap_or(""), ]; rsnix::gop::dump_lines(&lines); loop { @@ -150,7 +161,8 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut rsnix::vt::init(); let (rows, cols) = rsnix::vt::size(); rsnix::serial_println!("vt: terminal ready ({}x{})", rows, cols); - rsnix::gop::boot_marker("rsnix: gop+vt ok"); + let (fw, fh) = rsnix::gop::pixel_dims(); + rsnix::gop::boot_marker(&alloc::format!("rsnix: gop {}x{} vt {}x{} ok", fw, fh, cols, rows)); // ExitBootServices — we now own the machine if unsafe { (bs.exit_boot_services)(image_handle, map_key) } != EFI_SUCCESS { diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs index 7b97d14..90c24bf 100644 --- a/rsnix/src/gop.rs +++ b/rsnix/src/gop.rs @@ -71,6 +71,20 @@ pub fn framebuffer_region() -> Option<(usize, usize)> { static mut SHADOW: *mut u32 = core::ptr::null_mut(); static mut SHADOW_WORDS: usize = 0; +/// Last boot-progress stage (set by `boot_marker`); the panic handler +/// appends it to the on-screen band so a crash shows where boot died. +static mut LAST_BOOT_STAGE: [u8; 96] = [0; 96]; +static mut LAST_BOOT_STAGE_LEN: usize = 0; +const STAGE_MAX: usize = 96; + +/// The last boot stage string (for the panic band). +pub fn last_boot_stage() -> alloc::string::String { + unsafe { + alloc::string::String::from_utf8_lossy(&LAST_BOOT_STAGE[..LAST_BOOT_STAGE_LEN]) + .into_owned() + } +} + /// Diagnostic: when > 0, every display store busy-waits ~this many /// cycles first, simulating the per-write bus cost of an uncached real /// GOP framebuffer (QEMU's framebuffer is RAM — instant). Lets us @@ -338,6 +352,13 @@ pub fn init_shadow() { /// direct-display glyph path (the shadow may not exist yet in early /// boot); the shell banner overwrites row 0 at first paint. pub fn boot_marker(msg: &str) { + // Record the stage so a later panic band can show how far boot got. + unsafe { + let n = msg.len().min(STAGE_MAX); + let dst = core::ptr::addr_of_mut!(LAST_BOOT_STAGE) as *mut u8; + core::ptr::copy_nonoverlapping(msg.as_ptr(), dst, n); + LAST_BOOT_STAGE_LEN = n; + } let Some(fb) = fb().as_ref() else { return; }; diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs index 53821b7..4578f6c 100644 --- a/rsnix/src/vt.rs +++ b/rsnix/src/vt.rs @@ -96,6 +96,26 @@ static mut CUR: usize = 0; /// Allocate the cell grid. Call once after `gop::init()`. pub fn init() { let (cols, rows) = gop::dims(); + if rows == 0 || cols == 0 { + // No usable framebuffer geometry: GOP is missing entirely or the + // active mode reports 0x0. Without a display there is nothing + // the shell can do, so stop with a visible explanation instead + // of building an empty grid and panicking at the first cell + // write (a blank screen on real hardware, which has no serial + // console). + let lines = [ + "FATAL: no framebuffer geometry", + "GOP missing or active mode is 0x0", + "see the 'gop:' boot marker / serial log", + ]; + crate::gop::dump_lines(&lines); + crate::serial_println!("FATAL: no framebuffer geometry (GOP missing or 0x0 mode)"); + loop { + unsafe { + core::arch::asm!("cli; hlt"); + } + } + } unsafe { ROWS = rows; COLS = cols; -- cgit v1.2.3