code wiki / (root) / nx_dwarf_loc.nx

nx_dwarf_loc.nx source

↩ module page · 245 lines · 8324 B

1// nx_dwarf_loc.nx -- DWARF location expressions (DW_OP_*). 2// 3// Closes the gap in the DWARF emit pipeline: nx_dwarf_info today 4// can describe what a variable IS (DIE for the type) but cannot 5// say WHERE it lives (frame offset, register, etc.). Without 6// location expressions, gdb shows "<optimized out>" for everything. 7// 8// A DWARF location expression is a stack-machine bytecode evaluated 9// by the debugger's expression interpreter. The bytecode includes: 10// - Push/Pop constants and registers 11// - Arithmetic (plus, minus, mul, div, etc.) 12// - Dereferences (deref, deref_size) 13// - Composite-piece markers (for split-storage values) 14// 15// Common cases this module emits: 16// - Frame-relative local: DW_OP_fbreg <sleb128 offset> 17// - In a specific register: DW_OP_regN (N = 0..31) or 18// DW_OP_regx <uleb128 reg> 19// - Constant address: DW_OP_addr <8-byte vaddr> 20// - Composite (e.g. struct split across regs): DW_OP_piece 21// 22// Reference: DWARF v5 spec, section 2.5 (location expressions), 23// table 7.9 (operation encodings). 24// 25// Pairs with nx_dwarf_info -- the loc-expr bytes go inside a 26// DW_AT_location attribute (DW_FORM_exprloc). 27 28// nx_safety_envelope: 29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 30// sil_target: SIL1 31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 32// verdict: NOT_YET_EVALUATED 33 34import "syscalls.nx" 35 36// DW_OP_* opcodes we expose. 37const NX_DW_OP_addr: i64 = 0x03 38const NX_DW_OP_deref: i64 = 0x06 39const NX_DW_OP_const1u: i64 = 0x08 40const NX_DW_OP_const1s: i64 = 0x09 41const NX_DW_OP_const2u: i64 = 0x0A 42const NX_DW_OP_const4u: i64 = 0x0C 43const NX_DW_OP_const8u: i64 = 0x0E 44const NX_DW_OP_constu: i64 = 0x10 45const NX_DW_OP_consts: i64 = 0x11 46const NX_DW_OP_dup: i64 = 0x12 47const NX_DW_OP_drop: i64 = 0x13 48const NX_DW_OP_over: i64 = 0x14 49const NX_DW_OP_pick: i64 = 0x15 50const NX_DW_OP_swap: i64 = 0x16 51const NX_DW_OP_rot: i64 = 0x17 52const NX_DW_OP_xderef: i64 = 0x18 53const NX_DW_OP_abs: i64 = 0x19 54const NX_DW_OP_and: i64 = 0x1A 55const NX_DW_OP_div: i64 = 0x1B 56const NX_DW_OP_minus: i64 = 0x1C 57const NX_DW_OP_mod: i64 = 0x1D 58const NX_DW_OP_mul: i64 = 0x1E 59const NX_DW_OP_neg: i64 = 0x1F 60const NX_DW_OP_not: i64 = 0x20 61const NX_DW_OP_or: i64 = 0x21 62const NX_DW_OP_plus: i64 = 0x22 63const NX_DW_OP_plus_uconst: i64 = 0x23 64const NX_DW_OP_shl: i64 = 0x24 65const NX_DW_OP_shr: i64 = 0x25 66const NX_DW_OP_shra: i64 = 0x26 67const NX_DW_OP_xor: i64 = 0x27 68const NX_DW_OP_eq: i64 = 0x29 69const NX_DW_OP_ge: i64 = 0x2A 70const NX_DW_OP_gt: i64 = 0x2B 71const NX_DW_OP_le: i64 = 0x2C 72const NX_DW_OP_lt: i64 = 0x2D 73const NX_DW_OP_ne: i64 = 0x2E 74const NX_DW_OP_lit0: i64 = 0x30 // lit0..lit31 = 0x30..0x4F 75const NX_DW_OP_reg0: i64 = 0x50 // reg0..reg31 = 0x50..0x6F 76const NX_DW_OP_breg0: i64 = 0x70 // breg0..breg31 = 0x70..0x8F 77const NX_DW_OP_regx: i64 = 0x90 78const NX_DW_OP_fbreg: i64 = 0x91 79const NX_DW_OP_bregx: i64 = 0x92 80const NX_DW_OP_piece: i64 = 0x93 81const NX_DW_OP_deref_size: i64 = 0x94 82const NX_DW_OP_call_frame_cfa: i64 = 0x9C 83 84// Builder = simple growable byte buffer. 85struct NxDwLoc { 86 buf: *u8, 87 len: i64, 88 cap: i64, 89} 90 91const NX_DWLOC_BYTES: i64 = 24 92 93func nx_dwloc_new(cap: i64) -> *NxDwLoc { 94 let raw: *u8 = sys_mmap(NX_DWLOC_BYTES) 95 let l: *NxDwLoc = raw as *NxDwLoc 96 l.cap = cap 97 l.buf = sys_mmap(cap) 98 l.len = 0 99 return l 100} 101 102func nx_dwloc_byte(l: *NxDwLoc, b: i64) -> i64 { 103 if l.len >= l.cap { return -1 } 104 l.buf[l.len] = b & 0xFF 105 l.len = l.len + 1 106 return 0 107} 108 109// LEB128 encoders. 110 111func nx_dwloc_uleb128(l: *NxDwLoc, v: i64) -> i64 { 112 var n: i64 = v 113 var done: i64 = 0 114 while done == 0 { 115 let lo: i64 = n & 0x7F 116 n = (n >> 7) & 0x01FFFFFFFFFFFFFF // logical shift 117 if n == 0 { 118 nx_dwloc_byte(l, lo) 119 done = 1 120 } else { 121 nx_dwloc_byte(l, lo | 0x80) 122 } 123 } 124 return 0 125} 126 127func nx_dwloc_sleb128(l: *NxDwLoc, v: i64) -> i64 { 128 var n: i64 = v 129 var done: i64 = 0 130 while done == 0 { 131 let lo: i64 = n & 0x7F 132 let sign: i64 = lo & 0x40 133 let n2: i64 = n >> 7 134 let last_pos: i64 = (n == 0) 135 let last_neg: i64 = (n == (0 - 1)) 136 var stop: i64 = 0 137 if n2 == 0 { 138 if sign == 0 { stop = 1 } 139 } 140 if n2 == (0 - 1) { 141 if sign != 0 { stop = 1 } 142 } 143 if stop == 1 { 144 nx_dwloc_byte(l, lo) 145 done = 1 146 } else { 147 nx_dwloc_byte(l, lo | 0x80) 148 n = n2 149 } 150 } 151 return 0 152} 153 154// ---- high-level emitters ----------------------------------------- 155 156// Push the absolute address `vaddr` (8-byte little-endian). 157func nx_dwloc_addr(l: *NxDwLoc, vaddr: i64) -> i64 { 158 nx_dwloc_byte(l, NX_DW_OP_addr) 159 var i: i64 = 0 160 while i < 8 { 161 nx_dwloc_byte(l, (vaddr >> (i * 8)) & 0xFF) 162 i = i + 1 163 } 164 return 0 165} 166 167// Variable lives in register `r` (0..31): emit DW_OP_regN if N<=31, 168// else DW_OP_regx <uleb r>. 169func nx_dwloc_reg(l: *NxDwLoc, r: i64) -> i64 { 170 if r <= 31 { 171 return nx_dwloc_byte(l, NX_DW_OP_reg0 + r) 172 } 173 nx_dwloc_byte(l, NX_DW_OP_regx) 174 return nx_dwloc_uleb128(l, r) 175} 176 177// Variable lives at [reg + offset]: emit DW_OP_bregN <sleb off> 178// or DW_OP_bregx <uleb r> <sleb off>. 179func nx_dwloc_breg(l: *NxDwLoc, r: i64, off: i64) -> i64 { 180 if r <= 31 { 181 nx_dwloc_byte(l, NX_DW_OP_breg0 + r) 182 return nx_dwloc_sleb128(l, off) 183 } 184 nx_dwloc_byte(l, NX_DW_OP_bregx) 185 nx_dwloc_uleb128(l, r) 186 return nx_dwloc_sleb128(l, off) 187} 188 189// Frame-base + offset (most common for locals). 190func nx_dwloc_fbreg(l: *NxDwLoc, off: i64) -> i64 { 191 nx_dwloc_byte(l, NX_DW_OP_fbreg) 192 return nx_dwloc_sleb128(l, off) 193} 194 195// Composite piece marker: previous expression provides `n` bytes. 196func nx_dwloc_piece(l: *NxDwLoc, n_bytes: i64) -> i64 { 197 nx_dwloc_byte(l, NX_DW_OP_piece) 198 return nx_dwloc_uleb128(l, n_bytes) 199} 200 201// ---- self-test --------------------------------------------------- 202 203func main() -> i64 { 204 let l: *NxDwLoc = nx_dwloc_new(64) 205 206 // Encode "local at fbreg -16" 207 nx_dwloc_fbreg(l, 0 - 16) 208 if l.len != 2 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 209 if l.buf[0] != NX_DW_OP_fbreg { return __syscall(93, 2, 0, 0, 0, 0, 0) } 210 // sleb128(-16): -16 is 0x70 (0111 0000) -- top bit set in low 7 bits 211 // means sign extension would produce all-ones, so this is a 212 // single-byte sleb128 where bit6 is the sign bit. -16 = ...11110000 213 // which lo7 = 0x70. Top bit 0 means stop. 214 if l.buf[1] != 0x70 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 215 216 // Reset + encode "var in reg 10 (a0)" 217 let l2: *NxDwLoc = nx_dwloc_new(64) 218 nx_dwloc_reg(l2, 10) 219 if l2.len != 1 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 220 if l2.buf[0] != NX_DW_OP_reg0 + 10 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 221 222 // breg(reg 8 = s0, +24) 223 let l3: *NxDwLoc = nx_dwloc_new(64) 224 nx_dwloc_breg(l3, 8, 24) 225 if l3.buf[0] != NX_DW_OP_breg0 + 8 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 226 if l3.buf[1] != 24 { return __syscall(93, 7, 0, 0, 0, 0, 0) } // sleb128(24) = single byte 24 227 228 // addr(0x12345678) 229 let l4: *NxDwLoc = nx_dwloc_new(64) 230 nx_dwloc_addr(l4, 0x12345678) 231 if l4.len != 9 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 232 if l4.buf[0] != NX_DW_OP_addr { return __syscall(93, 9, 0, 0, 0, 0, 0) } 233 if l4.buf[1] != 0x78 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 234 if l4.buf[2] != 0x56 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 235 if l4.buf[3] != 0x34 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 236 if l4.buf[4] != 0x12 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 237 238 // reg32 should use bregx encoding (regx for r>31) 239 let l5: *NxDwLoc = nx_dwloc_new(64) 240 nx_dwloc_reg(l5, 50) 241 if l5.buf[0] != NX_DW_OP_regx { return __syscall(93, 14, 0, 0, 0, 0, 0) } 242 if l5.buf[1] != 50 { return __syscall(93, 15, 0, 0, 0, 0, 0) } // uleb128(50) single byte 243 244 return 0 245}