nx_addr2line.nx source
↩ module page · 287 lines · 10231 B
1// nx_addr2line.nx -- sovereign addr2line.
2//
3// Given an ELF + a code address, walk the DWARF .debug_line program
4// and return the source (file_idx, line) at that address. Foundation
5// for the future nx_dbg.nx debugger ("show me the source line at PC")
6// + sovereign nx_panic backtraces with file:line locations.
7//
8// What we do (DWARF v5 ยง6.2 line-program VM):
9//
10// 1. Locate .debug_line section via nx_elf_read.
11// 2. Decode the line-program header (file table + dir table).
12// 3. Run the line-program VM until address >= target_addr or end.
13// 4. Return the (file_idx, line) of the most recent committed row
14// whose address <= target.
15//
16// Pairs with nx_dwarf_line.nx which BUILDS the section; together,
17// produce + consume the same byte format with our own code only.
18//
19// v0.0.1 scope:
20// * Decode the standard opcodes our nx_dwarf_line emits:
21// DW_LNS_copy, DW_LNS_advance_pc, DW_LNS_advance_line,
22// DW_LNS_set_file
23// * Decode extended ops:
24// DW_LNE_end_sequence, DW_LNE_set_address
25//
26// Special opcodes (the compact bias-encoded form) NOT yet decoded;
27// our emitter doesn't use them today. Add when the emitter does.
28
29// nx_safety_envelope:
30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
31// sil_target: SIL1
32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
33// verdict: NOT_YET_EVALUATED
34
35import "syscalls.nx"
36import "nx_elf_read.nx"
37import "nx_dwarf_line.nx"
38
39// Same constants as nx_dwarf_line, repeated here so this module is
40// independently usable. Constant unification is a future cleanup
41// (all DWARF spec constants in one nx_dwarf_const.nx).
42const NX_A2L_DW_LNS_copy: i64 = 0x01
43const NX_A2L_DW_LNS_advance_pc: i64 = 0x02
44const NX_A2L_DW_LNS_advance_line: i64 = 0x03
45const NX_A2L_DW_LNS_set_file: i64 = 0x04
46const NX_A2L_DW_LNE_end_sequence: i64 = 0x01
47const NX_A2L_DW_LNE_set_address: i64 = 0x02
48
49struct NxA2LResult {
50 found: i64, // 1 if a row covered the address
51 file_idx: i64,
52 line: i64,
53 addr: i64, // the row's address (which is <= target_addr)
54}
55
56const NX_A2L_RESULT_BYTES: i64 = 32
57
58// uleb128 decoder. Reads from buf[off..]; updates *consumed.
59func nx_a2l_uleb(buf: *u8, off: i64, consumed: *i64) -> i64 {
60 var v: i64 = 0
61 var shift: i64 = 0
62 var i: i64 = 0
63 var go: i64 = 1
64 while go == 1 {
65 let b: i64 = buf[off + i]
66 v = v | ((b & 0x7F) << shift)
67 i = i + 1
68 if (b & 0x80) == 0 { go = 0 }
69 shift = shift + 7
70 }
71 *consumed = i
72 return v
73}
74
75// sleb128 decoder.
76func nx_a2l_sleb(buf: *u8, off: i64, consumed: *i64) -> i64 {
77 var v: i64 = 0
78 var shift: i64 = 0
79 var i: i64 = 0
80 var b: i64 = 0
81 var go: i64 = 1
82 while go == 1 {
83 b = buf[off + i]
84 v = v | ((b & 0x7F) << shift)
85 shift = shift + 7
86 i = i + 1
87 if (b & 0x80) == 0 { go = 0 }
88 }
89 // Sign-extend if the last byte's bit 6 is set.
90 if shift < 64 {
91 if (b & 0x40) != 0 {
92 v = v | ((~0) << shift)
93 }
94 }
95 *consumed = i
96 return v
97}
98
99// Decode the line program at section bytes `buf[0..len)` and find
100// the row matching `target_addr`. This implements the minimum VM
101// our nx_dwarf_line emitter uses; the full DWARF line program also
102// allows special opcodes which we'll add when the emitter does.
103//
104// Note: we treat the section as a SEQUENCE (no header decode for
105// v0.0.1). Callers should pass the raw line-program bytes after
106// any header. When elf-side header decoding lands we'll handle
107// the offset bookkeeping.
108func nx_a2l_run(buf: *u8, len: i64, target_addr: i64) -> *NxA2LResult {
109 let raw: *u8 = sys_mmap(NX_A2L_RESULT_BYTES)
110 let r: *NxA2LResult = raw as *NxA2LResult
111 r.found = 0
112 r.file_idx = 1
113 r.line = 1
114 r.addr = 0
115
116 // VM state.
117 var addr: i64 = 0
118 var line: i64 = 1
119 var file_idx: i64 = 1
120
121 let consumed_raw: *u8 = sys_mmap(16)
122 let consumed: *i64 = consumed_raw as *i64
123
124 var pc: i64 = 0
125 var go: i64 = 1
126 while go == 1 {
127 if pc >= len { go = 0 }
128 if go == 1 {
129 let op: i64 = buf[pc]
130 pc = pc + 1
131
132 if op == 0 {
133 // Extended opcode: 0 + uleb len + ext_op + payload.
134 *consumed = 0
135 let ext_len: i64 = nx_a2l_uleb(buf, pc, consumed)
136 pc = pc + *consumed
137 let ext_op: i64 = buf[pc]
138 pc = pc + 1
139 if ext_op == NX_A2L_DW_LNE_end_sequence {
140 // end_sequence: VM reset for next program. We
141 // don't iterate further sequences here.
142 go = 0
143 }
144 if ext_op == NX_A2L_DW_LNE_set_address {
145 // 8-byte LE address.
146 var v: i64 = 0
147 var k: i64 = 0
148 while k < 8 {
149 v = v | (buf[pc + k] << (k * 8))
150 k = k + 1
151 }
152 addr = v
153 pc = pc + 8
154 }
155 if ext_op != NX_A2L_DW_LNE_end_sequence {
156 if ext_op != NX_A2L_DW_LNE_set_address {
157 // Skip unknown extended op's payload.
158 pc = pc + (ext_len - 1)
159 }
160 }
161 } else {
162 if op == NX_A2L_DW_LNS_copy {
163 // Commit row. If this row is the best so far
164 // (addr <= target, but greater than last best),
165 // record it.
166 if addr <= target_addr {
167 if addr > r.addr {
168 r.found = 1
169 r.addr = addr
170 r.line = line
171 r.file_idx = file_idx
172 }
173 if r.found == 0 {
174 // First-ever row covering the target.
175 r.found = 1
176 r.addr = addr
177 r.line = line
178 r.file_idx = file_idx
179 }
180 }
181 if addr > target_addr { go = 0 }
182 } else {
183 if op == NX_A2L_DW_LNS_advance_pc {
184 *consumed = 0
185 let delta: i64 = nx_a2l_uleb(buf, pc, consumed)
186 pc = pc + *consumed
187 addr = addr + delta
188 } else {
189 if op == NX_A2L_DW_LNS_advance_line {
190 *consumed = 0
191 let delta: i64 = nx_a2l_sleb(buf, pc, consumed)
192 pc = pc + *consumed
193 line = line + delta
194 } else {
195 if op == NX_A2L_DW_LNS_set_file {
196 *consumed = 0
197 let f: i64 = nx_a2l_uleb(buf, pc, consumed)
198 pc = pc + *consumed
199 file_idx = f
200 } else {
201 // Unknown standard op: cannot safely
202 // skip without abbrev-table knowledge.
203 go = 0
204 }
205 }
206 }
207 }
208 }
209 }
210 }
211
212 return r
213}
214
215// ---- self-test ---------------------------------------------------
216
217func main() -> i64 {
218 // Build a tiny line-program by hand:
219 // set_address 0x10000
220 // copy -> row(addr=0x10000, line=1)
221 // advance_pc 4
222 // advance_line +2 -> line = 3
223 // copy -> row(addr=0x10004, line=3)
224 // advance_pc 8
225 // advance_line +1 -> line = 4
226 // copy -> row(addr=0x1000C, line=4)
227 // end_sequence
228 let buf: *u8 = sys_mmap(64)
229 var p: i64 = 0
230 // set_address 0x10000
231 buf[p] = 0; p = p + 1
232 buf[p] = 9; p = p + 1
233 buf[p] = NX_A2L_DW_LNE_set_address; p = p + 1
234 buf[p] = 0x00; p = p + 1
235 buf[p] = 0x00; p = p + 1
236 buf[p] = 0x01; p = p + 1
237 buf[p] = 0x00; p = p + 1
238 buf[p] = 0x00; p = p + 1
239 buf[p] = 0x00; p = p + 1
240 buf[p] = 0x00; p = p + 1
241 buf[p] = 0x00; p = p + 1
242 // copy
243 buf[p] = NX_A2L_DW_LNS_copy; p = p + 1
244 // advance_pc 4 (uleb128 = 0x04)
245 buf[p] = NX_A2L_DW_LNS_advance_pc; p = p + 1
246 buf[p] = 4; p = p + 1
247 // advance_line +2 (sleb128 = 0x02)
248 buf[p] = NX_A2L_DW_LNS_advance_line; p = p + 1
249 buf[p] = 2; p = p + 1
250 // copy
251 buf[p] = NX_A2L_DW_LNS_copy; p = p + 1
252 // advance_pc 8
253 buf[p] = NX_A2L_DW_LNS_advance_pc; p = p + 1
254 buf[p] = 8; p = p + 1
255 // advance_line +1
256 buf[p] = NX_A2L_DW_LNS_advance_line; p = p + 1
257 buf[p] = 1; p = p + 1
258 // copy
259 buf[p] = NX_A2L_DW_LNS_copy; p = p + 1
260 // end_sequence
261 buf[p] = 0; p = p + 1
262 buf[p] = 1; p = p + 1
263 buf[p] = NX_A2L_DW_LNE_end_sequence; p = p + 1
264
265 // Lookup at 0x10004 -- should hit row 2 (line 3).
266 let r1: *NxA2LResult = nx_a2l_run(buf, p, 0x10004)
267 if r1.found != 1 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
268 if r1.line != 3 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
269 if r1.addr != 0x10004 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
270
271 // Lookup at 0x10006 (between rows) -- should still resolve to
272 // row 2 (the last committed addr <= target).
273 let r2: *NxA2LResult = nx_a2l_run(buf, p, 0x10006)
274 if r2.found != 1 { return __syscall(93, 20, 0, 0, 0, 0, 0) }
275 if r2.line != 3 { return __syscall(93, 21, 0, 0, 0, 0, 0) }
276
277 // Lookup at 0x10000 (the very first row).
278 let r3: *NxA2LResult = nx_a2l_run(buf, p, 0x10000)
279 if r3.found != 1 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
280 if r3.line != 1 { return __syscall(93, 31, 0, 0, 0, 0, 0) }
281
282 // Lookup before any row -- not found.
283 let r4: *NxA2LResult = nx_a2l_run(buf, p, 0x100)
284 if r4.found != 0 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
285
286 return 0
287}