nx_dwarf_addr.nx source
↩ module page · 179 lines · 5525 B
1// nx_dwarf_addr.nx -- DWARF v5 .debug_addr builder.
2//
3// Same indirection trick as .debug_str_offsets, applied to runtime
4// code addresses. DWARF v4 stored each address inline as 8 bytes
5// in DIE attributes (DW_FORM_addr). DWARF v5 introduces
6// DW_FORM_addrx -- a uleb128 index into a per-CU address table
7// living in .debug_addr.
8//
9// Benefits:
10// - All link-time-relocated addresses concentrate in ONE section
11// (the linker only patches .debug_addr, never DIEs).
12// - DIEs become smaller and more compressible.
13// - Addresses can be deduplicated within a CU.
14//
15// Per-CU layout (DWARF v5 7.27):
16// length u32 (excludes itself)
17// version u16 = 5
18// addr_size u8 = 8
19// segment_selector u8 = 0
20// addresses[] 8 bytes each (vaddr post-link)
21//
22// Caller emits one CU contribution per .debug_info CU and writes
23// one u64 per address the CU references. DIE attributes use form
24// DW_FORM_addrx (uleb128 index).
25//
26// Pairs with nx_dwarf_info (consumer) + nx_dwarf_str_offsets
27// (sister DWARF v5 indirection).
28
29// nx_safety_envelope:
30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
31// sil_target: SIL1
32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
33// verdict: NOT_YET_EVALUATED
34
35import "syscalls.nx"
36
37const NX_DWADDR_VERSION: i64 = 5
38const NX_DWADDR_ADDR_SIZE: i64 = 8
39const NX_DWADDR_SEG_SIZE: i64 = 0
40
41struct NxDwAddr {
42 buf: *u8,
43 len: i64,
44 cap: i64,
45 cu_start: i64,
46 cu_open: i64,
47}
48
49const NX_DWADDR_BYTES: i64 = 40
50
51func nx_dwaddr_new(cap: i64) -> *NxDwAddr {
52 let raw: *u8 = sys_mmap(NX_DWADDR_BYTES)
53 let a: *NxDwAddr = raw as *NxDwAddr
54 a.cap = cap
55 a.buf = sys_mmap(cap)
56 a.len = 0
57 a.cu_start = 0
58 a.cu_open = 0
59 return a
60}
61
62func nx_dwaddr_byte(a: *NxDwAddr, b: i64) -> i64 {
63 if a.len >= a.cap { return -1 }
64 a.buf[a.len] = b & 0xFF
65 a.len = a.len + 1
66 return 0
67}
68
69func nx_dwaddr_u16(a: *NxDwAddr, v: i64) -> i64 {
70 nx_dwaddr_byte(a, v & 0xFF)
71 return nx_dwaddr_byte(a, (v >> 8) & 0xFF)
72}
73
74func nx_dwaddr_u32(a: *NxDwAddr, v: i64) -> i64 {
75 nx_dwaddr_byte(a, v & 0xFF)
76 nx_dwaddr_byte(a, (v >> 8) & 0xFF)
77 nx_dwaddr_byte(a, (v >> 16) & 0xFF)
78 return nx_dwaddr_byte(a, (v >> 24) & 0xFF)
79}
80
81func nx_dwaddr_u64(a: *NxDwAddr, v: i64) -> i64 {
82 var i: i64 = 0
83 while i < 8 {
84 nx_dwaddr_byte(a, (v >> (i * 8)) & 0xFF)
85 i = i + 1
86 }
87 return 0
88}
89
90func nx_dwaddr_patch_u32(a: *NxDwAddr, off: i64, v: i64) -> i64 {
91 a.buf[off] = v & 0xFF
92 a.buf[off + 1] = (v >> 8) & 0xFF
93 a.buf[off + 2] = (v >> 16) & 0xFF
94 a.buf[off + 3] = (v >> 24) & 0xFF
95 return 0
96}
97
98// Begin a CU's address table.
99func nx_dwaddr_cu_begin(a: *NxDwAddr) -> i64 {
100 if a.cu_open == 1 { return -1 }
101 a.cu_start = a.len
102 a.cu_open = 1
103 nx_dwaddr_u32(a, 0) // length placeholder
104 nx_dwaddr_u16(a, NX_DWADDR_VERSION)
105 nx_dwaddr_byte(a, NX_DWADDR_ADDR_SIZE)
106 nx_dwaddr_byte(a, NX_DWADDR_SEG_SIZE)
107 return 0
108}
109
110// Append one address. Returns the index of the entry just written
111// (use as the addrx value in DIE attributes).
112func nx_dwaddr_add(a: *NxDwAddr, vaddr: i64) -> i64 {
113 if a.cu_open == 0 { return -1 }
114 let header_end: i64 = a.cu_start + 8
115 let idx: i64 = (a.len - header_end) / 8
116 nx_dwaddr_u64(a, vaddr)
117 return idx
118}
119
120// CU base = file offset of first address slot. Goes in
121// DW_AT_addr_base attribute.
122func nx_dwaddr_cu_base(a: *NxDwAddr) -> i64 {
123 if a.cu_open == 0 { return -1 }
124 return a.cu_start + 8
125}
126
127// Close a CU contribution. Patches the length field at the start.
128func nx_dwaddr_cu_end(a: *NxDwAddr) -> i64 {
129 if a.cu_open == 0 { return -1 }
130 let unit_len: i64 = a.len - a.cu_start - 4
131 nx_dwaddr_patch_u32(a, a.cu_start, unit_len)
132 a.cu_open = 0
133 return 0
134}
135
136// ---- self-test ---------------------------------------------------
137
138func main() -> i64 {
139 let a: *NxDwAddr = nx_dwaddr_new(128)
140
141 nx_dwaddr_cu_begin(a)
142 let base: i64 = nx_dwaddr_cu_base(a)
143 if base != 8 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
144
145 let i0: i64 = nx_dwaddr_add(a, 0x10000)
146 let i1: i64 = nx_dwaddr_add(a, 0x10100)
147 let i2: i64 = nx_dwaddr_add(a, 0x10200)
148 if i0 != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
149 if i1 != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
150 if i2 != 2 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
151
152 nx_dwaddr_cu_end(a)
153
154 // Length field at offset 0 = total - 4.
155 let len: i64 = a.buf[0]
156 | (a.buf[1] << 8)
157 | (a.buf[2] << 16)
158 | (a.buf[3] << 24)
159 if len != a.len - 4 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
160
161 // version = 5.
162 if a.buf[4] != 5 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
163
164 // addr_size = 8 / seg_size = 0.
165 if a.buf[6] != 8 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
166 if a.buf[7] != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
167
168 // First address at offset 8 = 0x10000 LE.
169 if a.buf[8] != 0x00 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
170 if a.buf[9] != 0x00 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
171 if a.buf[10] != 0x01 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
172
173 // Third address at offset 24 = 0x10200 LE.
174 if a.buf[24] != 0x00 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
175 if a.buf[25] != 0x02 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
176 if a.buf[26] != 0x01 { return __syscall(93, 14, 0, 0, 0, 0, 0) }
177
178 return 0
179}