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/sched.rs | 99 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 25 deletions(-) (limited to 'rsnix/src/sched.rs') diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index f48e210..a195d1c 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs @@ -28,9 +28,7 @@ use core::fmt; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; extern crate alloc; -use alloc::format; use alloc::string::String; -use alloc::string::ToString; use alloc::vec::Vec; use crate::interrupts::IntFrame; @@ -40,7 +38,7 @@ use crate::paging; pub const MAX_TASKS: usize = 16; pub const SHELL_TID: usize = 0; pub const IDLE_TID: usize = 1; -const STACK_PAGES: usize = 3; // 1 guard page + 8 KiB stack +const STACK_PAGES: usize = 9; // 1 guard page + 32 KiB stack — faults dump on the task's own stack const NAME_MAX: usize = 24; const PANIC_MAX: usize = 64; @@ -556,8 +554,15 @@ pub fn abort_current(msg: &str) -> ! { /// Real hardware has no serial console, so the screen dump is what lets /// a user report a crash precisely — vector, fault address, RIP and task. /// Runs with interrupts disabled (we're inside the ISR). +/// +/// Heap-free by construction: the dump's own `alloc::format!` used to +/// allocate from the heap, and when the underlying fault *was* heap +/// corruption the reporter itself died (#PF → #DF → triple fault → +/// silent reboot). Everything here formats into stack buffers, and the +/// ISR `in_dump` guard halts instead of recursing if even that faults. #[allow(clippy::needless_return)] pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFrame) -> ! { + crate::interrupts::enter_dump(); let vector = frame.vector; let error = frame.error; let rip = frame.rip; @@ -565,35 +570,79 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra let rsp = frame.rsp; let id = unsafe { CURRENT }; let name = name_str(id); - let name_buf = name.to_string(); let in_kernel = unsafe { CURRENT } <= IDLE_TID; - // Serial: a full, parseable line. - crate::serial_println!( - "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"", - kind, - vector, - error, - rip, - cr2, - rsp, - rflags, - id, - name_buf - ); - crate::serial_println!("!!! halting (shell task fault — machine stops)"); - - // Screen: white-on-red band via the clamped raw dump path. - let l1 = alloc::format!("!!! {} vec={} err={:#x}", kind, vector, error); - let l2 = alloc::format!("rip={:#x} cr2={:#x}", rip, cr2); - let l3 = alloc::format!("rsp={:#x} task={} {}", rsp, id, name_buf); - let lines = [l1.as_str(), l2.as_str(), l3.as_str()]; + // Serial: a full, parseable line (stack-formatted). + let mut buf = [0u8; 176]; + let n = { + let mut w = crate::strbuf::FmtBuf::new(&mut buf); + let _ = fmt::write( + &mut w, + core::format_args!( + "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"", + kind, + vector, + error, + rip, + cr2, + rsp, + rflags, + id, + name + ), + ); + w.len() + }; + let line = core::str::from_utf8(&buf[..n]).unwrap_or("!!! dump"); + crate::serial::write_str(line); + crate::serial::write_str("\r\n!!! halting (shell task fault — machine stops)\r\n"); + + // Screen: white-on-red band via the clamped raw dump path (heap-free). + let mut b1 = [0u8; 72]; + let mut b2 = [0u8; 72]; + let mut b3 = [0u8; 96]; + let n1 = { + let mut w = crate::strbuf::FmtBuf::new(&mut b1); + let _ = fmt::write( + &mut w, + core::format_args!("!!! {} vec={} err={:#x}", kind, vector, error), + ); + w.len() + }; + let n2 = { + let mut w = crate::strbuf::FmtBuf::new(&mut b2); + let _ = fmt::write(&mut w, core::format_args!("rip={:#x} cr2={:#x}", rip, cr2)); + w.len() + }; + let n3 = { + let mut w = crate::strbuf::FmtBuf::new(&mut b3); + let _ = fmt::write( + &mut w, + core::format_args!("rsp={:#x} task={} {}", rsp, id, name), + ); + w.len() + }; + let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!"); + let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or(""); + let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or(""); + let lines = [l1, l2, l3]; crate::gop::dump_lines(&lines); + // Best-effort: persist the report to the disk so it survives the + // reboot on real hardware (no serial console). Any fault here is + // caught by the in_dump guard and halts. + crate::fs::write_crash_log(&lines); + if in_kernel { crate::interrupts::halt() } else { - abort_current(&format!("{} vec={}", kind, vector)) + let mut mb = [0u8; 48]; + let mn = { + let mut w = crate::strbuf::FmtBuf::new(&mut mb); + let _ = fmt::write(&mut w, core::format_args!("{} vec={}", kind, vector)); + w.len() + }; + abort_current(core::str::from_utf8(&mb[..mn]).unwrap_or("fault")) } } -- cgit v1.2.3