code wiki / (root) / nx_dwarf_rnglists.nx

nx_dwarf_rnglists.nx source

↩ module page · 250 lines · 8058 B

1// nx_dwarf_rnglists.nx -- DWARF v5 .debug_rnglists builder. 2// 3// .debug_rnglists supersedes .debug_ranges (DWARF v3/v4). Stores 4// non-contiguous PC-range sets for DIEs whose code lives in 5// multiple discontiguous .text fragments (e.g. inlined functions, 6// hot/cold splitting, function-section partitioning). 7// 8// A DIE references its range list via DW_AT_ranges with form 9// DW_FORM_rnglistx (uleb128 index into .debug_rnglists's offset 10// table) or DW_FORM_sec_offset. 11// 12// Per-CU range list layout (DWARF v5 7.28): 13// header: 14// length u32 15// version u16 = 5 16// address_size u8 = 8 17// segment_selector u8 = 0 18// offset_entry_count u32 19// offsets[count] u32 (each = file offset within rnglists 20// of one range list relative to end of header) 21// range lists: 22// each = sequence of operations terminated by DW_RLE_end_of_list 23// 24// DW_RLE_* opcodes (subset we emit): 25// DW_RLE_end_of_list 0x00 26// DW_RLE_base_addressx 0x01 (uleb idx into addr table) 27// DW_RLE_startx_endx 0x02 (uleb startx, uleb endx) 28// DW_RLE_startx_length 0x03 (uleb startx, uleb len) 29// DW_RLE_offset_pair 0x04 (uleb start, uleb end -- relative 30// to base addr) 31// DW_RLE_base_address 0x05 (8-byte abs) 32// DW_RLE_start_end 0x06 (8-byte start, 8-byte end) 33// DW_RLE_start_length 0x07 (8-byte start, uleb length) 34// 35// Pairs with nx_dwarf_info (consumer of the offset references) + 36// nx_dwarf_aranges (similar but for PC-to-CU lookup, not per-DIE). 37 38// nx_safety_envelope: 39// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 40// sil_target: SIL1 41// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 42// verdict: NOT_YET_EVALUATED 43 44import "syscalls.nx" 45 46const NX_DWRL_VERSION: i64 = 5 47const NX_DWRL_ADDR_SIZE: i64 = 8 48const NX_DWRL_SEG_SIZE: i64 = 0 49 50// DW_RLE_* 51const NX_DW_RLE_END_OF_LIST: i64 = 0x00 52const NX_DW_RLE_BASE_ADDRESSX: i64 = 0x01 53const NX_DW_RLE_STARTX_ENDX: i64 = 0x02 54const NX_DW_RLE_STARTX_LENGTH: i64 = 0x03 55const NX_DW_RLE_OFFSET_PAIR: i64 = 0x04 56const NX_DW_RLE_BASE_ADDRESS: i64 = 0x05 57const NX_DW_RLE_START_END: i64 = 0x06 58const NX_DW_RLE_START_LENGTH: i64 = 0x07 59 60struct NxDwRl { 61 buf: *u8, 62 len: i64, 63 cap: i64, 64 cu_start: i64, 65 cu_open: i64, 66} 67 68const NX_DWRL_BYTES: i64 = 40 69 70func nx_dwrl_new(cap: i64) -> *NxDwRl { 71 let raw: *u8 = sys_mmap(NX_DWRL_BYTES) 72 let r: *NxDwRl = raw as *NxDwRl 73 r.cap = cap 74 r.buf = sys_mmap(cap) 75 r.len = 0 76 r.cu_start = 0 77 r.cu_open = 0 78 return r 79} 80 81func nx_dwrl_byte(r: *NxDwRl, b: i64) -> i64 { 82 if r.len >= r.cap { return -1 } 83 r.buf[r.len] = b & 0xFF 84 r.len = r.len + 1 85 return 0 86} 87 88func nx_dwrl_u16(r: *NxDwRl, v: i64) -> i64 { 89 nx_dwrl_byte(r, v & 0xFF) 90 return nx_dwrl_byte(r, (v >> 8) & 0xFF) 91} 92 93func nx_dwrl_u32(r: *NxDwRl, v: i64) -> i64 { 94 nx_dwrl_byte(r, v & 0xFF) 95 nx_dwrl_byte(r, (v >> 8) & 0xFF) 96 nx_dwrl_byte(r, (v >> 16) & 0xFF) 97 return nx_dwrl_byte(r, (v >> 24) & 0xFF) 98} 99 100func nx_dwrl_u64(r: *NxDwRl, v: i64) -> i64 { 101 var i: i64 = 0 102 while i < 8 { 103 nx_dwrl_byte(r, (v >> (i * 8)) & 0xFF) 104 i = i + 1 105 } 106 return 0 107} 108 109// uleb128 encoder (matches nx_uleb -- inlined to keep this module 110// self-contained, since nx_uleb returns offset-based output). 111func nx_dwrl_uleb(r: *NxDwRl, 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 117 if n == 0 { 118 nx_dwrl_byte(r, lo) 119 done = 1 120 } else { 121 nx_dwrl_byte(r, lo | 0x80) 122 } 123 } 124 return 0 125} 126 127func nx_dwrl_patch_u32(r: *NxDwRl, off: i64, v: i64) -> i64 { 128 r.buf[off] = v & 0xFF 129 r.buf[off + 1] = (v >> 8) & 0xFF 130 r.buf[off + 2] = (v >> 16) & 0xFF 131 r.buf[off + 3] = (v >> 24) & 0xFF 132 return 0 133} 134 135// Begin a CU contribution. `n_offset_entries` is the number of 136// range-list offsets the CU header will index (we emit them but 137// don't manage them -- caller fills via nx_dwrl_patch_u32 after 138// each list ends). 139func nx_dwrl_cu_begin(r: *NxDwRl, n_offset_entries: i64) -> i64 { 140 if r.cu_open == 1 { return -1 } 141 r.cu_start = r.len 142 r.cu_open = 1 143 nx_dwrl_u32(r, 0) // length placeholder 144 nx_dwrl_u16(r, NX_DWRL_VERSION) 145 nx_dwrl_byte(r, NX_DWRL_ADDR_SIZE) 146 nx_dwrl_byte(r, NX_DWRL_SEG_SIZE) 147 nx_dwrl_u32(r, n_offset_entries) 148 // Reserve space for the offset table (one u32 per entry). 149 var i: i64 = 0 150 while i < n_offset_entries { 151 nx_dwrl_u32(r, 0) 152 i = i + 1 153 } 154 return 0 155} 156 157// Patch one offset-table entry (called after each range list ends 158// so its file-relative offset is known). `idx` is 0-based. 159func nx_dwrl_patch_offset(r: *NxDwRl, idx: i64, list_off_in_cu: i64) -> i64 { 160 let off_in_buf: i64 = r.cu_start + 12 + idx * 4 161 return nx_dwrl_patch_u32(r, off_in_buf, list_off_in_cu) 162} 163 164// Returns the current file offset of where the next range list 165// will begin, RELATIVE to the end of the CU header (which is what 166// the offset table indexes). Caller passes this to patch_offset. 167func nx_dwrl_list_offset(r: *NxDwRl, n_offset_entries: i64) -> i64 { 168 let header_end: i64 = r.cu_start + 12 + n_offset_entries * 4 169 return r.len - header_end 170} 171 172// Emit a single contiguous range as start_length form. 173func nx_dwrl_start_length(r: *NxDwRl, start: i64, length: i64) -> i64 { 174 nx_dwrl_byte(r, NX_DW_RLE_START_LENGTH) 175 nx_dwrl_u64(r, start) 176 return nx_dwrl_uleb(r, length) 177} 178 179// Emit a single contiguous range as start_end form. 180func nx_dwrl_start_end(r: *NxDwRl, start: i64, end: i64) -> i64 { 181 nx_dwrl_byte(r, NX_DW_RLE_START_END) 182 nx_dwrl_u64(r, start) 183 return nx_dwrl_u64(r, end) 184} 185 186// Emit the end-of-list terminator. 187func nx_dwrl_end_list(r: *NxDwRl) -> i64 { 188 return nx_dwrl_byte(r, NX_DW_RLE_END_OF_LIST) 189} 190 191// Close the CU: patch the length field. 192func nx_dwrl_cu_end(r: *NxDwRl) -> i64 { 193 if r.cu_open == 0 { return -1 } 194 let unit_len: i64 = r.len - r.cu_start - 4 195 nx_dwrl_patch_u32(r, r.cu_start, unit_len) 196 r.cu_open = 0 197 return 0 198} 199 200// ---- self-test --------------------------------------------------- 201 202func main() -> i64 { 203 let r: *NxDwRl = nx_dwrl_new(256) 204 205 // Begin a CU with 2 range lists. 206 nx_dwrl_cu_begin(r, 2) 207 208 // First list: one range [0x10000, 0x10100). 209 let off0: i64 = nx_dwrl_list_offset(r, 2) 210 nx_dwrl_patch_offset(r, 0, off0) 211 nx_dwrl_start_length(r, 0x10000, 0x100) 212 nx_dwrl_end_list(r) 213 214 // Second list: one range, start_end form. 215 let off1: i64 = nx_dwrl_list_offset(r, 2) 216 nx_dwrl_patch_offset(r, 1, off1) 217 nx_dwrl_start_end(r, 0x20000, 0x20200) 218 nx_dwrl_end_list(r) 219 220 nx_dwrl_cu_end(r) 221 222 // Length = total - 4 (we exclude the length field itself). 223 let len: i64 = r.buf[0] 224 | (r.buf[1] << 8) 225 | (r.buf[2] << 16) 226 | (r.buf[3] << 24) 227 if len != r.len - 4 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 228 229 // version = 5 at offset 4. 230 if r.buf[4] != 5 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 231 if r.buf[5] != 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 232 233 // addr_size = 8 at offset 6. 234 if r.buf[6] != 8 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 235 // seg_size = 0 at offset 7. 236 if r.buf[7] != 0 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 237 238 // n_offset_entries = 2 at offset 8. 239 if r.buf[8] != 2 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 240 241 // First offset (offset 12) should be 0 (first list begins 242 // immediately after header end). 243 if r.buf[12] != 0 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 244 245 // First list opcode at offset 20 (after header 12 + 2*4 offsets): 246 // header_end = 12 + 8 = 20. Should be NX_DW_RLE_START_LENGTH = 7. 247 if r.buf[20] != 7 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 248 249 return 0 250}