From a459dd3fb267bfba24c3fad1e3dc789bf50ee20b Mon Sep 17 00:00:00 2001 From: salmonik Date: Sun, 2 Aug 2026 16:06:40 +0200 Subject: v0.5.6: fault dumps carry kernel image base + stack backtrace - fault_dump prints img= (kernel image base) on the serial line and the on-screen band, so a real-hardware repro can be resolved against the PDB without serial - backtrace: rbp-chain walk then a raw scan of the stack above the interrupted rsp; both filter to the .text range (PE-header parse via memory::text_range) so only code addresses are collected - on-screen boot marker shows the kernel image range at every boot - crash.log now records img= + bt= lines alongside rip/rsp --- rsnix/src/memory.rs | 39 +++++++++++++++++- rsnix/src/sched.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 147 insertions(+), 5 deletions(-) (limited to 'rsnix/src') diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs index e6896f2..35f3464 100644 --- a/rsnix/src/memory.rs +++ b/rsnix/src/memory.rs @@ -91,7 +91,10 @@ pub fn set_kernel_image_range(base: usize, size: usize) { } } -fn kernel_image_range() -> Option<(usize, usize)> { +/// The loaded kernel image range `(start, end)`, if known. Used to +/// resolve fault RIPs against the symbol table and to filter stack +/// scans for return addresses. +pub fn kernel_image_range() -> Option<(usize, usize)> { unsafe { if KERNEL_IMAGE_END > KERNEL_IMAGE_START { Some((KERNEL_IMAGE_START, KERNEL_IMAGE_END)) @@ -101,6 +104,40 @@ fn kernel_image_range() -> Option<(usize, usize)> { } } +/// The in-memory `.text` section range `(start, end)`, parsed from the +/// loaded PE headers. Used to filter fault backtraces: only code +/// addresses are meaningful return addresses (the rest of the image +/// range is .rdata/.data pointers). Falls back to the full image +/// range if the headers are unreadable. +pub fn text_range() -> Option<(usize, usize)> { + let (base, _end) = kernel_image_range()?; + unsafe { + let base = base as *const u8; + if base.read() != b'M' || base.add(1).read() != b'Z' { + return kernel_image_range(); + } + let e_lfanew = (base.add(0x3c) as *const u32).read() as usize; + let pe = base.add(e_lfanew); + if (pe as *const u32).read() != 0x4550 { + return kernel_image_range(); + } + let fh = pe.add(4); // COFF file header (20 bytes) + let size_opt = (fh.add(16) as *const u16).read() as usize; + let nsec = (fh.add(2) as *const u16).read() as usize; + let sec = fh.add(20).add(size_opt); + for i in 0..nsec { + let s = sec.add(i * 40); + let nm = core::slice::from_raw_parts(s, 8); + if nm.starts_with(b".text") { + let vsz = (s.add(8) as *const u32).read() as usize; + let va = (s.add(12) as *const u32).read() as usize; + return Some((base as usize + va, base as usize + va + vsz)); + } + } + kernel_image_range() + } +} + /// Minimum pages `grow_heap` takes from the page allocator per growth. const HEAP_GROW_PAGES: usize = 64; diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index a195d1c..a31ea6e 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs @@ -571,6 +571,65 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra let id = unsafe { CURRENT }; let name = name_str(id); let in_kernel = unsafe { CURRENT } <= IDLE_TID; + let img = crate::memory::kernel_image_range(); + // Return-address filter: `.text` only (code addresses). Falls + // back to the full image range if the PE headers are unreadable. + let filter = crate::memory::text_range(); + + // Backtrace: candidate return addresses on the faulting stack. + // 1) Walk the rbp chain (functions that keep frame pointers). + // 2) Fill any remaining slots with a raw scan of the stack above + // the interrupted rsp — robust when rbp is stale, since rustc + // eliminates frame pointers in many functions. Both filters + // accept only addresses inside the kernel image range; the + // range is `(start, end)`, so the second element is used as-is. + let mut bt: [u64; 12] = [0; 12]; + let mut bn = 0usize; + unsafe { + let mut fp = frame.rbp; + let mut steps = 0; + while bn < bt.len() && steps < 64 { + if fp == 0 + || fp < frame.rsp.saturating_sub(0x8000) + || fp > frame.rsp.saturating_add(0x100000) + { + break; + } + let ra = *(fp as *const u64).add(1); // [rbp+8] = return address + if let Some((start, end)) = filter + && ra >= start as u64 && ra < end as u64 { + bt[bn] = ra; + bn += 1; + } + let nfp = *(fp as *const u64); + if nfp <= fp { + break; // chain must grow upward + } + fp = nfp; + steps += 1; + } + // Raw scan: 32 KiB above the interrupted rsp. + let mut p = frame.rsp; + let limit = frame.rsp.saturating_add(0x8000); + while p + 8 <= limit && bn < bt.len() { + let v = *(p as *const u64); + if let Some((start, end)) = filter + && v >= start as u64 && v < end as u64 { + let mut dup = false; + for i in 0..bn { + if bt[i] == v { + dup = true; + break; + } + } + if !dup { + bt[bn] = v; + bn += 1; + } + } + p += 8; + } + } // Serial: a full, parseable line (stack-formatted). let mut buf = [0u8; 176]; @@ -579,16 +638,18 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra let _ = fmt::write( &mut w, core::format_args!( - "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"", + "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rbp={:#x} rflags={:#x} task={} \"{}\" img={:#x}", kind, vector, error, rip, cr2, rsp, + frame.rbp, rflags, id, - name + name, + img.map(|(b, _)| b).unwrap_or(0) ), ); w.len() @@ -596,6 +657,25 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra 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"); + if bn > 0 { + let mut bb = [0u8; 256]; + let bbn = { + let mut w = crate::strbuf::FmtBuf::new(&mut bb); + let mut first = true; + for i in 0..bn { + if !first { + let _ = fmt::write(&mut w, core::format_args!(" ")); + } + first = false; + let _ = fmt::write(&mut w, core::format_args!("{:#x}", bt[i])); + } + w.len() + }; + let bt_line = core::str::from_utf8(&bb[..bbn]).unwrap_or("?"); + crate::serial::write_str("bt="); + crate::serial::write_str(bt_line); + crate::serial::write_str("\r\n"); + } // Screen: white-on-red band via the clamped raw dump path (heap-free). let mut b1 = [0u8; 72]; @@ -618,14 +698,39 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra let mut w = crate::strbuf::FmtBuf::new(&mut b3); let _ = fmt::write( &mut w, - core::format_args!("rsp={:#x} task={} {}", rsp, id, name), + core::format_args!( + "rsp={:#x} task={} {} img={:#x}", + rsp, + id, + name, + img.map(|(b, _)| b).unwrap_or(0) + ), ); w.len() }; + let mut b4 = [0u8; 216]; + let n4 = { + let mut w = crate::strbuf::FmtBuf::new(&mut b4); + if bn > 0 { + let _ = fmt::write(&mut w, core::format_args!("bt=")); + let mut first = true; + for i in 0..bn { + if !first { + let _ = fmt::write(&mut w, core::format_args!(" ")); + } + first = false; + let _ = fmt::write(&mut w, core::format_args!("{:#x}", bt[i])); + } + } else { + let _ = fmt::write(&mut w, core::format_args!("bt=none")); + } + 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]; + let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or("bt=?"); + let lines = [l1, l2, l3, l4]; crate::gop::dump_lines(&lines); // Best-effort: persist the report to the disk so it survives the -- cgit v1.2.3