summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-02 16:06:40 +0200
committersalmonik <salmonik@tuta.io>2026-08-02 16:06:40 +0200
commita459dd3fb267bfba24c3fad1e3dc789bf50ee20b (patch)
treef2d541f442d9fec5cf6ecb210c5b89710870ecfb
parentv0.5.5: on-screen boot markers for disk/fs stage (diff)
downloadrs-dos-a459dd3fb267bfba24c3fad1e3dc789bf50ee20b.tar.gz
rs-dos-a459dd3fb267bfba24c3fad1e3dc789bf50ee20b.zip
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
-rw-r--r--rsdos/src/main.rs8
-rw-r--r--rsnix/src/memory.rs39
-rw-r--r--rsnix/src/sched.rs113
3 files changed, 155 insertions, 5 deletions
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs
index 4fe89c9..60acc4e 100644
--- a/rsdos/src/main.rs
+++ b/rsdos/src/main.rs
@@ -197,6 +197,14 @@ pub unsafe extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut
197 cols, 197 cols,
198 rows 198 rows
199 )); 199 ));
200 if let Some((ib, ie)) = rsnix::memory::kernel_image_range() {
201 rsnix::gop::boot_marker(&alloc::format!(
202 "rsnix: img 0x{:x}-0x{:x} ({} KiB)",
203 ib,
204 ie,
205 (ie - ib) / 1024
206 ));
207 }
200 208
201 // ExitBootServices — we now own the machine 209 // ExitBootServices — we now own the machine
202 if unsafe { (bs.exit_boot_services)(image_handle, map_key) } != EFI_SUCCESS { 210 if unsafe { (bs.exit_boot_services)(image_handle, map_key) } != EFI_SUCCESS {
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) {
91 } 91 }
92} 92}
93 93
94fn kernel_image_range() -> Option<(usize, usize)> { 94/// The loaded kernel image range `(start, end)`, if known. Used to
95/// resolve fault RIPs against the symbol table and to filter stack
96/// scans for return addresses.
97pub fn kernel_image_range() -> Option<(usize, usize)> {
95 unsafe { 98 unsafe {
96 if KERNEL_IMAGE_END > KERNEL_IMAGE_START { 99 if KERNEL_IMAGE_END > KERNEL_IMAGE_START {
97 Some((KERNEL_IMAGE_START, KERNEL_IMAGE_END)) 100 Some((KERNEL_IMAGE_START, KERNEL_IMAGE_END))
@@ -101,6 +104,40 @@ fn kernel_image_range() -> Option<(usize, usize)> {
101 } 104 }
102} 105}
103 106
107/// The in-memory `.text` section range `(start, end)`, parsed from the
108/// loaded PE headers. Used to filter fault backtraces: only code
109/// addresses are meaningful return addresses (the rest of the image
110/// range is .rdata/.data pointers). Falls back to the full image
111/// range if the headers are unreadable.
112pub fn text_range() -> Option<(usize, usize)> {
113 let (base, _end) = kernel_image_range()?;
114 unsafe {
115 let base = base as *const u8;
116 if base.read() != b'M' || base.add(1).read() != b'Z' {
117 return kernel_image_range();
118 }
119 let e_lfanew = (base.add(0x3c) as *const u32).read() as usize;
120 let pe = base.add(e_lfanew);
121 if (pe as *const u32).read() != 0x4550 {
122 return kernel_image_range();
123 }
124 let fh = pe.add(4); // COFF file header (20 bytes)
125 let size_opt = (fh.add(16) as *const u16).read() as usize;
126 let nsec = (fh.add(2) as *const u16).read() as usize;
127 let sec = fh.add(20).add(size_opt);
128 for i in 0..nsec {
129 let s = sec.add(i * 40);
130 let nm = core::slice::from_raw_parts(s, 8);
131 if nm.starts_with(b".text") {
132 let vsz = (s.add(8) as *const u32).read() as usize;
133 let va = (s.add(12) as *const u32).read() as usize;
134 return Some((base as usize + va, base as usize + va + vsz));
135 }
136 }
137 kernel_image_range()
138 }
139}
140
104/// Minimum pages `grow_heap` takes from the page allocator per growth. 141/// Minimum pages `grow_heap` takes from the page allocator per growth.
105const HEAP_GROW_PAGES: usize = 64; 142const HEAP_GROW_PAGES: usize = 64;
106 143
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
571 let id = unsafe { CURRENT }; 571 let id = unsafe { CURRENT };
572 let name = name_str(id); 572 let name = name_str(id);
573 let in_kernel = unsafe { CURRENT } <= IDLE_TID; 573 let in_kernel = unsafe { CURRENT } <= IDLE_TID;
574 let img = crate::memory::kernel_image_range();
575 // Return-address filter: `.text` only (code addresses). Falls
576 // back to the full image range if the PE headers are unreadable.
577 let filter = crate::memory::text_range();
578
579 // Backtrace: candidate return addresses on the faulting stack.
580 // 1) Walk the rbp chain (functions that keep frame pointers).
581 // 2) Fill any remaining slots with a raw scan of the stack above
582 // the interrupted rsp — robust when rbp is stale, since rustc
583 // eliminates frame pointers in many functions. Both filters
584 // accept only addresses inside the kernel image range; the
585 // range is `(start, end)`, so the second element is used as-is.
586 let mut bt: [u64; 12] = [0; 12];
587 let mut bn = 0usize;
588 unsafe {
589 let mut fp = frame.rbp;
590 let mut steps = 0;
591 while bn < bt.len() && steps < 64 {
592 if fp == 0
593 || fp < frame.rsp.saturating_sub(0x8000)
594 || fp > frame.rsp.saturating_add(0x100000)
595 {
596 break;
597 }
598 let ra = *(fp as *const u64).add(1); // [rbp+8] = return address
599 if let Some((start, end)) = filter
600 && ra >= start as u64 && ra < end as u64 {
601 bt[bn] = ra;
602 bn += 1;
603 }
604 let nfp = *(fp as *const u64);
605 if nfp <= fp {
606 break; // chain must grow upward
607 }
608 fp = nfp;
609 steps += 1;
610 }
611 // Raw scan: 32 KiB above the interrupted rsp.
612 let mut p = frame.rsp;
613 let limit = frame.rsp.saturating_add(0x8000);
614 while p + 8 <= limit && bn < bt.len() {
615 let v = *(p as *const u64);
616 if let Some((start, end)) = filter
617 && v >= start as u64 && v < end as u64 {
618 let mut dup = false;
619 for i in 0..bn {
620 if bt[i] == v {
621 dup = true;
622 break;
623 }
624 }
625 if !dup {
626 bt[bn] = v;
627 bn += 1;
628 }
629 }
630 p += 8;
631 }
632 }
574 633
575 // Serial: a full, parseable line (stack-formatted). 634 // Serial: a full, parseable line (stack-formatted).
576 let mut buf = [0u8; 176]; 635 let mut buf = [0u8; 176];
@@ -579,16 +638,18 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra
579 let _ = fmt::write( 638 let _ = fmt::write(
580 &mut w, 639 &mut w,
581 core::format_args!( 640 core::format_args!(
582 "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rflags={:#x} task={} \"{}\"", 641 "!!! {} vec={} err={:#x} rip={:#x} cr2={:#x} rsp={:#x} rbp={:#x} rflags={:#x} task={} \"{}\" img={:#x}",
583 kind, 642 kind,
584 vector, 643 vector,
585 error, 644 error,
586 rip, 645 rip,
587 cr2, 646 cr2,
588 rsp, 647 rsp,
648 frame.rbp,
589 rflags, 649 rflags,
590 id, 650 id,
591 name 651 name,
652 img.map(|(b, _)| b).unwrap_or(0)
592 ), 653 ),
593 ); 654 );
594 w.len() 655 w.len()
@@ -596,6 +657,25 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra
596 let line = core::str::from_utf8(&buf[..n]).unwrap_or("!!! dump"); 657 let line = core::str::from_utf8(&buf[..n]).unwrap_or("!!! dump");
597 crate::serial::write_str(line); 658 crate::serial::write_str(line);
598 crate::serial::write_str("\r\n!!! halting (shell task fault — machine stops)\r\n"); 659 crate::serial::write_str("\r\n!!! halting (shell task fault — machine stops)\r\n");
660 if bn > 0 {
661 let mut bb = [0u8; 256];
662 let bbn = {
663 let mut w = crate::strbuf::FmtBuf::new(&mut bb);
664 let mut first = true;
665 for i in 0..bn {
666 if !first {
667 let _ = fmt::write(&mut w, core::format_args!(" "));
668 }
669 first = false;
670 let _ = fmt::write(&mut w, core::format_args!("{:#x}", bt[i]));
671 }
672 w.len()
673 };
674 let bt_line = core::str::from_utf8(&bb[..bbn]).unwrap_or("?");
675 crate::serial::write_str("bt=");
676 crate::serial::write_str(bt_line);
677 crate::serial::write_str("\r\n");
678 }
599 679
600 // Screen: white-on-red band via the clamped raw dump path (heap-free). 680 // Screen: white-on-red band via the clamped raw dump path (heap-free).
601 let mut b1 = [0u8; 72]; 681 let mut b1 = [0u8; 72];
@@ -618,14 +698,39 @@ pub(crate) fn fault_dump(kind: &str, cr2: u64, frame: &crate::interrupts::IntFra
618 let mut w = crate::strbuf::FmtBuf::new(&mut b3); 698 let mut w = crate::strbuf::FmtBuf::new(&mut b3);
619 let _ = fmt::write( 699 let _ = fmt::write(
620 &mut w, 700 &mut w,
621 core::format_args!("rsp={:#x} task={} {}", rsp, id, name), 701 core::format_args!(
702 "rsp={:#x} task={} {} img={:#x}",
703 rsp,
704 id,
705 name,
706 img.map(|(b, _)| b).unwrap_or(0)
707 ),
622 ); 708 );
623 w.len() 709 w.len()
624 }; 710 };
711 let mut b4 = [0u8; 216];
712 let n4 = {
713 let mut w = crate::strbuf::FmtBuf::new(&mut b4);
714 if bn > 0 {
715 let _ = fmt::write(&mut w, core::format_args!("bt="));
716 let mut first = true;
717 for i in 0..bn {
718 if !first {
719 let _ = fmt::write(&mut w, core::format_args!(" "));
720 }
721 first = false;
722 let _ = fmt::write(&mut w, core::format_args!("{:#x}", bt[i]));
723 }
724 } else {
725 let _ = fmt::write(&mut w, core::format_args!("bt=none"));
726 }
727 w.len()
728 };
625 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!"); 729 let l1 = core::str::from_utf8(&b1[..n1]).unwrap_or("!!!");
626 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or(""); 730 let l2 = core::str::from_utf8(&b2[..n2]).unwrap_or("");
627 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or(""); 731 let l3 = core::str::from_utf8(&b3[..n3]).unwrap_or("");
628 let lines = [l1, l2, l3]; 732 let l4 = core::str::from_utf8(&b4[..n4]).unwrap_or("bt=?");
733 let lines = [l1, l2, l3, l4];
629 crate::gop::dump_lines(&lines); 734 crate::gop::dump_lines(&lines);
630 735
631 // Best-effort: persist the report to the disk so it survives the 736 // Best-effort: persist the report to the disk so it survives the