code wiki / (root) / nx_backtrace.nx

nx_backtrace.nx source

↩ module page · 184 lines · 6246 B

1// nx_backtrace.nx -- frame-pointer-based stack walker. 2// 3// Walks the saved-frame-pointer chain to recover return addresses 4// for a backtrace. Used by: 5// - Panic handler (print stack on assertion / SIGSEGV) 6// - Sampling profilers 7// - Memory-leak attribution (allocate-site tracking) 8// 9// Convention (RV64 ABI): 10// - s0 / fp = current frame pointer 11// - At each call, the prologue stores ra and previous-fp on the 12// stack and sets fp = sp + frame_size. 13// - The slot at [fp - 8] holds the previous frame's ra. 14// - The slot at [fp - 16] holds the previous frame's saved-fp. 15// 16// Limitations: 17// - Functions compiled WITHOUT a frame pointer (e.g. -fomit-frame-pointer 18// leaf functions) are invisible to this walker. For full coverage 19// we need DWARF .eh_frame / nx_dwarf_cfi-driven unwinding -- that 20// belongs in a future nx_unwind module. 21// - We bound the walk at 64 frames to avoid runaway loops on 22// corrupted stacks. 23// 24// API: 25// n = nx_bt_capture(start_fp, frames, max) -> count of frames captured 26// nx_bt_print(io, frames, n) -> print to fd 27// ra = nx_bt_self_fp() -> read fp register 28// 29// nx_bt_self_fp is a stub today (NishiLang doesn't expose inline 30// asm yet); callers pass in a frame pointer they captured via the 31// host runtime. Once asm-block syntax lands the stub becomes a 32// real read of x8. 33 34// nx_safety_envelope: 35// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 36// sil_target: SIL1 37// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 38// verdict: NOT_YET_EVALUATED 39 40import "syscalls.nx" 41 42// One captured frame. 43struct NxBtFrame { 44 pc: i64, // return address into the calling function 45 fp: i64, // saved fp from the previous frame 46} 47 48const NX_BT_FRAME_BYTES: i64 = 16 49const NX_BT_MAX_FRAMES: i64 = 64 50 51// Walk fp chain starting at `start_fp`. Stops when fp == 0 or 52// when we've captured `max` frames or hit NX_BT_MAX_FRAMES. 53// 54// `frames` must be at least max * NX_BT_FRAME_BYTES bytes. 55// 56// Returns the number of frames written. 57func nx_bt_capture(start_fp: i64, frames: *NxBtFrame, max: i64) -> i64 { 58 if start_fp == 0 { return 0 } 59 var fp: i64 = start_fp 60 var n: i64 = 0 61 var cap: i64 = max 62 if cap > NX_BT_MAX_FRAMES { cap = NX_BT_MAX_FRAMES } 63 64 while n < cap { 65 if fp == 0 { return n } 66 // Sanity: fp must be 16-byte aligned. 67 if (fp & 15) != 0 { return n } 68 69 let ra_p: *i64 = (fp - 8) as *i64 70 let prev_fp_p: *i64 = (fp - 16) as *i64 71 72 let ra: i64 = *ra_p 73 let prev_fp: i64 = *prev_fp_p 74 75 let f: *NxBtFrame = (((frames as i64) + n * NX_BT_FRAME_BYTES) as *NxBtFrame) 76 f.pc = ra 77 f.fp = prev_fp 78 n = n + 1 79 80 // Forward progress: prev_fp must be > current fp (stack 81 // grows down, walking up). If not, halt. 82 if prev_fp <= fp { return n } 83 fp = prev_fp 84 } 85 return n 86} 87 88// Print one PC as decimal then hex into fd 2. No symbol resolution 89// today; pair with nx_addr2line later. 90func nx_bt_print_pc(fd: i64, pc: i64) -> i64 { 91 sys_write(fd, " pc=" as *u8, 5) 92 let buf: *u8 = sys_mmap(32) 93 var n: i64 = pc 94 if n < 0 { sys_write(fd, "-" as *u8, 1); n = 0 - n } 95 if n == 0 { sys_write(fd, "0" as *u8, 1) } 96 var k: i64 = 0 97 while n > 0 { 98 buf[k] = 0x30 + (n - (n / 10) * 10) 99 n = n / 10 100 k = k + 1 101 } 102 var j: i64 = k - 1 103 while j >= 0 { 104 sys_write(fd, (((buf as i64) + j) as *u8), 1) 105 j = j - 1 106 } 107 // Hex. 108 sys_write(fd, " (0x" as *u8, 4) 109 let hex_buf: *u8 = sys_mmap(32) 110 var p: i64 = pc 111 var hk: i64 = 0 112 if p == 0 { hex_buf[0] = 0x30; hk = 1 } 113 while p != 0 { 114 let d: i64 = p & 0xF 115 if d < 10 { hex_buf[hk] = 0x30 + d } else { hex_buf[hk] = 0x57 + d } 116 p = (p >> 4) & 0x0FFFFFFFFFFFFFFF 117 hk = hk + 1 118 } 119 var hj: i64 = hk - 1 120 while hj >= 0 { 121 sys_write(fd, (((hex_buf as i64) + hj) as *u8), 1) 122 hj = hj - 1 123 } 124 sys_write(fd, ")\n" as *u8, 2) 125 return 0 126} 127 128// Print captured frames. 129func nx_bt_print(fd: i64, frames: *NxBtFrame, n: i64) -> i64 { 130 sys_write(fd, "backtrace:\n" as *u8, 11) 131 var i: i64 = 0 132 while i < n { 133 let f: *NxBtFrame = (((frames as i64) + i * NX_BT_FRAME_BYTES) as *NxBtFrame) 134 nx_bt_print_pc(fd, f.pc) 135 i = i + 1 136 } 137 return 0 138} 139 140// ---- self-test --------------------------------------------------- 141 142func main() -> i64 { 143 // Build a fake fp chain in heap memory and walk it. 144 // 145 // Layout (each frame = 16 bytes: prev_fp at [fp-16], ra at [fp-8]): 146 // [chain[0..16)] prev_fp -> 0 ra -> 0xAAAA (oldest) 147 // [chain[16..32)] prev_fp -> 16 ra -> 0xBBBB 148 // [chain[32..48)] prev_fp -> 32 ra -> 0xCCCC (newest) 149 // 150 // start_fp = 48 (== one past chain[32..48), fp points just past 151 // the newest frame). 152 let chain_raw: *u8 = sys_mmap(64) 153 let chain: *i64 = chain_raw as *i64 154 chain[0] = 0 // prev_fp for oldest = 0 (terminator) 155 chain[1] = 0xAAAA // ra for oldest 156 chain[2] = 16 // prev_fp for middle (points at frame 0's saved area) 157 158 chain[3] = 0xBBBB // ra for middle 159 chain[4] = 32 // prev_fp for newest 160 chain[5] = 0xCCCC // ra for newest 161 162 // start_fp = address of "one past" the newest frame. 163 let start_fp: i64 = (chain as i64) + 48 164 165 let frames_raw: *u8 = sys_mmap(NX_BT_FRAME_BYTES * 4) 166 let frames: *NxBtFrame = frames_raw as *NxBtFrame 167 let n: i64 = nx_bt_capture(start_fp, frames, 4) 168 169 if n != 3 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 170 171 let f0: *NxBtFrame = frames 172 let f1: *NxBtFrame = (((frames as i64) + NX_BT_FRAME_BYTES) as *NxBtFrame) 173 let f2: *NxBtFrame = (((frames as i64) + 2 * NX_BT_FRAME_BYTES) as *NxBtFrame) 174 175 if f0.pc != 0xCCCC { return __syscall(93, 2, 0, 0, 0, 0, 0) } 176 if f1.pc != 0xBBBB { return __syscall(93, 3, 0, 0, 0, 0, 0) } 177 if f2.pc != 0xAAAA { return __syscall(93, 4, 0, 0, 0, 0, 0) } 178 179 // Empty start 180 let n2: i64 = nx_bt_capture(0, frames, 4) 181 if n2 != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 182 183 return 0 184}