code wiki / (root) / nx_addr2line.nx

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}