code wiki / (root) / nx_dwarf_info.nx

nx_dwarf_info.nx source

↩ module page · 304 lines · 9878 B

1// nx_dwarf_info.nx -- DWARF .debug_info section builder. 2// 3// The DIE (Debugging Information Entry) forest. Each DIE is: 4// 5// abbrev_code uleb128 (refers to nx_dwarf_abbrev entry) 6// attribute values raw bytes shaped per the abbrev's form list 7// 8// A compilation unit (CU) starts with a header: 9// length u32 (or extended u64 for >4GB CUs) 10// version u16 (= 5 for DWARF v5) 11// unit_type u8 (= DW_UT_compile = 0x01) 12// addr_size u8 (= 8 for RV64) 13// abbrev_offset u32 (offset into .debug_abbrev) 14// ...DIE bytes... 15// 16// The first DIE after the header is the CU's root DIE (usually 17// DW_TAG_compile_unit). Children DIEs follow when the abbrev 18// declared has_children=1, terminated by a NULL DIE (single 0 byte). 19// 20// Pairs with: 21// nx_dwarf_abbrev -- supplies the codes referenced from each DIE 22// nx_dwarf_str -- supplies offsets for DW_FORM_strp values 23// nx_dwarf_line -- pointed-to by DW_AT_stmt_list 24// 25// What this module ships (v0.0.1): 26// * CU-header writer (begin/finalize that fix up the length field) 27// * DIE-emit helpers: emit_die_open(code) / emit_die_close() 28// * Per-FORM attribute writers (addr / data4 / data8 / strp / ref4 29// / flag_present / sec_offset / udata / sdata) 30// 31// Doesn't yet validate that the (form, value) pairs match the 32// abbrev's declared schema -- caller responsibility for now. 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" 41const NX_MAGIC_1024: i64 = 1024 42 43const NX_DW_UT_compile: i64 = 0x01 44 45// ---- builder struct ---------------------------------------------- 46 47struct NxDwInfo { 48 buf: *u8, 49 cap: i64, 50 used: i64, 51 cu_start: i64, // offset where current CU's length field lives 52 cu_open: i64, // 1 if a CU is mid-emission 53 addr_size: i64, // 8 for RV64 54} 55 56const NX_DW_INFO_BYTES: i64 = 48 57 58func nx_dwinfo_new(cap: i64) -> *NxDwInfo { 59 let raw: *u8 = sys_mmap(NX_DW_INFO_BYTES) 60 let i: *NxDwInfo = raw as *NxDwInfo 61 i.buf = sys_mmap(cap) 62 i.cap = cap 63 i.used = 0 64 i.cu_start = 0 65 i.cu_open = 0 66 i.addr_size = 8 67 return i 68} 69 70// ---- low-level byte writers -------------------------------------- 71 72func nx_dwinfo_byte(d: *NxDwInfo, v: i64) -> i64 { 73 if d.used >= d.cap { return -1 } 74 d.buf[d.used] = v & 0xFF 75 d.used = d.used + 1 76 return 0 77} 78 79func nx_dwinfo_u16(d: *NxDwInfo, v: i64) -> i64 { 80 nx_dwinfo_byte(d, v & 0xFF) 81 nx_dwinfo_byte(d, (v >> 8) & 0xFF) 82 return 0 83} 84 85func nx_dwinfo_u32(d: *NxDwInfo, v: i64) -> i64 { 86 nx_dwinfo_byte(d, v & 0xFF) 87 nx_dwinfo_byte(d, (v >> 8) & 0xFF) 88 nx_dwinfo_byte(d, (v >> 16) & 0xFF) 89 nx_dwinfo_byte(d, (v >> 24) & 0xFF) 90 return 0 91} 92 93func nx_dwinfo_u64(d: *NxDwInfo, v: i64) -> i64 { 94 nx_dwinfo_byte(d, v & 0xFF) 95 nx_dwinfo_byte(d, (v >> 8) & 0xFF) 96 nx_dwinfo_byte(d, (v >> 16) & 0xFF) 97 nx_dwinfo_byte(d, (v >> 24) & 0xFF) 98 nx_dwinfo_byte(d, (v >> 32) & 0xFF) 99 nx_dwinfo_byte(d, (v >> 40) & 0xFF) 100 nx_dwinfo_byte(d, (v >> 48) & 0xFF) 101 nx_dwinfo_byte(d, (v >> 56) & 0xFF) 102 return 0 103} 104 105func nx_dwinfo_uleb(d: *NxDwInfo, v: i64) -> i64 { 106 var x: i64 = v 107 var go: i64 = 1 108 while go == 1 { 109 var b: i64 = x & 0x7F 110 x = x >> 7 111 if x != 0 { b = b | 0x80 } 112 if nx_dwinfo_byte(d, b) < 0 { return -1 } 113 if x == 0 { go = 0 } 114 } 115 return 0 116} 117 118func nx_dwinfo_sleb(d: *NxDwInfo, v: i64) -> i64 { 119 var x: i64 = v 120 var go: i64 = 1 121 while go == 1 { 122 var b: i64 = x & 0x7F 123 x = x >> 7 124 let sign_bit: i64 = b & 0x40 125 let done: i64 = ((x == 0) & (sign_bit == 0)) | ((x == -1) & (sign_bit != 0)) 126 if done == 0 { b = b | 0x80 } 127 if nx_dwinfo_byte(d, b) < 0 { return -1 } 128 if done != 0 { go = 0 } 129 } 130 return 0 131} 132 133// ---- CU header --------------------------------------------------- 134 135// Begin a compilation unit. Writes the header with a placeholder 136// length field; nx_dwinfo_cu_finalize patches it once the body is 137// emitted. abbrev_offset = byte offset into .debug_abbrev where 138// this CU's abbreviation table starts. 139func nx_dwinfo_cu_begin(d: *NxDwInfo, abbrev_offset: i64) -> i64 { 140 d.cu_start = d.used 141 nx_dwinfo_u32(d, 0) // length placeholder 142 nx_dwinfo_u16(d, 5) // DWARF v5 143 nx_dwinfo_byte(d, NX_DW_UT_compile) 144 nx_dwinfo_byte(d, d.addr_size) 145 nx_dwinfo_u32(d, abbrev_offset) 146 d.cu_open = 1 147 return 0 148} 149 150// Finalize the CU: patch the length field to total_bytes_after_length. 151// Length encoding excludes the 4-byte length field itself. 152func nx_dwinfo_cu_finalize(d: *NxDwInfo) -> i64 { 153 if d.cu_open != 1 { return -1 } 154 let length: i64 = d.used - (d.cu_start + 4) 155 // Patch in-place at d.cu_start (LE u32). 156 d.buf[d.cu_start + 0] = length & 0xFF 157 d.buf[d.cu_start + 1] = (length >> 8) & 0xFF 158 d.buf[d.cu_start + 2] = (length >> 16) & 0xFF 159 d.buf[d.cu_start + 3] = (length >> 24) & 0xFF 160 d.cu_open = 0 161 return 0 162} 163 164// ---- DIE emit ---------------------------------------------------- 165 166// Emit a DIE header (just its abbrev code). Caller follows with 167// the attribute values per the abbrev's form list. 168func nx_dwinfo_die_open(d: *NxDwInfo, abbrev_code: i64) -> i64 { 169 return nx_dwinfo_uleb(d, abbrev_code) 170} 171 172// Emit the NULL-DIE terminator (used to close a children list). 173func nx_dwinfo_die_null(d: *NxDwInfo) -> i64 { 174 return nx_dwinfo_byte(d, 0) 175} 176 177// ---- per-FORM attribute writers ---------------------------------- 178 179func nx_dwinfo_attr_addr(d: *NxDwInfo, addr: i64) -> i64 { 180 return nx_dwinfo_u64(d, addr) // d.addr_size == 8 always (RV64) 181} 182 183func nx_dwinfo_attr_data1(d: *NxDwInfo, v: i64) -> i64 { 184 return nx_dwinfo_byte(d, v) 185} 186 187func nx_dwinfo_attr_data2(d: *NxDwInfo, v: i64) -> i64 { 188 return nx_dwinfo_u16(d, v) 189} 190 191func nx_dwinfo_attr_data4(d: *NxDwInfo, v: i64) -> i64 { 192 return nx_dwinfo_u32(d, v) 193} 194 195func nx_dwinfo_attr_data8(d: *NxDwInfo, v: i64) -> i64 { 196 return nx_dwinfo_u64(d, v) 197} 198 199// strp = 4-byte offset into .debug_str (DWARF32; DWARF64 would be 8). 200func nx_dwinfo_attr_strp(d: *NxDwInfo, str_off: i64) -> i64 { 201 return nx_dwinfo_u32(d, str_off) 202} 203 204// ref4 = 4-byte offset relative to the CU start (.debug_info-local). 205func nx_dwinfo_attr_ref4(d: *NxDwInfo, cu_local_off: i64) -> i64 { 206 return nx_dwinfo_u32(d, cu_local_off) 207} 208 209// sec_offset = 4-byte offset into another section (.debug_line, ...) 210func nx_dwinfo_attr_sec_offset(d: *NxDwInfo, off: i64) -> i64 { 211 return nx_dwinfo_u32(d, off) 212} 213 214// flag (one byte: 0x00 false / 0x01 true). 215func nx_dwinfo_attr_flag(d: *NxDwInfo, b: i64) -> i64 { 216 return nx_dwinfo_byte(d, b & 0x1) 217} 218 219// flag_present consumes ZERO bytes; the form's mere presence in 220// the abbrev declares the attribute true. 221// (No emitter -- caller declares the attribute in the abbrev only.) 222 223func nx_dwinfo_attr_udata(d: *NxDwInfo, v: i64) -> i64 { 224 return nx_dwinfo_uleb(d, v) 225} 226 227func nx_dwinfo_attr_sdata(d: *NxDwInfo, v: i64) -> i64 { 228 return nx_dwinfo_sleb(d, v) 229} 230 231// String inline (DW_FORM_string): NUL-terminated bytes inline. 232func nx_dwinfo_attr_string(d: *NxDwInfo, src: *u8) -> i64 { 233 var i: i64 = 0 234 while src[i] != 0 { 235 nx_dwinfo_byte(d, src[i]) 236 i = i + 1 237 } 238 return nx_dwinfo_byte(d, 0) 239} 240 241// ---- query -------------------------------------------------------- 242 243func nx_dwinfo_size(d: *NxDwInfo) -> i64 { return d.used } 244func nx_dwinfo_bytes(d: *NxDwInfo) -> *u8 { return d.buf } 245 246// ---- self-test --------------------------------------------------- 247 248func main() -> i64 { 249 let d: *NxDwInfo = nx_dwinfo_new(NX_MAGIC_1024) 250 251 // Begin a CU at abbrev_offset=0. 252 nx_dwinfo_cu_begin(d, 0) 253 if d.used != 12 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 254 // Header bytes: length placeholder (4), version 5 (2), unit_type 1 (1), 255 // addr_size 8 (1), abbrev_off 0 (4) = 12. 256 257 // Open the root DIE (abbrev code 1 = compile_unit per nx_dwarf_abbrev 258 // self-test). Then attribute values: 259 // strp (name) -> 0x1234 260 // strp (producer) -> 0x5678 261 // addr (low_pc) -> 0x10000 262 // data8 (high_pc) -> 256 263 // sec_offset (stmt_list) -> 0 264 nx_dwinfo_die_open(d, 1) 265 nx_dwinfo_attr_strp(d, 0x1234) 266 nx_dwinfo_attr_strp(d, 0x5678) 267 nx_dwinfo_attr_addr(d, 0x10000) 268 nx_dwinfo_attr_data8(d, 256) 269 nx_dwinfo_attr_sec_offset(d, 0) 270 271 // Open a child subprogram DIE (abbrev code 2): 272 // strp (name) -> 0xABCD 273 // addr (low_pc) -> 0x10010 274 // data8 (high_pc) -> 64 275 // ref4 (type) -> 0x20 (offset within this CU) 276 // (no flag emitted -- flag_present is zero-byte) 277 nx_dwinfo_die_open(d, 2) 278 nx_dwinfo_attr_strp(d, 0xABCD) 279 nx_dwinfo_attr_addr(d, 0x10010) 280 nx_dwinfo_attr_data8(d, 64) 281 nx_dwinfo_attr_ref4(d, 0x20) 282 283 // Close the children list with a NULL DIE. 284 nx_dwinfo_die_null(d) 285 286 // Finalize CU header length. 287 nx_dwinfo_cu_finalize(d) 288 289 // First 4 bytes = length field, which should equal d.used - 4. 290 let length_in_buf: i64 = d.buf[0] | (d.buf[1] << 8) | (d.buf[2] << 16) | (d.buf[3] << 24) 291 if length_in_buf != d.used - 4 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 292 293 // Bytes 4-5 = version 5. 294 if d.buf[4] != 5 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 295 if d.buf[5] != 0 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 296 297 // Byte 6 = unit_type = DW_UT_compile = 1. 298 if d.buf[6] != 1 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 299 300 // Byte 7 = addr_size = 8. 301 if d.buf[7] != 8 { return __syscall(93, 41, 0, 0, 0, 0, 0) } 302 303 return 0 304}