nx_dwarf_loclists.nx source
↩ module page · 247 lines · 7363 B
1// nx_dwarf_loclists.nx -- DWARF v5 .debug_loclists builder.
2//
3// Companion to nx_dwarf_loc (single location expression at a fixed
4// PC). loclists describes a variable's location as a SEQUENCE of
5// (PC range, location expression) pairs -- needed when an
6// optimizer moves the variable across registers / spills it to
7// the frame at different points in the function.
8//
9// Without loclists, gdb shows the variable's last-known location
10// at every breakpoint, which is wrong inside the optimized
11// function body. With loclists, gdb shows '<optimized out>'
12// outside any covered range and the right register/offset inside.
13//
14// Per-CU layout (DWARF v5 7.7.3) -- mirrors nx_dwarf_rnglists:
15// length u32
16// version u16 = 5
17// addr_size u8 = 8
18// segment_selector u8 = 0
19// offset_entry_count u32
20// offsets[count] u32
21// loc lists sequence of DW_LLE_* opcodes
22//
23// DW_LLE_* opcodes (subset we emit):
24// end_of_list 0x00
25// base_addressx 0x01
26// startx_endx 0x02
27// startx_length 0x03
28// offset_pair 0x04
29// default_location 0x05
30// base_address 0x06
31// start_end 0x07
32// start_length 0x08
33//
34// Each entry that includes a "location description" is followed
35// by a uleb128 length and that many bytes of DW_OP_* expression
36// (built via nx_dwarf_loc).
37//
38// Pairs with nx_dwarf_loc (location-expression bytes) +
39// nx_dwarf_info (DIE attributes referencing loclists offsets) +
40// nx_dwarf_rnglists (sister section).
41
42// nx_safety_envelope:
43// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
44// sil_target: SIL1
45// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
46// verdict: NOT_YET_EVALUATED
47
48import "syscalls.nx"
49
50const NX_DWLL_VERSION: i64 = 5
51const NX_DWLL_ADDR_SIZE: i64 = 8
52const NX_DWLL_SEG_SIZE: i64 = 0
53
54// DW_LLE_*
55const NX_DW_LLE_END_OF_LIST: i64 = 0x00
56const NX_DW_LLE_BASE_ADDRESSX: i64 = 0x01
57const NX_DW_LLE_STARTX_ENDX: i64 = 0x02
58const NX_DW_LLE_STARTX_LENGTH: i64 = 0x03
59const NX_DW_LLE_OFFSET_PAIR: i64 = 0x04
60const NX_DW_LLE_DEFAULT_LOC: i64 = 0x05
61const NX_DW_LLE_BASE_ADDRESS: i64 = 0x06
62const NX_DW_LLE_START_END: i64 = 0x07
63const NX_DW_LLE_START_LENGTH: i64 = 0x08
64
65struct NxDwLl {
66 buf: *u8,
67 len: i64,
68 cap: i64,
69 cu_start: i64,
70 cu_open: i64,
71}
72
73const NX_DWLL_BYTES: i64 = 40
74
75func nx_dwll_new(cap: i64) -> *NxDwLl {
76 let raw: *u8 = sys_mmap(NX_DWLL_BYTES)
77 let r: *NxDwLl = raw as *NxDwLl
78 r.cap = cap
79 r.buf = sys_mmap(cap)
80 r.len = 0
81 r.cu_start = 0
82 r.cu_open = 0
83 return r
84}
85
86func nx_dwll_byte(r: *NxDwLl, b: i64) -> i64 {
87 if r.len >= r.cap { return -1 }
88 r.buf[r.len] = b & 0xFF
89 r.len = r.len + 1
90 return 0
91}
92
93func nx_dwll_u16(r: *NxDwLl, v: i64) -> i64 {
94 nx_dwll_byte(r, v & 0xFF)
95 return nx_dwll_byte(r, (v >> 8) & 0xFF)
96}
97
98func nx_dwll_u32(r: *NxDwLl, v: i64) -> i64 {
99 nx_dwll_byte(r, v & 0xFF)
100 nx_dwll_byte(r, (v >> 8) & 0xFF)
101 nx_dwll_byte(r, (v >> 16) & 0xFF)
102 return nx_dwll_byte(r, (v >> 24) & 0xFF)
103}
104
105func nx_dwll_u64(r: *NxDwLl, v: i64) -> i64 {
106 var i: i64 = 0
107 while i < 8 {
108 nx_dwll_byte(r, (v >> (i * 8)) & 0xFF)
109 i = i + 1
110 }
111 return 0
112}
113
114func nx_dwll_uleb(r: *NxDwLl, v: i64) -> i64 {
115 var n: i64 = v
116 var done: i64 = 0
117 while done == 0 {
118 let lo: i64 = n & 0x7F
119 n = (n >> 7) & 0x01FFFFFFFFFFFFFF
120 if n == 0 {
121 nx_dwll_byte(r, lo)
122 done = 1
123 } else {
124 nx_dwll_byte(r, lo | 0x80)
125 }
126 }
127 return 0
128}
129
130func nx_dwll_patch_u32(r: *NxDwLl, off: i64, v: i64) -> i64 {
131 r.buf[off] = v & 0xFF
132 r.buf[off + 1] = (v >> 8) & 0xFF
133 r.buf[off + 2] = (v >> 16) & 0xFF
134 r.buf[off + 3] = (v >> 24) & 0xFF
135 return 0
136}
137
138// Begin a CU contribution. `n_offset_entries` reserves space for
139// the offset table (one u32 per loc-list).
140func nx_dwll_cu_begin(r: *NxDwLl, n_offset_entries: i64) -> i64 {
141 if r.cu_open == 1 { return -1 }
142 r.cu_start = r.len
143 r.cu_open = 1
144 nx_dwll_u32(r, 0) // length placeholder
145 nx_dwll_u16(r, NX_DWLL_VERSION)
146 nx_dwll_byte(r, NX_DWLL_ADDR_SIZE)
147 nx_dwll_byte(r, NX_DWLL_SEG_SIZE)
148 nx_dwll_u32(r, n_offset_entries)
149 var i: i64 = 0
150 while i < n_offset_entries {
151 nx_dwll_u32(r, 0)
152 i = i + 1
153 }
154 return 0
155}
156
157func nx_dwll_patch_offset(r: *NxDwLl, idx: i64, list_off_in_cu: i64) -> i64 {
158 let off_in_buf: i64 = r.cu_start + 12 + idx * 4
159 return nx_dwll_patch_u32(r, off_in_buf, list_off_in_cu)
160}
161
162func nx_dwll_list_offset(r: *NxDwLl, n_offset_entries: i64) -> i64 {
163 let header_end: i64 = r.cu_start + 12 + n_offset_entries * 4
164 return r.len - header_end
165}
166
167// Emit a (start, length) entry followed by a location description.
168// `expr_buf` is `expr_len` bytes from nx_dwarf_loc.
169func nx_dwll_start_length(r: *NxDwLl, start: i64, length: i64, expr_buf: *u8, expr_len: i64) -> i64 {
170 nx_dwll_byte(r, NX_DW_LLE_START_LENGTH)
171 nx_dwll_u64(r, start)
172 nx_dwll_uleb(r, length)
173 nx_dwll_uleb(r, expr_len)
174 var i: i64 = 0
175 while i < expr_len {
176 nx_dwll_byte(r, expr_buf[i])
177 i = i + 1
178 }
179 return 0
180}
181
182// Default location (always-applies fallback).
183func nx_dwll_default_location(r: *NxDwLl, expr_buf: *u8, expr_len: i64) -> i64 {
184 nx_dwll_byte(r, NX_DW_LLE_DEFAULT_LOC)
185 nx_dwll_uleb(r, expr_len)
186 var i: i64 = 0
187 while i < expr_len {
188 nx_dwll_byte(r, expr_buf[i])
189 i = i + 1
190 }
191 return 0
192}
193
194func nx_dwll_end_list(r: *NxDwLl) -> i64 {
195 return nx_dwll_byte(r, NX_DW_LLE_END_OF_LIST)
196}
197
198func nx_dwll_cu_end(r: *NxDwLl) -> i64 {
199 if r.cu_open == 0 { return -1 }
200 let unit_len: i64 = r.len - r.cu_start - 4
201 nx_dwll_patch_u32(r, r.cu_start, unit_len)
202 r.cu_open = 0
203 return 0
204}
205
206// ---- self-test ---------------------------------------------------
207
208func main() -> i64 {
209 let r: *NxDwLl = nx_dwll_new(256)
210
211 // Synthesise a tiny DW_OP_reg10 expression (one byte: 0x5A).
212 let expr: *u8 = sys_mmap(8)
213 expr[0] = 0x5A // DW_OP_reg10
214
215 nx_dwll_cu_begin(r, 1)
216 let off0: i64 = nx_dwll_list_offset(r, 1)
217 nx_dwll_patch_offset(r, 0, off0)
218 nx_dwll_start_length(r, 0x10000, 0x80, expr, 1)
219 nx_dwll_end_list(r)
220 nx_dwll_cu_end(r)
221
222 // Length field at offset 0 = total - 4.
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.
230 if r.buf[4] != 5 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
231
232 // addr_size = 8 / seg_size = 0.
233 if r.buf[6] != 8 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
234 if r.buf[7] != 0 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
235
236 // n_offset_entries = 1.
237 if r.buf[8] != 1 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
238
239 // First offset entry (offset 12) should be 0.
240 if r.buf[12] != 0 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
241
242 // First list opcode at offset 16 (header_end = 12 + 4 = 16):
243 // should be NX_DW_LLE_START_LENGTH = 0x08.
244 if r.buf[16] != 0x08 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
245
246 return 0
247}