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}