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}