Skip to main content

strat9_kernel/boot/
panic.rs

1use crate::arch::xshim::VirtAddr;
2use core::{
3    panic::PanicInfo,
4    sync::atomic::{AtomicBool, Ordering},
5};
6use spin::Mutex;
7type PanicHook = fn(&PanicInfo);
8const MAX_PANIC_HOOKS: usize = 8;
9
10static PANIC_HOOKS: Mutex<[Option<PanicHook>; MAX_PANIC_HOOKS]> =
11    Mutex::new([None; MAX_PANIC_HOOKS]);
12static PANIC_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
13
14/// Returns true if a kernel panic is currently in progress.
15pub fn panic_in_progress() -> bool {
16    PANIC_IN_PROGRESS.load(Ordering::SeqCst)
17}
18
19/// Register a function to be called during a panic (serial-only hooks).
20pub fn register_panic_hook(hook: PanicHook) -> bool {
21    crate::e9_mark!(b'P');
22    let mut hooks = PANIC_HOOKS.lock();
23    crate::e9_mark!(b'p');
24    for slot in hooks.iter_mut() {
25        if slot.is_none() {
26            *slot = Some(hook);
27            crate::e9_mark!(b'q');
28            return true;
29        }
30    }
31    crate::e9_mark!(b'r');
32    false
33}
34
35/// Run all registered panic hooks (serial output only : no framebuffer access).
36fn run_panic_hooks(info: &PanicInfo) {
37    match PANIC_HOOKS.try_lock() {
38        Some(hooks) => {
39            for hook in hooks.iter().flatten() {
40                hook(info);
41            }
42        }
43        None => {
44            crate::serial_force_println!(
45                "[panic] WARNING: panic hooks skipped (lock held by panicking context)"
46            );
47        }
48    }
49}
50
51/// Dump CPU/scheduler context to serial.
52fn panic_hook_dump_context(_info: &PanicInfo) {
53    let cpu = crate::arch::percpu::current_cpu_index();
54    let ticks = crate::process::scheduler::ticks();
55    let cr3 = crate::memory::paging::active_page_table().as_u64();
56    crate::serial_println!("panic-hook: cpu={} ticks={} cr3=0x{:x}", cpu, ticks, cr3);
57    if let Some(task) = crate::process::scheduler::current_task_clone_try() {
58        crate::serial_println!(
59            "panic-hook: current_task id={} name={}",
60            task.id.as_u64(),
61            task.name
62        );
63    } else {
64        crate::serial_println!("panic-hook: current_task none (scheduler locked or idle)");
65    }
66    let sched = crate::process::scheduler::state_snapshot();
67    if cpu < sched.cpu_count {
68        crate::serial_println!(
69            "panic-hook: sched cpu={} current_tid={} need_resched={} rq(rt/fair/idle)={}/{}/{} blocked={} init={} phase={}",
70            cpu,
71            sched.current_task[cpu],
72            sched.need_resched[cpu],
73            sched.rq_rt[cpu],
74            sched.rq_fair[cpu],
75            sched.rq_idle[cpu],
76            sched.blocked_tasks,
77            sched.initialized,
78            sched.boot_phase
79        );
80    }
81}
82
83// -----------------------------------------------------------------------
84// Register / stack helpers
85// -----------------------------------------------------------------------
86
87#[inline(always)]
88fn read_rbp() -> u64 {
89    let rbp: u64;
90    unsafe {
91        core::arch::asm!("mov {}, rbp", out(reg) rbp, options(nomem, nostack, preserves_flags));
92    }
93    rbp
94}
95
96#[inline(always)]
97fn read_rsp() -> u64 {
98    let rsp: u64;
99    unsafe {
100        core::arch::asm!("mov {}, rsp", out(reg) rsp, options(nomem, nostack, preserves_flags));
101    }
102    rsp
103}
104
105fn addr_readable(addr: u64) -> bool {
106    crate::memory::paging::translate(VirtAddr::new(addr)).is_some()
107}
108
109/// Read CR0, CR2, CR3, CR4 into the provided mutable references.
110fn read_cr_regs() -> (u64, u64, u64, u64) {
111    let cr0: u64;
112    let cr2: u64;
113    let cr3: u64;
114    let cr4: u64;
115    unsafe {
116        core::arch::asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags));
117        core::arch::asm!("mov {}, cr2", out(reg) cr2, options(nomem, nostack, preserves_flags));
118        core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack, preserves_flags));
119        core::arch::asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack, preserves_flags));
120    }
121    (cr0, cr2, cr3, cr4)
122}
123
124/// Dump backtrace via frame-pointer unwinding, printing directly to serial.
125fn dump_backtrace() {
126    crate::serial_println!("RSP=0x{:016X} RBP=0x{:016X}", read_rsp(), read_rbp());
127    crate::serial_println!("Backtrace (frame-pointer):");
128    let mut rbp = read_rbp();
129    for i in 0..16 {
130        if rbp == 0 || (rbp & 0x7) != 0 {
131            crate::serial_println!("  #{:02}: stop (invalid rbp)", i);
132            break;
133        }
134        if !addr_readable(rbp) || !addr_readable(rbp.saturating_add(8)) {
135            crate::serial_println!("  #{:02}: stop (unmapped)", i);
136            break;
137        }
138        let prev = unsafe { *(rbp as *const u64) };
139        let ret = unsafe { *((rbp + 8) as *const u64) };
140
141        // Try symbol resolution
142        if let Some((name, offset)) = super::symbols::lookup(ret) {
143            if offset == 0 {
144                crate::serial_println!("  #{:02}: RIP=0x{:016X}  {}", i, ret, name);
145            } else {
146                crate::serial_println!("  #{:02}: RIP=0x{:016X}  {}+0x{:x}", i, ret, name, offset);
147            }
148        } else {
149            crate::serial_println!("  #{:02}: RIP=0x{:016X}", i, ret);
150        }
151
152        if prev <= rbp || prev.saturating_sub(rbp) > 1024 * 1024 {
153            break;
154        }
155        rbp = prev;
156    }
157}
158
159/// Collect comprehensive debug info and print directly to serial (heap-safe).
160fn dump_panic_info(info: &PanicInfo) {
161    crate::serial_force_println!("\n\x1b[31;1m!!! KERNEL PANIC !!!\x1b[0m");
162    crate::serial_force_println!("=== GURU MEDiTATiON :: KERNEL PANiK ===");
163    crate::serial_force_println!("");
164
165    // Panic location
166    if let Some(loc) = info.location() {
167        crate::serial_force_println!("File: {}:{}:{}", loc.file(), loc.line(), loc.column());
168    } else {
169        crate::serial_force_println!("File: (unknown)");
170    }
171    crate::serial_force_println!("Message: {}", info.message());
172    crate::serial_force_println!("");
173
174    // CPU state
175    let cpu = crate::arch::percpu::current_cpu_index();
176    let (cr0, cr2, cr3, cr4) = read_cr_regs();
177    let rsp = read_rsp();
178    let rbp = read_rbp();
179    crate::serial_force_println!("CPU={}  CR0={:#X}  CR2={:#X}", cpu, cr0, cr2);
180    crate::serial_force_println!("CR3={:#X}  CR4={:#X}", cr3, cr4);
181    crate::serial_force_println!("RSP={:#018X}  RBP={:#018X}", rsp, rbp);
182    crate::serial_force_println!("");
183
184    // Backtrace
185    dump_backtrace();
186    crate::serial_force_println!("");
187
188    // Scheduler state
189    let ticks = crate::process::scheduler::ticks();
190    crate::serial_force_println!("Ticks={}", ticks);
191    if let Some(task) = crate::process::scheduler::current_task_clone_try() {
192        crate::serial_force_println!("Task: id={} name={}", task.id.as_u64(), task.name);
193    } else {
194        crate::serial_force_println!("Task: (scheduler locked / idle)");
195    }
196
197    let sched = crate::process::scheduler::state_snapshot();
198    if cpu < sched.cpu_count {
199        crate::serial_force_println!(
200            "Sched: tid={} rt={} fair={} idle={} blocked={} init={}",
201            sched.current_task[cpu],
202            sched.rq_rt[cpu],
203            sched.rq_fair[cpu],
204            sched.rq_idle[cpu],
205            sched.blocked_tasks,
206            sched.initialized,
207        );
208    }
209    crate::serial_force_println!("");
210
211    // Uptime
212    let ts = crate::arch::boot_timestamp::elapsed_ms();
213    crate::serial_force_println!("Uptime: {} ms", ts);
214}
215
216// -----------------------------------------------------------------------
217// Public API
218// -----------------------------------------------------------------------
219
220/// Install the default panic hooks (serial context + backtrace dumps).
221pub fn install_default_panic_hooks() {
222    crate::e9_mark!(b'O');
223    let _ = register_panic_hook(panic_hook_dump_context);
224    crate::e9_mark!(b'o');
225}
226
227/// Main kernel panic handler.
228///
229/// Always emits full debug info to the serial port.  Tries to display it
230/// on the framebuffer via the normal VGA_WRITER path.  If VGA_WRITER is
231/// locked (e.g. the fault happened inside a write to the console), falls
232/// back to a direct framebuffer draw that bypasses all locks.
233///
234/// After all output is delivered, halts the current CPU forever.
235pub fn panic_handler(info: &PanicInfo) -> ! {
236    // Visible even when the kernel serial path is not initialized yet.
237    crate::e9_println!("[panic] {}", info.message());
238    crate::e9_println!("[panic] at {:?}", info.location());
239    // 1. Emergency serial mode : serial_println! bypasses all locks.
240    crate::arch::serial::enter_emergency_mode();
241
242    // 2. Guard against recursive panics.
243    if PANIC_IN_PROGRESS.swap(true, Ordering::SeqCst) {
244        // Double panic: dump as much as we can with stack-only formatting.
245        crate::arch::cli();
246        crate::serial_force_println!("\n\x1b[31;1m!!! DOUBLE PANIC !!!\x1b[0m");
247        if let Some(loc) = info.location() {
248            crate::serial_force_println!("panic at {}:{}", loc.file(), loc.line());
249        }
250        crate::serial_force_println!("Message: {}", info.message());
251        let cpu = crate::arch::percpu::current_cpu_index();
252        let (cr0, cr2, cr3, cr4) = read_cr_regs();
253        crate::serial_force_println!(
254            "CPU={}  CR0={:#X}  CR2={:#X}  CR3={:#X}  CR4={:#X}",
255            cpu,
256            cr0,
257            cr2,
258            cr3,
259            cr4
260        );
261        crate::serial_force_println!("RSP={:#018X}  RBP={:#018X}", read_rsp(), read_rbp());
262        dump_backtrace();
263        loop {
264            crate::arch::hlt();
265        }
266    }
267
268    // 3. Disable interrupts so nothing disturbs the dump.
269    crate::arch::cli();
270
271    // 3b. Panic beep : audible signal before we halt.
272    crate::arch::speaker::beep_panic();
273
274    // 4. Dump all debug information directly to serial (heap-free).
275    dump_panic_info(info);
276
277    // 5. Run custom panic hooks (serial-only).
278    run_panic_hooks(info);
279
280    // 6. Stop all other CPUs and wait for them to halt.
281    crate::arch::smp::broadcast_panic_halt();
282    // Brief delay to let other CPUs observe the NMI/halt IPI before we
283    // touch shared framebuffer memory.
284    for _ in 0..100_000 {
285        core::hint::spin_loop();
286    }
287
288    // 7. Flush the VGA circular buffer so any buffered log lines appear.
289    crate::arch::vgabuf::vgabuf_flush_all();
290
291    // 8. Display panic info on framebuffer (best-effort, may use heap for String formatting).
292    if crate::arch::vga::is_available() {
293        if let Some(mut writer) = crate::arch::vga::VGA_WRITER.try_lock() {
294            use core::fmt::Write;
295            writer.set_rgb_color(
296                crate::arch::vga::RgbColor::new(0xFF, 0xE7, 0xA0),
297                crate::arch::vga::RgbColor::new(0x3A, 0x1F, 0x00),
298            );
299            writer.clear();
300            let _ = writeln!(writer, "=== GURU MEDiTATiON :: KERNEL PANiK ===");
301            if let Some(loc) = info.location() {
302                let _ = writeln!(
303                    writer,
304                    "File: {}:{}:{}",
305                    loc.file(),
306                    loc.line(),
307                    loc.column()
308                );
309            }
310            let _ = writeln!(writer, "Message: {}", info.message());
311            let _ = writeln!(writer, "");
312            let _ = writeln!(writer, "See serial output for full backtrace.");
313        } else {
314            // VGA_WRITER locked : draw directly to the framebuffer.
315            let title = "=== GURU MEDiTATiON :: KERNEL PANiK ===";
316            let serial_hint = "See serial output for full backtrace.";
317            let str_lines: [&str; 2] = [title, serial_hint];
318            crate::arch::vga::panic_draw_direct(&str_lines);
319        }
320    }
321
322    // 9. Halt forever.
323    loop {
324        crate::arch::hlt();
325    }
326}