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}