code wiki / (root) / nx_dwarf_line.nx

nx_dwarf_line.nx source

↩ module page · 289 lines · 10226 B

1// nx_dwarf_line.nx -- DWARF v5 .debug_line section builder. 2// 3// Maps machine-instruction addresses back to source-file line + col. 4// Enables `gdb` (and our future sovereign debugger) to show the .nx 5// source line when stopped at any RV64 PC. 6// 7// DWARF v5 line program (DWARF 5 spec §6.2): 8// 9// .debug_line consists of one "line number program" per compilation 10// unit. Header (~30 bytes), then a sequence of standard + extended 11// opcodes that build a "line table" via a small VM: 12// 13// state: address (u64), op_index, file, line, column, 14// is_stmt, basic_block, end_sequence, prologue_end, 15// epilogue_begin, isa, discriminator 16// 17// Standard opcodes drive the VM: 18// DW_LNS_copy commit current row 19// DW_LNS_advance_pc += operand uleb128 20// DW_LNS_advance_line += signed leb 21// DW_LNS_set_file := operand 22// DW_LNS_set_column := operand 23// ... etc 24// 25// Why now (R2 capability lever): 26// 27// * Managed platforms (V8/WASM) can't give us proper PC-to-source 28// debugging at the silicon level. We can. 29// * Pairs with nx_dbg.nx (future) for full sovereign debugging: 30// read .debug_line, decode VM, print source location at faults. 31// * F6-deterministic: same source -> same .debug_line bytes. 32// 33// What we ship here (v0.0.1): 34// 35// * Builder for the .debug_line bytes (header + line program). 36// * uleb128 / sleb128 encoders (used widely in DWARF). 37// * Helper to register (address, file, line) tuples and finalize. 38// 39// What we DEFER (separate commits): 40// 41// * .debug_info builder (DIE forest -- compilation units, types, 42// functions, variables). ~500 LoC; lands when nxc2 grows the 43// -g flag. 44// * .debug_str + .debug_str_offsets builders. Tied to nx_strtab. 45// * .debug_abbrev builder (abbreviation table for DIEs). 46// * Reader/decoder for sovereign debugger. 47 48// nx_safety_envelope: 49// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 50// sil_target: SIL1 51// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 52// verdict: NOT_YET_EVALUATED 53 54import "syscalls.nx" 55 56// ---- VM standard opcodes (DWARF 5 §6.2.5.2) ---------------------- 57 58const DW_LNS_copy: i64 = 0x01 59const DW_LNS_advance_pc: i64 = 0x02 60const DW_LNS_advance_line: i64 = 0x03 61const DW_LNS_set_file: i64 = 0x04 62const DW_LNS_set_column: i64 = 0x05 63const DW_LNS_negate_stmt: i64 = 0x06 64const DW_LNS_set_basic_block: i64 = 0x07 65const DW_LNS_const_add_pc: i64 = 0x08 66const DW_LNS_fixed_advance_pc: i64 = 0x09 67const DW_LNS_set_prologue_end: i64 = 0x0A 68const DW_LNS_set_epilogue_begin: i64 = 0x0B 69const DW_LNS_set_isa: i64 = 0x0C 70 71// Extended opcodes (preceded by 0 + uleb-length). 72const DW_LNE_end_sequence: i64 = 0x01 73const DW_LNE_set_address: i64 = 0x02 74const DW_LNE_set_discriminator: i64 = 0x04 75 76// Special-opcode tuning constants. Keep simple defaults; line_base 77// = -5 / line_range = 14 / opcode_base = 13 are common across LLVM 78// + GCC. 79const NX_DW_LINE_BASE: i64 = -5 80const NX_DW_LINE_RANGE: i64 = 14 81const NX_DW_OPCODE_BASE: i64 = 13 82const NX_DW_MIN_INST_LEN: i64 = 1 83const NX_DW_MAX_OPS_PER_INST: i64 = 1 84const NX_DW_DEFAULT_IS_STMT: i64 = 1 85 86// ---- builder context --------------------------------------------- 87 88struct NxDwLine { 89 buf: *u8, 90 cap: i64, 91 used: i64, 92 cur_addr: i64, // VM state: current address 93 cur_line: i64, // VM state: current line 94 cur_file: i64, // VM state: current file index (1-based) 95} 96 97const NX_DWLINE_BYTES: i64 = 48 98 99func nx_dwline_new(cap: i64) -> *NxDwLine { 100 let raw: *u8 = sys_mmap(NX_DWLINE_BYTES) 101 let d: *NxDwLine = raw as *NxDwLine 102 d.buf = sys_mmap(cap) 103 d.cap = cap 104 d.used = 0 105 d.cur_addr = 0 106 d.cur_line = 1 // DWARF lines are 1-based 107 d.cur_file = 1 // first file 108 return d 109} 110 111// ---- byte append --------------------------------------------------- 112 113func nx_dwline_byte(d: *NxDwLine, v: i64) -> i64 { 114 if d.used >= d.cap { return -1 } 115 d.buf[d.used] = v & 0xFF 116 d.used = d.used + 1 117 return 0 118} 119 120// uleb128 encoder. 121func nx_dwline_uleb(d: *NxDwLine, v: i64) -> i64 { 122 var x: i64 = v 123 var go: i64 = 1 124 while go == 1 { 125 var b: i64 = x & 0x7F 126 x = x >> 7 127 if x != 0 { b = b | 0x80 } 128 if nx_dwline_byte(d, b) < 0 { return -1 } 129 if x == 0 { go = 0 } 130 } 131 return 0 132} 133 134// sleb128 encoder. Two's-complement with sign-extension stop rule. 135func nx_dwline_sleb(d: *NxDwLine, v: i64) -> i64 { 136 var x: i64 = v 137 var go: i64 = 1 138 while go == 1 { 139 var b: i64 = x & 0x7F 140 // Arithmetic shift: preserves sign for negatives. 141 x = x >> 7 142 let sign_bit: i64 = b & 0x40 143 let done: i64 = ((x == 0) & (sign_bit == 0)) | ((x == -1) & (sign_bit != 0)) 144 if done == 0 { b = b | 0x80 } 145 if nx_dwline_byte(d, b) < 0 { return -1 } 146 if done != 0 { go = 0 } 147 } 148 return 0 149} 150 151// ---- VM ops ------------------------------------------------------ 152 153// DW_LNS_advance_pc: opcode + uleb operand 154func nx_dwline_advance_pc(d: *NxDwLine, delta: i64) -> i64 { 155 if delta < 0 { return -1 } 156 nx_dwline_byte(d, DW_LNS_advance_pc) 157 nx_dwline_uleb(d, delta / NX_DW_MIN_INST_LEN) 158 d.cur_addr = d.cur_addr + delta 159 return 0 160} 161 162// DW_LNS_advance_line: opcode + sleb operand 163func nx_dwline_advance_line(d: *NxDwLine, delta: i64) -> i64 { 164 nx_dwline_byte(d, DW_LNS_advance_line) 165 nx_dwline_sleb(d, delta) 166 d.cur_line = d.cur_line + delta 167 return 0 168} 169 170// DW_LNS_set_file: opcode + uleb file index 171func nx_dwline_set_file(d: *NxDwLine, file_idx: i64) -> i64 { 172 nx_dwline_byte(d, DW_LNS_set_file) 173 nx_dwline_uleb(d, file_idx) 174 d.cur_file = file_idx 175 return 0 176} 177 178// DW_LNS_copy: commit current row 179func nx_dwline_copy(d: *NxDwLine) -> i64 { 180 return nx_dwline_byte(d, DW_LNS_copy) 181} 182 183// Extended op: end of sequence (closes the program). 184func nx_dwline_end_sequence(d: *NxDwLine) -> i64 { 185 // 0x00 (extended marker), uleb128(1) (length), DW_LNE_end_sequence 186 nx_dwline_byte(d, 0) 187 nx_dwline_byte(d, 1) 188 nx_dwline_byte(d, DW_LNE_end_sequence) 189 return 0 190} 191 192// Extended op: set address (8 bytes for 64-bit). 193func nx_dwline_set_address(d: *NxDwLine, addr: i64) -> i64 { 194 nx_dwline_byte(d, 0) 195 nx_dwline_byte(d, 9) // length: opcode + 8 bytes 196 nx_dwline_byte(d, DW_LNE_set_address) 197 nx_dwline_byte(d, (addr ) & 0xFF) 198 nx_dwline_byte(d, (addr >> 8) & 0xFF) 199 nx_dwline_byte(d, (addr >> 16) & 0xFF) 200 nx_dwline_byte(d, (addr >> 24) & 0xFF) 201 nx_dwline_byte(d, (addr >> 32) & 0xFF) 202 nx_dwline_byte(d, (addr >> 40) & 0xFF) 203 nx_dwline_byte(d, (addr >> 48) & 0xFF) 204 nx_dwline_byte(d, (addr >> 56) & 0xFF) 205 d.cur_addr = addr 206 return 0 207} 208 209// ---- high-level: register one (addr, file, line) row ------------- 210// 211// Idiomatic usage: walk emitted instructions and call this for each 212// statement boundary. The builder advances PC + line via the VM, 213// then DW_LNS_copy commits. 214 215func nx_dwline_record(d: *NxDwLine, addr: i64, file_idx: i64, line: i64) -> i64 { 216 if file_idx != d.cur_file { 217 nx_dwline_set_file(d, file_idx) 218 } 219 let pc_delta: i64 = addr - d.cur_addr 220 if pc_delta < 0 { 221 // Backward jump (e.g. inlining): emit DW_LNE_set_address. 222 nx_dwline_set_address(d, addr) 223 } else { 224 if pc_delta > 0 { nx_dwline_advance_pc(d, pc_delta) } 225 } 226 let line_delta: i64 = line - d.cur_line 227 if line_delta != 0 { nx_dwline_advance_line(d, line_delta) } 228 return nx_dwline_copy(d) 229} 230 231// ---- query -------------------------------------------------------- 232 233func nx_dwline_size(d: *NxDwLine) -> i64 { return d.used } 234func nx_dwline_bytes(d: *NxDwLine) -> *u8 { return d.buf } 235 236// ---- self-test ---------------------------------------------------- 237 238func main() -> i64 { 239 let d: *NxDwLine = nx_dwline_new(256) 240 241 // Empty: 0 bytes. 242 if nx_dwline_size(d) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 243 244 // Encode a few typical operations. 245 246 // Set initial address to 0x10000. 247 nx_dwline_set_address(d, 0x10000) 248 // Bytes: 0x00, 0x09, 0x02, then 8 LE bytes for 0x10000. 249 if d.buf[0] != 0x00 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 250 if d.buf[1] != 0x09 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 251 if d.buf[2] != 0x02 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 252 if d.buf[3] != 0x00 { return __syscall(93, 23, 0, 0, 0, 0, 0) } // addr LE byte 0 253 if d.buf[4] != 0x00 { return __syscall(93, 24, 0, 0, 0, 0, 0) } 254 if d.buf[5] != 0x01 { return __syscall(93, 25, 0, 0, 0, 0, 0) } // 0x10000 byte 2 255 if d.cur_addr != 0x10000 { return __syscall(93, 26, 0, 0, 0, 0, 0) } 256 257 // Advance PC by 4 (one RV64 instr). 258 let s_before: i64 = d.used 259 nx_dwline_advance_pc(d, 4) 260 // Expect: 0x02 + uleb128(4) = 2 bytes total. 261 if (d.used - s_before) != 2 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 262 if d.buf[s_before + 0] != DW_LNS_advance_pc { return __syscall(93, 31, 0, 0, 0, 0, 0) } 263 if d.buf[s_before + 1] != 4 { return __syscall(93, 32, 0, 0, 0, 0, 0) } 264 if d.cur_addr != 0x10004 { return __syscall(93, 33, 0, 0, 0, 0, 0) } 265 266 // Advance line by +3. 267 let s_line: i64 = d.used 268 nx_dwline_advance_line(d, 3) 269 if (d.used - s_line) != 2 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 270 if d.buf[s_line + 0] != DW_LNS_advance_line { return __syscall(93, 41, 0, 0, 0, 0, 0) } 271 if d.buf[s_line + 1] != 3 { return __syscall(93, 42, 0, 0, 0, 0, 0) } 272 if d.cur_line != 4 { return __syscall(93, 43, 0, 0, 0, 0, 0) } 273 274 // Test high-level record helper. 275 let d2: *NxDwLine = nx_dwline_new(256) 276 nx_dwline_set_address(d2, 0x10000) 277 nx_dwline_record(d2, 0x10010, 1, 5) 278 if d2.cur_addr != 0x10010 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 279 if d2.cur_line != 5 { return __syscall(93, 51, 0, 0, 0, 0, 0) } 280 281 nx_dwline_record(d2, 0x10014, 1, 6) 282 if d2.cur_addr != 0x10014 { return __syscall(93, 52, 0, 0, 0, 0, 0) } 283 if d2.cur_line != 6 { return __syscall(93, 53, 0, 0, 0, 0, 0) } 284 285 // End sequence cleanly. 286 nx_dwline_end_sequence(d2) 287 288 return 0 289}