nx_dwarf_str_offsets.nx source
↩ module page · 168 lines · 5399 B
1// nx_dwarf_str_offsets.nx -- DWARF v5 .debug_str_offsets builder.
2//
3// DWARF v5 introduced a level of indirection: instead of every DIE
4// attribute storing a direct file offset into .debug_str (form
5// DW_FORM_strp), per-CU strings are referenced by INDEX into a
6// per-CU offset table that lives in .debug_str_offsets (form
7// DW_FORM_strx). Benefits:
8// - Smaller DIEs in 32-bit DWARF (uleb128 index vs 4-byte offset).
9// - All string offsets concentrate in one section -- better
10// compression by gold/lld split-debug toolchains.
11// - Makes incremental linking cheaper (only patch the offset
12// table, not every DIE that referenced a moved string).
13//
14// Per-CU layout (DWARF v5 7.26):
15// length u32 (excludes itself)
16// version u16 = 5
17// padding u16 = 0
18// offsets[] u32 each (file offsets into .debug_str)
19//
20// Caller emits one CU contribution per .debug_info CU and writes
21// one u32 per string the CU references. DIE attributes use form
22// DW_FORM_strx (uleb128 index) which the consumer resolves via
23// (cu_str_offsets_base + idx*4) -> file offset into .debug_str.
24//
25// Pairs with nx_dwarf_str (string pool) + nx_dwarf_info (consumer
26// of strx forms).
27
28// nx_safety_envelope:
29// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
30// sil_target: SIL1
31// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
32// verdict: NOT_YET_EVALUATED
33
34import "syscalls.nx"
35
36const NX_DWSO_VERSION: i64 = 5
37
38struct NxDwSO {
39 buf: *u8,
40 len: i64,
41 cap: i64,
42 cu_start: i64,
43 cu_open: i64,
44}
45
46const NX_DWSO_BYTES: i64 = 40
47
48func nx_dwso_new(cap: i64) -> *NxDwSO {
49 let raw: *u8 = sys_mmap(NX_DWSO_BYTES)
50 let s: *NxDwSO = raw as *NxDwSO
51 s.cap = cap
52 s.buf = sys_mmap(cap)
53 s.len = 0
54 s.cu_start = 0
55 s.cu_open = 0
56 return s
57}
58
59func nx_dwso_byte(s: *NxDwSO, b: i64) -> i64 {
60 if s.len >= s.cap { return -1 }
61 s.buf[s.len] = b & 0xFF
62 s.len = s.len + 1
63 return 0
64}
65
66func nx_dwso_u16(s: *NxDwSO, v: i64) -> i64 {
67 nx_dwso_byte(s, v & 0xFF)
68 return nx_dwso_byte(s, (v >> 8) & 0xFF)
69}
70
71func nx_dwso_u32(s: *NxDwSO, v: i64) -> i64 {
72 nx_dwso_byte(s, v & 0xFF)
73 nx_dwso_byte(s, (v >> 8) & 0xFF)
74 nx_dwso_byte(s, (v >> 16) & 0xFF)
75 return nx_dwso_byte(s, (v >> 24) & 0xFF)
76}
77
78func nx_dwso_patch_u32(s: *NxDwSO, off: i64, v: i64) -> i64 {
79 s.buf[off] = v & 0xFF
80 s.buf[off + 1] = (v >> 8) & 0xFF
81 s.buf[off + 2] = (v >> 16) & 0xFF
82 s.buf[off + 3] = (v >> 24) & 0xFF
83 return 0
84}
85
86// Begin a CU's offset table.
87func nx_dwso_cu_begin(s: *NxDwSO) -> i64 {
88 if s.cu_open == 1 { return -1 }
89 s.cu_start = s.len
90 s.cu_open = 1
91 nx_dwso_u32(s, 0) // length placeholder
92 nx_dwso_u16(s, NX_DWSO_VERSION)
93 nx_dwso_u16(s, 0) // padding
94 return 0
95}
96
97// Append one string offset (file offset into .debug_str). Returns
98// the index of the entry just written -- caller stores this and
99// uses it as the strx in DIE attributes.
100func nx_dwso_add(s: *NxDwSO, str_offset: i64) -> i64 {
101 if s.cu_open == 0 { return -1 }
102 let header_end: i64 = s.cu_start + 8
103 let idx: i64 = (s.len - header_end) / 4
104 nx_dwso_u32(s, str_offset)
105 return idx
106}
107
108// The "base" address of this CU's offset table -- the offset where
109// the first u32 entry lives. Goes into the CU's
110// DW_AT_str_offsets_base attribute in .debug_info.
111func nx_dwso_cu_base(s: *NxDwSO) -> i64 {
112 if s.cu_open == 0 { return -1 }
113 return s.cu_start + 8
114}
115
116// Close a CU contribution. Patches the length field at the start.
117func nx_dwso_cu_end(s: *NxDwSO) -> i64 {
118 if s.cu_open == 0 { return -1 }
119 let unit_len: i64 = s.len - s.cu_start - 4
120 nx_dwso_patch_u32(s, s.cu_start, unit_len)
121 s.cu_open = 0
122 return 0
123}
124
125// ---- self-test ---------------------------------------------------
126
127func main() -> i64 {
128 let s: *NxDwSO = nx_dwso_new(128)
129
130 nx_dwso_cu_begin(s)
131 let base: i64 = nx_dwso_cu_base(s)
132 if base != 8 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
133
134 // Add three strings.
135 let i0: i64 = nx_dwso_add(s, 0x100)
136 let i1: i64 = nx_dwso_add(s, 0x108)
137 let i2: i64 = nx_dwso_add(s, 0x110)
138 if i0 != 0 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
139 if i1 != 1 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
140 if i2 != 2 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
141
142 nx_dwso_cu_end(s)
143
144 // Length field at offset 0 = total - 4.
145 let len: i64 = s.buf[0]
146 | (s.buf[1] << 8)
147 | (s.buf[2] << 16)
148 | (s.buf[3] << 24)
149 if len != s.len - 4 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
150
151 // version u16 at offset 4 = 5.
152 if s.buf[4] != 5 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
153 if s.buf[5] != 0 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
154
155 // padding u16 at offset 6 = 0.
156 if s.buf[6] != 0 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
157 if s.buf[7] != 0 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
158
159 // First entry at offset 8 = 0x100 LE.
160 if s.buf[8] != 0x00 { return __syscall(93, 10, 0, 0, 0, 0, 0) }
161 if s.buf[9] != 0x01 { return __syscall(93, 11, 0, 0, 0, 0, 0) }
162
163 // Third entry at offset 16 = 0x110 LE.
164 if s.buf[16] != 0x10 { return __syscall(93, 12, 0, 0, 0, 0, 0) }
165 if s.buf[17] != 0x01 { return __syscall(93, 13, 0, 0, 0, 0, 0) }
166
167 return 0
168}