diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-02 16:06:40 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-02 16:06:40 +0200 |
| commit | a459dd3fb267bfba24c3fad1e3dc789bf50ee20b (patch) | |
| tree | f2d541f442d9fec5cf6ecb210c5b89710870ecfb | |
| parent | v0.5.5: on-screen boot markers for disk/fs stage (diff) | |
| download | rs-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.rs | 8 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 39 | ||||
| -rw-r--r-- | rsnix/src/sched.rs | 113 |
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 | ||
| 94 | fn 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. | ||
| 97 | pub 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. | ||
| 112 | pub 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. |
| 105 | const HEAP_GROW_PAGES: usize = 64; | 142 | const 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 |
