summaryrefslogtreecommitdiffstats
path: root/rsnix/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/fat.rs31
-rw-r--r--rsnix/src/fs.rs93
-rw-r--r--rsnix/src/gdt.rs24
-rw-r--r--rsnix/src/gop.rs6
-rw-r--r--rsnix/src/interrupts.rs41
-rw-r--r--rsnix/src/memory.rs88
-rw-r--r--rsnix/src/sched.rs99
-rw-r--r--rsnix/src/uefi.rs26
8 files changed, 349 insertions, 59 deletions
diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs
index eda81ed..b7e0da1 100644
--- a/rsnix/src/fat.rs
+++ b/rsnix/src/fat.rs
@@ -71,6 +71,31 @@ pub fn is_mounted(vol: usize) -> bool {
71 vols().get(vol).map(|v| v.mounted).unwrap_or(false) 71 vols().get(vol).map(|v| v.mounted).unwrap_or(false)
72} 72}
73 73
74/// The volume's label — the part of its uid before the serial suffix
75/// (uid is "LABEL-XXXXXXXX", label sanitized to [A-Z0-9_], so the first
76/// '-' always terminates it). No allocation: borrows the uid string.
77pub fn volume_label(vol: usize) -> Option<&'static str> {
78 let uid = &vols().get(vol)?.uid;
79 Some(uid.split_once('-').map(|(l, _)| l).unwrap_or(uid))
80}
81
82/// Index of the crash-log volume: the first volume whose label is
83/// "CRASHLOG" (a dedicated stick survives reboot writes that a boot
84/// device may not accept), else slot 0 (first mounted) as before.
85pub fn crash_vol() -> Option<usize> {
86 let n = volume_count();
87 if n == 0 {
88 return None;
89 }
90 for vol in 0..n {
91 if let Some(label) = volume_label(vol)
92 && label.eq_ignore_ascii_case("CRASHLOG") {
93 return Some(vol);
94 }
95 }
96 Some(0)
97}
98
74/// Re-scan after a disk arrival/removal: unmount volumes whose disk left, 99/// Re-scan after a disk arrival/removal: unmount volumes whose disk left,
75/// mount every disk that has no mounted volume (which also re-tries 100/// mount every disk that has no mounted volume (which also re-tries
76/// mounts that failed at boot). Volume slots are tombstoned, never 101/// mounts that failed at boot). Volume slots are tombstoned, never
@@ -175,7 +200,11 @@ fn v() -> &'static mut Volume {
175 match vols().get_mut(i) { 200 match vols().get_mut(i) {
176 Some(v) => v, 201 Some(v) => v,
177 None => { 202 None => {
178 crate::serial_println!("fat: internal error — no volume at slot {}", i); 203 crate::serial_println!(
204 "fat: internal error — no volume at slot {} (len={})",
205 i,
206 vols().len()
207 );
179 crate::interrupts::halt(); 208 crate::interrupts::halt();
180 } 209 }
181 } 210 }
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs
index 5fc6d7a..24d8fcd 100644
--- a/rsnix/src/fs.rs
+++ b/rsnix/src/fs.rs
@@ -464,9 +464,98 @@ pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> {
464 } 464 }
465} 465}
466 466
467/// Handle of `crash.log` on the crash-log volume, if it exists. The
468/// crash-log volume is the one labeled CRASHLOG (a dedicated stick that
469/// survives reboot writes), else the first mounted volume. Resolves the
470/// FAT path (`/disk/<uid>/crash.log`) because Disk nodes hold no cached
471/// children.
472pub fn read_crash_log() -> Option<usize> {
473 let disk_dir = lookup(0, "disk")?;
474 let children = match &nodes()[disk_dir].data {
475 NodeData::Dir(v) => v.clone(),
476 _ => return None,
477 };
478 // Prefer a disk node whose name starts with "CRASHLOG-".
479 let mut first = None;
480 for &c in &children {
481 if nodes()[c].removed || !matches!(nodes()[c].data, NodeData::Disk(_)) {
482 continue;
483 }
484 let name = &nodes()[c].name;
485 let label = name.split_once('-').map(|(l, _)| l).unwrap_or(name);
486 if label.eq_ignore_ascii_case("CRASHLOG") {
487 return resolve(&alloc::format!("/disk/{}/crash.log", name));
488 }
489 if first.is_none() {
490 first = Some(c);
491 }
492 }
493 first.and_then(|disk| {
494 let name = nodes()[disk].name.clone();
495 resolve(&alloc::format!("/disk/{}/crash.log", name))
496 })
497}
498
499/// Persist a crash report to the mounted FAT volume so it survives a reboot
500/// (real hardware has no serial console). Best-effort: any failure is
501/// ignored — the caller (fault handler) must never fault here; the
502/// in-dump ISR guard catches that case and halts.
503///
504/// Goes straight at the FAT layer (the crash-log volume + its root
505/// cluster) rather than through the fs node table: the fault may have
506/// interrupted the fs mid-operation, and a stale DiskInfo/ACTIVE_VOL
507/// would make node-based writes fail with "no volume at slot N". The
508/// crash-log volume is the one labeled CRASHLOG (a dedicated stick that
509/// survives reboot writes), else slot 0 (first mounted) as before.
510pub fn write_crash_log(lines: &[&str]) {
511 // Assemble the report on the stack (heap-free — a corrupted heap is
512 // exactly the situation that produces crash reports).
513 let mut text = [0u8; 640];
514 let mut n = 0usize;
515 for line in lines {
516 for &b in line.as_bytes() {
517 if n < text.len() {
518 text[n] = b;
519 n += 1;
520 }
521 }
522 if n < text.len() {
523 text[n] = b'\n';
524 n += 1;
525 }
526 }
527 let Some(vol) = crate::fat::crash_vol() else {
528 crate::serial::write_raw("crashlog: no volume\r\n");
529 return;
530 };
531 // Trace the target so a test can verify the right stick got the log.
532 let label = crate::fat::volume_label(vol).unwrap_or("?");
533 let mut tb = [0u8; 48];
534 let tn = {
535 let mut w = crate::strbuf::FmtBuf::new(&mut tb);
536 let _ = core::fmt::write(&mut w, core::format_args!("crashlog: vol {} ({})\r\n", vol, label));
537 w.len()
538 };
539 crate::serial::write_raw(core::str::from_utf8(&tb[..tn]).unwrap_or("?"));
540 let root = crate::fat::root_cluster(vol);
541 if crate::fat::create(vol, root, "crash.log", false).is_err()
542 && crate::fat::find(vol, root, "crash.log").is_err()
543 {
544 crate::serial::write_raw("crashlog: create failed\r\n");
545 return;
546 }
547 if crate::fat::set_len(vol, root, "crash.log", 0).is_err() {
548 crate::serial::write_raw("crashlog: set_len failed\r\n");
549 return;
550 }
551 match crate::fat::write_file(vol, root, "crash.log", &text[..n], 0) {
552 Ok(_) => crate::serial::write_raw("crashlog: wrote\r\n"),
553 Err(_) => crate::serial::write_raw("crashlog: write failed\r\n"),
554 }
555}
556
467/// Truncate/resize a regular file. 557/// Truncate/resize a regular file.
468pub fn set_len(h: usize, len: u64) -> Result<(), ()> { 558pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h].data {
469 match &mut nodes()[h].data {
470 NodeData::Regular(v) => { 559 NodeData::Regular(v) => {
471 v.resize(len as usize, 0); 560 v.resize(len as usize, 0);
472 Ok(()) 561 Ok(())
diff --git a/rsnix/src/gdt.rs b/rsnix/src/gdt.rs
index 514ed2e..f3d8b9a 100644
--- a/rsnix/src/gdt.rs
+++ b/rsnix/src/gdt.rs
@@ -116,12 +116,14 @@ impl Tss {
116 } 116 }
117} 117}
118 118
119/// 4 KiB page-aligned block for the IST stacks. 119/// IST stack size: the fault path (fault dump + best-effort crash-log
120#[repr(align(4096))] 120/// FAT write) is deep, so each IST stack is 32 KiB, allocated from the
121struct Page([u8; 4096]); 121/// heap at init. They must NOT live in .bss: a 4 KiB .bss stack placed
122/// next to kernel statics silently overwrites them on overflow (the FAT
123/// ACTIVE_VOL was clobbered this way — a "no volume at slot 8" crash in
124/// the fault path). The heap region is far from the kernel image.
125const IST_STACK_SIZE: usize = 32 * 1024;
122 126
123static mut PF_STACK: Page = Page([0; 4096]);
124static mut DF_STACK: Page = Page([0; 4096]);
125static mut TSS: Tss = Tss::zero(); 127static mut TSS: Tss = Tss::zero();
126// GDT slots are 8 bytes except the TSS descriptor at index 63, which is 128// GDT slots are 8 bytes except the TSS descriptor at index 63, which is
127// 16 bytes — total 64*8 + 16. 129// 16 bytes — total 64*8 + 16.
@@ -193,8 +195,16 @@ pub fn init() -> bool {
193 195
194 // TSS descriptor (16 bytes) at index 63. 196 // TSS descriptor (16 bytes) at index 63.
195 let tss_base = core::ptr::addr_of_mut!(TSS) as usize as u64; 197 let tss_base = core::ptr::addr_of_mut!(TSS) as usize as u64;
196 TSS.ist1 = core::ptr::addr_of_mut!(PF_STACK.0[0]) as usize as u64 + 4096; 198 // IST stacks come from the heap (see IST_STACK_SIZE): 32 KiB each,
197 TSS.ist2 = core::ptr::addr_of_mut!(DF_STACK.0[0]) as usize as u64 + 4096; 199 // well away from the kernel image/.bss so an overflowing fault
200 // path can't silently overwrite statics.
201 let pf = crate::memory::allocate(IST_STACK_SIZE);
202 let df = crate::memory::allocate(IST_STACK_SIZE);
203 if pf.is_null() || df.is_null() {
204 return false;
205 }
206 TSS.ist1 = pf as u64 + IST_STACK_SIZE as u64;
207 TSS.ist2 = df as u64 + IST_STACK_SIZE as u64;
198 TSS.iomap_base = TSS_SIZE as u16; // no I/O bitmap 208 TSS.iomap_base = TSS_SIZE as u16; // no I/O bitmap
199 209
200 let mut td = [0u8; 16]; 210 let mut td = [0u8; 16];
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
index d37468f..7aeb224 100644
--- a/rsnix/src/gop.rs
+++ b/rsnix/src/gop.rs
@@ -257,7 +257,8 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
257 let words = (fbinfo.pitch * fbinfo.height) / 4; 257 let words = (fbinfo.pitch * fbinfo.height) / 4;
258 if words > 0 { 258 if words > 0 {
259 let bytes = words * 4; 259 let bytes = words * 4;
260 #[allow(clippy::slow_vector_initialization)] // deliberate: try_reserve must not panic 260 #[allow(clippy::slow_vector_initialization)]
261 // deliberate: try_reserve must not panic
261 let mut v: alloc::vec::Vec<u32> = alloc::vec::Vec::new(); 262 let mut v: alloc::vec::Vec<u32> = alloc::vec::Vec::new();
262 if v.try_reserve_exact(words).is_ok() { 263 if v.try_reserve_exact(words).is_ok() {
263 v.resize(words, 0); // can't fail after the reserve 264 v.resize(words, 0); // can't fail after the reserve
@@ -284,8 +285,7 @@ pub fn init(gop: &mut GraphicsOutputProtocol) {
284 rbase + rsize, 285 rbase + rsize,
285 ); 286 );
286 if SHADOW.is_null() { 287 if SHADOW.is_null() {
287 let msg = 288 let msg = "gop: WARNING — shadow rejected (fb overlap); no-shadow fallback";
288 "gop: WARNING — shadow rejected (fb overlap); no-shadow fallback";
289 crate::serial_println!("{}", msg); 289 crate::serial_println!("{}", msg);
290 let lines: alloc::vec::Vec<&str> = msg 290 let lines: alloc::vec::Vec<&str> = msg
291 .as_bytes() 291 .as_bytes()
diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs
index be252df..927e4f1 100644
--- a/rsnix/src/interrupts.rs
+++ b/rsnix/src/interrupts.rs
@@ -393,6 +393,38 @@ pub(crate) struct IntFrame {
393 pub(crate) ss: u64, 393 pub(crate) ss: u64,
394} 394}
395 395
396// ---------------------------------------------------------------------------
397// Fault-dump re-entry guard
398// ---------------------------------------------------------------------------
399
400/// Set when `sched::fault_dump` is running. Any CPU exception that
401/// fires *while the dump is already in progress* (typically because the
402/// heap is corrupted and the dump path tripped over it) must never
403/// recurse into the dump — that chain (#PF → #DF → #PF) is a triple
404/// fault, which silently resets the machine with no report (black
405/// screen, firmware splash — exactly what real hardware showed). The
406/// guard short-circuits to a raw heap-free band and a hard halt.
407static mut IN_DUMP: bool = false;
408
409pub fn enter_dump() {
410 unsafe {
411 IN_DUMP = true;
412 }
413}
414
415fn in_dump() -> bool {
416 unsafe { IN_DUMP }
417}
418
419/// Raw, heap-free fallback: a fault fired inside the fault dump.
420fn dump_guard_halt() -> ! {
421 crate::gop::dump_lines(&[
422 "!!! fault inside fault dump",
423 "!!! halting (no triple fault)",
424 ]);
425 halt();
426}
427
396/// Called from the asm common handler — dispatches to the right Rust 428/// Called from the asm common handler — dispatches to the right Rust
397/// handler and returns the frame pointer the asm should switch to (the 429/// handler and returns the frame pointer the asm should switch to (the
398/// next task's, or the same one when no switch happens). 430/// next task's, or the same one when no switch happens).
@@ -433,9 +465,15 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize {
433 8 => { 465 8 => {
434 // Double fault — dump the frame (we're on the IST stack 466 // Double fault — dump the frame (we're on the IST stack
435 // so this is safe) and halt. 467 // so this is safe) and halt.
468 if in_dump() {
469 dump_guard_halt();
470 }
436 crate::sched::fault_dump("double fault", 0, frame) 471 crate::sched::fault_dump("double fault", 0, frame)
437 } 472 }
438 14 => { 473 14 => {
474 if in_dump() {
475 dump_guard_halt();
476 }
439 let cr2: u64; 477 let cr2: u64;
440 core::arch::asm!("mov {}, cr2", out(reg) cr2, 478 core::arch::asm!("mov {}, cr2", out(reg) cr2,
441 options(nostack, preserves_flags)); 479 options(nostack, preserves_flags));
@@ -445,6 +483,9 @@ unsafe extern "C" fn isr_dispatch(frame: &IntFrame) -> usize {
445 v if v < 32 => { 483 v if v < 32 => {
446 // Any other CPU exception: full dump (screen + serial), 484 // Any other CPU exception: full dump (screen + serial),
447 // then halt the shell or abort the faulting background task. 485 // then halt the shell or abort the faulting background task.
486 if in_dump() {
487 dump_guard_halt();
488 }
448 crate::sched::fault_dump("exception", 0, frame) 489 crate::sched::fault_dump("exception", 0, frame)
449 } 490 }
450 _ => fp, 491 _ => fp,
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.
77static mut HEAP_CAPACITY: usize = 0; 77static 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.
83static mut KERNEL_IMAGE_START: usize = 0;
84static mut KERNEL_IMAGE_END: usize = 0;
85
86/// Record the loaded-image memory range (from EFI_LOADED_IMAGE_PROTOCOL).
87pub 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
94fn 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.
80const HEAP_GROW_PAGES: usize = 64; 105const 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
355fn init_heap() { 380fn 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.
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;
28use core::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 28use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
29 29
30extern crate alloc; 30extern crate alloc;
31use alloc::format;
32use alloc::string::String; 31use alloc::string::String;
33use alloc::string::ToString;
34use alloc::vec::Vec; 32use alloc::vec::Vec;
35 33
36use crate::interrupts::IntFrame; 34use crate::interrupts::IntFrame;
@@ -40,7 +38,7 @@ use crate::paging;
40pub const MAX_TASKS: usize = 16; 38pub const MAX_TASKS: usize = 16;
41pub const SHELL_TID: usize = 0; 39pub const SHELL_TID: usize = 0;
42pub const IDLE_TID: usize = 1; 40pub const IDLE_TID: usize = 1;
43const STACK_PAGES: usize = 3; // 1 guard page + 8 KiB stack 41const STACK_PAGES: usize = 9; // 1 guard page + 32 KiB stack — faults dump on the task's own stack
44const NAME_MAX: usize = 24; 42const NAME_MAX: usize = 24;
45const PANIC_MAX: usize = 64; 43const PANIC_MAX: usize = 64;
46 44
@@ -556,8 +554,15 @@ pub fn abort_current(msg: &str) -> ! {
556/// Real hardware has no serial console, so the screen dump is what lets 554/// Real hardware has no serial console, so the screen dump is what lets
557/// a user report a crash precisely — vector, fault address, RIP and task. 555/// a user report a crash precisely — vector, fault address, RIP and task.
558/// Runs with interrupts disabled (we're inside the ISR). 556/// Runs with interrupts disabled (we're inside the ISR).
557///
558/// Heap-free by construction: the dump's own `alloc::format!` used to
559/// allocate from the heap, and when the underlying fault *was* heap
560/// corruption the reporter itself died (#PF → #DF → triple fault →
561/// silent reboot). Everything here formats into stack buffers, and the
562/// ISR `in_dump` guard halts instead of recursing if even that faults.
559#[allow(clippy::needless_return)] 563#[allow(clippy::needless_return)]
560pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFrame) -> ! { 564pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFrame) -> ! {
565 crate::interrupts::enter_dump();
561 let vector = frame.vector; 566 let vector = frame.vector;
562 let error = frame.error; 567 let error = frame.error;
563 let rip = frame.rip; 568 let rip = frame.rip;
@@ -565,35 +570,79 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra
565 let rsp = frame.rsp; 570 let rsp = frame.rsp;
566 let id = unsafe { CURRENT }; 571 let id = unsafe { CURRENT };
567 let name = name_str(id); 572 let name = name_str(id);
568 let name_buf = name.to_string();
569 let in_kernel = unsafe { CURRENT } <= IDLE_TID; 573 let in_kernel = unsafe { CURRENT } <= IDLE_TID;
570 574
571 // Serial: a full, parseable line. 575 // Serial: a full, parseable line (stack-formatted).
572 crate::serial_println!( 576 let mut buf = [0u8; 176];
573 "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"", 577 let n = {
574 kind, 578 let mut w = crate::strbuf::FmtBuf::new(&mut buf);
575 vector, 579 let _ = fmt::write(
576 error, 580 &mut w,
577 rip, 581 core::format_args!(
578 cr2, 582 "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"",
579 rsp, 583 kind,
580 rflags, 584 vector,
581 id, 585 error,
582 name_buf 586 rip,
583 ); 587 cr2,
584 crate::serial_println!("!!! halting (shell task fault — machine stops)"); 588 rsp,
585 589 rflags,
586 // Screen: white-on-red band via the clamped raw dump path. 590 id,
587 let l1 = alloc::format!("!!! {} vec={} err={:#x}", kind, vector, error); 591 name
588 let l2 = alloc::format!("rip={:#x} cr2={:#x}", rip, cr2); 592 ),
589 let l3 = alloc::format!("rsp={:#x} task={} {}", rsp, id, name_buf); 593 );
590 let lines = [l1.as_str(), l2.as_str(), l3.as_str()]; 594 w.len()
595 };
596 let line = core::str::from_utf8(&buf[..n]).unwrap_or("!!! dump");
597 crate::serial::write_str(line);
598 crate::serial::write_str("\r\n!!! halting (shell task fault — machine stops)\r\n");
599
600 // Screen: white-on-red band via the clamped raw dump path (heap-free).
601 let mut b1 = [0u8; 72];
602 let mut b2 = [0u8; 72];
603 let mut b3 = [0u8; 96];
604 let n1 = {
605 let mut w = crate::strbuf::FmtBuf::new(&mut b1);
606 let _ = fmt::write(
607 &mut w,
608 core::format_args!("!!! {} vec={} err={:#x}", kind, vector, error),
609 );
610 w.len()
611 };
612 let n2 = {
613 let mut w = crate::strbuf::FmtBuf::new(&mut b2);
614 let _ = fmt::write(&mut w, core::format_args!("rip={:#x} cr2={:#x}", rip, cr2));
615 w.len()
616 };
617 let n3 = {
618 let mut w = crate::strbuf::FmtBuf::new(&mut b3);
619 let _ = fmt::write(
620 &mut w,
621 core::format_args!("rsp={:#x} task={} {}", rsp, id, name),
622 );
623 w.len()
624 };
625 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!");
626 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or("");
627 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or("");
628 let lines = [l1, l2, l3];
591 crate::gop::dump_lines(&lines); 629 crate::gop::dump_lines(&lines);
592 630
631 // Best-effort: persist the report to the disk so it survives the
632 // reboot on real hardware (no serial console). Any fault here is
633 // caught by the in_dump guard and halts.
634 crate::fs::write_crash_log(&lines);
635
593 if in_kernel { 636 if in_kernel {
594 crate::interrupts::halt() 637 crate::interrupts::halt()
595 } else { 638 } else {
596 abort_current(&format!("{} vec={}", kind, vector)) 639 let mut mb = [0u8; 48];
640 let mn = {
641 let mut w = crate::strbuf::FmtBuf::new(&mut mb);
642 let _ = fmt::write(&mut w, core::format_args!("{} vec={}", kind, vector));
643 w.len()
644 };
645 abort_current(core::str::from_utf8(&mb[..mn]).unwrap_or("fault"))
597 } 646 }
598} 647}
599 648
diff --git a/rsnix/src/uefi.rs b/rsnix/src/uefi.rs
index c311ad9..234ad65 100644
--- a/rsnix/src/uefi.rs
+++ b/rsnix/src/uefi.rs
@@ -40,6 +40,32 @@ pub const EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID: Guid = Guid {
40 d: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a], 40 d: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a],
41}; 41};
42 42
43pub const EFI_LOADED_IMAGE_PROTOCOL_GUID: Guid = Guid {
44 a: 0x5b1b31a1,
45 b: 0x9562,
46 c: 0x11d2,
47 d: [0x8e, 0x3f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b],
48};
49
50/// Minimal EFI_LOADED_IMAGE_PROTOCOL — we only need the image base/size
51/// to reserve the kernel's own code/data/bss pages so the heap and page
52/// allocators can never overwrite the running kernel.
53#[repr(C)]
54pub struct LoadedImageProtocol {
55 pub revision: u32,
56 _pad0: u32,
57 pub parent_handle: Handle,
58 pub system_table: *mut core::ffi::c_void,
59 pub device_handle: Handle,
60 pub file_path: *mut core::ffi::c_void,
61 _reserved: *mut core::ffi::c_void,
62 pub load_options_size: u32,
63 _pad1: u32,
64 pub load_options: *mut core::ffi::c_void,
65 pub image_base: *mut core::ffi::c_void,
66 pub image_size: u64,
67}
68
43// --------------------------------------------------------------------------- 69// ---------------------------------------------------------------------------
44// Table header 70// Table header
45// --------------------------------------------------------------------------- 71// ---------------------------------------------------------------------------