code wiki / (root) / nx_dwarf_aranges.nx

nx_dwarf_aranges.nx source

↩ module page · 184 lines · 6410 B

1// nx_dwarf_aranges.nx -- DWARF .debug_aranges builder. 2// 3// .debug_aranges is a fast PC-to-CU (Compilation Unit) lookup table. 4// addr2line / gdb consult it FIRST when given a program counter: 5// each entry maps a [start_addr, length) range to the file offset of 6// a CU's DIE in .debug_info. Without aranges, the debugger has to 7// scan every CU's DIE forest to find the one covering the PC -- 8// O(n_CUs) per address lookup vs O(log n) here. 9// 10// Per-CU header layout (DWARF v5, section 6.1.2): 11// length (u32 / u64 -- excludes itself) 12// version (u16, =2 for DWARF v5) 13// debug_info_off (u32 / u64 -- file offset of CU header in .debug_info) 14// addr_size (u8, 8 for RV64) 15// seg_size (u8, 0 -- we don't use segments) 16// PADDING to 2*addr_size boundary 17// tuples: (start_addr, length) pairs of `addr_size` bytes each 18// final tuple: (0, 0) terminator 19// 20// We always use 32-bit DWARF (single .o files don't exceed 4 GB 21// of debug info; if they do we'll switch to 64-bit DWARF). 22// 23// Pairs with nx_dwarf_info (the consumer of debug_info_off) + 24// nx_dwarf_line / nx_dwarf_str / nx_dwarf_abbrev (cousins). 25 26// nx_safety_envelope: 27// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 28// sil_target: SIL1 29// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 30// verdict: NOT_YET_EVALUATED 31 32import "syscalls.nx" 33 34const NX_DWAR_VERSION: i64 = 2 35const NX_DWAR_ADDR_SIZE: i64 = 8 36const NX_DWAR_SEG_SIZE: i64 = 0 37 38struct NxDwAranges { 39 buf: *u8, 40 len: i64, 41 cap: i64, 42 cu_start: i64, // index where current CU header begins 43 cu_open: i64, // 1 if a CU is in progress 44} 45 46const NX_DWARANGES_BYTES: i64 = 40 47 48func nx_dwar_new(cap: i64) -> *NxDwAranges { 49 let raw: *u8 = sys_mmap(NX_DWARANGES_BYTES) 50 let a: *NxDwAranges = raw as *NxDwAranges 51 a.cap = cap 52 a.buf = sys_mmap(cap) 53 a.len = 0 54 a.cu_start = 0 55 a.cu_open = 0 56 return a 57} 58 59func nx_dwar_byte(a: *NxDwAranges, b: i64) -> i64 { 60 if a.len >= a.cap { return -1 } 61 a.buf[a.len] = b & 0xFF 62 a.len = a.len + 1 63 return 0 64} 65 66func nx_dwar_u16(a: *NxDwAranges, v: i64) -> i64 { 67 nx_dwar_byte(a, v & 0xFF) 68 return nx_dwar_byte(a, (v >> 8) & 0xFF) 69} 70 71func nx_dwar_u32(a: *NxDwAranges, v: i64) -> i64 { 72 nx_dwar_byte(a, v & 0xFF) 73 nx_dwar_byte(a, (v >> 8) & 0xFF) 74 nx_dwar_byte(a, (v >> 16) & 0xFF) 75 return nx_dwar_byte(a, (v >> 24) & 0xFF) 76} 77 78func nx_dwar_u64(a: *NxDwAranges, v: i64) -> i64 { 79 var i: i64 = 0 80 while i < 8 { 81 nx_dwar_byte(a, (v >> (i * 8)) & 0xFF) 82 i = i + 1 83 } 84 return 0 85} 86 87// Patch a u32 in-place at offset `off`. 88func nx_dwar_patch_u32(a: *NxDwAranges, off: i64, v: i64) -> i64 { 89 a.buf[off] = v & 0xFF 90 a.buf[off + 1] = (v >> 8) & 0xFF 91 a.buf[off + 2] = (v >> 16) & 0xFF 92 a.buf[off + 3] = (v >> 24) & 0xFF 93 return 0 94} 95 96// Begin a new CU's range list. Caller passes the file offset of 97// the CU header in .debug_info (the same offset they recorded in 98// the .debug_info CU header's debug_abbrev_offset). 99func nx_dwar_cu_begin(a: *NxDwAranges, debug_info_off: i64) -> i64 { 100 if a.cu_open == 1 { return -1 } 101 a.cu_start = a.len 102 a.cu_open = 1 103 nx_dwar_u32(a, 0) // length placeholder 104 nx_dwar_u16(a, NX_DWAR_VERSION) 105 nx_dwar_u32(a, debug_info_off) 106 nx_dwar_byte(a, NX_DWAR_ADDR_SIZE) 107 nx_dwar_byte(a, NX_DWAR_SEG_SIZE) 108 // Pad to 2 * addr_size = 16 bytes from the start of the unit 109 // (DWARF v5 requires the first tuple to be 2*addr_size aligned). 110 var pad_done: i64 = 0 111 while pad_done == 0 { 112 let off_in_cu: i64 = a.len - a.cu_start 113 if (off_in_cu & 15) == 0 { pad_done = 1 } 114 else { nx_dwar_byte(a, 0) } 115 } 116 return 0 117} 118 119// Add one (start_addr, length) tuple. Caller calls this once per 120// contiguous PC range owned by the CU (typically just one per CU 121// for our codegen, since our functions land in one .text block). 122func nx_dwar_add(a: *NxDwAranges, start: i64, len: i64) -> i64 { 123 if a.cu_open == 0 { return -1 } 124 nx_dwar_u64(a, start) 125 return nx_dwar_u64(a, len) 126} 127 128// Close the current CU's range list. Writes the (0, 0) terminator 129// then patches the length field at the start. 130func nx_dwar_cu_end(a: *NxDwAranges) -> i64 { 131 if a.cu_open == 0 { return -1 } 132 nx_dwar_u64(a, 0) 133 nx_dwar_u64(a, 0) 134 let end_off: i64 = a.len 135 let unit_len: i64 = end_off - a.cu_start - 4 // exclude the length field itself 136 nx_dwar_patch_u32(a, a.cu_start, unit_len) 137 a.cu_open = 0 138 return 0 139} 140 141// ---- self-test --------------------------------------------------- 142 143func main() -> i64 { 144 let a: *NxDwAranges = nx_dwar_new(256) 145 146 nx_dwar_cu_begin(a, 0x1234) 147 nx_dwar_add(a, 0x10000, 0x800) // function 1 148 nx_dwar_add(a, 0x10800, 0x400) // function 2 149 nx_dwar_cu_end(a) 150 151 // Length field at offset 0 should be (total - 4). 152 if a.len < 16 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 153 let stored_len: i64 = a.buf[0] 154 | (a.buf[1] << 8) 155 | (a.buf[2] << 16) 156 | (a.buf[3] << 24) 157 if stored_len != a.len - 4 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 158 159 // version u16 at offset 4 should be 2. 160 if a.buf[4] != 2 { return __syscall(93, 3, 0, 0, 0, 0, 0) } 161 if a.buf[5] != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 162 163 // debug_info_off u32 at offset 6 should be 0x1234. 164 if a.buf[6] != 0x34 { return __syscall(93, 5, 0, 0, 0, 0, 0) } 165 if a.buf[7] != 0x12 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 166 167 // addr_size byte at offset 10 should be 8. 168 if a.buf[10] != 8 { return __syscall(93, 7, 0, 0, 0, 0, 0) } 169 // seg_size byte at offset 11 should be 0. 170 if a.buf[11] != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) } 171 172 // First tuple should appear at offset 16 (after pad to 16 from CU start). 173 // start_addr = 0x10000 little-endian. 174 if a.buf[16] != 0x00 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 175 if a.buf[17] != 0x00 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 176 if a.buf[18] != 0x01 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 177 178 // Reject double-begin without close. 179 if nx_dwar_cu_begin(a, 0) != 0 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 180 let r: i64 = nx_dwar_cu_begin(a, 0) 181 if r != -1 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 182 183 return 0 184}