From ab7d87bcde31c1681ea09c30534dde840a58d6fa Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 22:28:34 +0200 Subject: rsnix v0.5.2: fault diagnostics + real-hardware scroll-crash hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The real-hardware scroll crash (white bands + instant freeze, QEMU-clean) was invisible because the only trace was a single serial line on machines with no serial console. This makes every crash self-identifying and hardens the real-hardware failure surfaces: - Every CPU exception vector 0-31 now has an IDT entry (macro-generated stubs; 8/14 keep their IST stacks). A fault in the shell prints a white-on-red band to the framebuffer via gop::dump_lines (raw fb writes bypassing the vt, fully clamped) plus a parseable serial line: '!!! vec=N err=… rip=… cr2=… rsp=… rflags=… task=N "name"', then halts — instead of converging to a cryptic double fault. Background-task faults abort the task as before. - Boot log now reports the authoritative post-SetMode geometry: 'gop: fb base=… size=… pitch=… WxH bpp=… bgr=…', so the next real machine's report identifies whether pitch*height exceeds the mapping. - Framebuffer writes (clear, push_rect, shift_up_n bulk push, cursor overlay) are clamped to the firmware's framebuffer_size: a pitch- padded mode degrades to a truncated view instead of faulting past the mapping. - The GOP framebuffer region is excluded from the page allocator (and subtracted from the heap's conventional-region pick) even when the firmware lists it as ConventionalMemory — kernel allocations can never overwrite the display again. The GOP protocol is located and peeked before the memory map is captured so the exclusion is known at heap-init time. - Shell gains a hidden 'fault' command (deliberate unmapped write) and 'make test-fault' verifies the dump: vec=14, cr2=0x400000000000, task=0, halt — no triple fault. All other suites re-pass. - CONTEXT.md: fault-dump format documented for real-hardware reports. --- rsnix/src/sched.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 7 deletions(-) (limited to 'rsnix/src/sched.rs') diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index dbe3647..cfce58f 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs @@ -28,7 +28,9 @@ 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; @@ -280,11 +282,7 @@ pub fn spawn(entry: fn(), name: &str) -> Option { }; let ok = spawn_at(id, entry, name); unlock(); - if ok { - Some(id) - } else { - None - } + if ok { Some(id) } else { None } } unsafe fn reap(id: usize) { @@ -548,10 +546,57 @@ pub fn abort_current(msg: &str) -> ! { /// Vector 14 (page fault) handler — runs on the dedicated IST stack. /// A fault in a real task (id >= 2) kills the task gracefully; anything /// else (shell, idle, pre-scheduler) is a kernel bug: halt. +/// Dump a fatal exception to serial *and* the screen (raw, bypassing the +/// vt), then halt the shell or abort the faulting background task. +/// +/// 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). +#[allow(clippy::needless_return)] +pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFrame) -> ! { + let vector = frame.vector; + let error = frame.error; + let rip = frame.rip; + let rflags = frame.rflags; + 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()]; + crate::gop::dump_lines(&lines); + + if in_kernel { + crate::interrupts::halt() + } else { + abort_current(&format!("{} vec={}", kind, vector)) + } +} + pub fn page_fault(cr2: u64, frame_ptr: usize) -> usize { if !is_ready() || unsafe { CURRENT } <= IDLE_TID { - crate::serial_println!("pgfault: kernel page fault at {:#x} — halting", cr2); - crate::interrupts::halt(); + let frame = unsafe { &*(frame_ptr as *const crate::interrupts::IntFrame) }; + fault_dump("page fault", cr2, frame) } let g = unsafe { TASKS[CURRENT].guard }; let in_guard = g != 0 && cr2 >= g as u64 && cr2 < (g + memory::PAGE_SIZE) as u64; -- cgit v1.2.3