nx_dwarf_pubnames.nx source
↩ module page · 168 lines · 5685 B
1// nx_dwarf_pubnames.nx -- DWARF .debug_pubnames + .debug_pubtypes builder.
2//
3// Companion to .debug_aranges (PC-to-CU lookup): .debug_pubnames
4// gives gdb/lldb a NAME-to-DIE-offset lookup, so when the user
5// types `b foo`, the debugger doesn't scan every CU's DIE forest
6// to find the function `foo` -- it consults this index first.
7//
8// Per-CU layout (DWARF v4 6.1.1; v5 deprecated this in favor of
9// .debug_names but most consumers still understand pubnames):
10//
11// length u32 (excludes itself)
12// version u16 = 2
13// debug_info_off u32 (offset of CU header in .debug_info)
14// debug_info_len u32 (length of CU contribution to .debug_info)
15// tuples:
16// die_offset u32 (offset within the CU)
17// name NUL-terminated string
18// terminator u32 (=0)
19//
20// .debug_pubtypes uses the same layout but for type names instead.
21// The two builders share this code -- caller decides which section
22// the bytes go into.
23
24// nx_safety_envelope:
25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
26// sil_target: SIL1
27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
28// verdict: NOT_YET_EVALUATED
29
30import "syscalls.nx"
31
32const NX_DWPUB_VERSION: i64 = 2
33
34struct NxDwPub {
35 buf: *u8,
36 len: i64,
37 cap: i64,
38 cu_start: i64,
39 cu_open: i64,
40}
41
42const NX_DWPUB_BYTES: i64 = 40
43
44func nx_dwpub_new(cap: i64) -> *NxDwPub {
45 let raw: *u8 = sys_mmap(NX_DWPUB_BYTES)
46 let p: *NxDwPub = raw as *NxDwPub
47 p.cap = cap
48 p.buf = sys_mmap(cap)
49 p.len = 0
50 p.cu_start = 0
51 p.cu_open = 0
52 return p
53}
54
55func nx_dwpub_byte(p: *NxDwPub, b: i64) -> i64 {
56 if p.len >= p.cap { return -1 }
57 p.buf[p.len] = b & 0xFF
58 p.len = p.len + 1
59 return 0
60}
61
62func nx_dwpub_u16(p: *NxDwPub, v: i64) -> i64 {
63 nx_dwpub_byte(p, v & 0xFF)
64 return nx_dwpub_byte(p, (v >> 8) & 0xFF)
65}
66
67func nx_dwpub_u32(p: *NxDwPub, v: i64) -> i64 {
68 nx_dwpub_byte(p, v & 0xFF)
69 nx_dwpub_byte(p, (v >> 8) & 0xFF)
70 nx_dwpub_byte(p, (v >> 16) & 0xFF)
71 return nx_dwpub_byte(p, (v >> 24) & 0xFF)
72}
73
74func nx_dwpub_patch_u32(p: *NxDwPub, off: i64, v: i64) -> i64 {
75 p.buf[off] = v & 0xFF
76 p.buf[off + 1] = (v >> 8) & 0xFF
77 p.buf[off + 2] = (v >> 16) & 0xFF
78 p.buf[off + 3] = (v >> 24) & 0xFF
79 return 0
80}
81
82// Begin a new per-CU pubnames/pubtypes section. Caller passes the
83// CU's offset into .debug_info plus the length of that CU's
84// contribution.
85func nx_dwpub_cu_begin(p: *NxDwPub, info_off: i64, info_len: i64) -> i64 {
86 if p.cu_open == 1 { return -1 }
87 p.cu_start = p.len
88 p.cu_open = 1
89 nx_dwpub_u32(p, 0) // length placeholder
90 nx_dwpub_u16(p, NX_DWPUB_VERSION)
91 nx_dwpub_u32(p, info_off)
92 nx_dwpub_u32(p, info_len)
93 return 0
94}
95
96// Add one (DIE offset, name) tuple. `name` must be NUL-terminated.
97func nx_dwpub_add(p: *NxDwPub, die_off: i64, name: *u8) -> i64 {
98 if p.cu_open == 0 { return -1 }
99 nx_dwpub_u32(p, die_off)
100 var i: i64 = 0
101 while name[i] != 0 {
102 nx_dwpub_byte(p, name[i])
103 i = i + 1
104 }
105 return nx_dwpub_byte(p, 0)
106}
107
108// Close the current CU's range list. Writes the 0 terminator
109// then patches the length field at the start of the unit.
110func nx_dwpub_cu_end(p: *NxDwPub) -> i64 {
111 if p.cu_open == 0 { return -1 }
112 nx_dwpub_u32(p, 0)
113 let unit_len: i64 = p.len - p.cu_start - 4
114 nx_dwpub_patch_u32(p, p.cu_start, unit_len)
115 p.cu_open = 0
116 return 0
117}
118
119// ---- self-test ---------------------------------------------------
120
121func main() -> i64 {
122 let p: *NxDwPub = nx_dwpub_new(256)
123
124 let main_name: *u8 = sys_mmap(8)
125 main_name[0] = 0x6D; main_name[1] = 0x61; main_name[2] = 0x69
126 main_name[3] = 0x6E; main_name[4] = 0
127 let foo_name: *u8 = sys_mmap(8)
128 foo_name[0] = 0x66; foo_name[1] = 0x6F; foo_name[2] = 0x6F; foo_name[3] = 0
129
130 nx_dwpub_cu_begin(p, 0x100, 0x500)
131 nx_dwpub_add(p, 0x40, main_name)
132 nx_dwpub_add(p, 0x80, foo_name)
133 nx_dwpub_cu_end(p)
134
135 // Length field at offset 0 = total - 4.
136 let stored_len: i64 = p.buf[0]
137 | (p.buf[1] << 8)
138 | (p.buf[2] << 16)
139 | (p.buf[3] << 24)
140 if stored_len != p.len - 4 { return __syscall(93, 1, 0, 0, 0, 0, 0) }
141
142 // version u16 at offset 4 should be 2.
143 if p.buf[4] != 2 { return __syscall(93, 2, 0, 0, 0, 0, 0) }
144 if p.buf[5] != 0 { return __syscall(93, 3, 0, 0, 0, 0, 0) }
145
146 // info_off u32 at offset 6 should be 0x100.
147 if p.buf[6] != 0x00 { return __syscall(93, 4, 0, 0, 0, 0, 0) }
148 if p.buf[7] != 0x01 { return __syscall(93, 5, 0, 0, 0, 0, 0) }
149
150 // info_len u32 at offset 10 should be 0x500.
151 if p.buf[10] != 0x00 { return __syscall(93, 6, 0, 0, 0, 0, 0) }
152 if p.buf[11] != 0x05 { return __syscall(93, 7, 0, 0, 0, 0, 0) }
153
154 // First tuple at offset 14: die_off u32 = 0x40 then "main\0"
155 if p.buf[14] != 0x40 { return __syscall(93, 8, 0, 0, 0, 0, 0) }
156 if p.buf[15] != 0 { return __syscall(93, 9, 0, 0, 0, 0, 0) }
157 if p.buf[18] != 0x6D { return __syscall(93, 10, 0, 0, 0, 0, 0) } // 'm'
158 if p.buf[19] != 0x61 { return __syscall(93, 11, 0, 0, 0, 0, 0) } // 'a'
159 if p.buf[20] != 0x69 { return __syscall(93, 12, 0, 0, 0, 0, 0) } // 'i'
160 if p.buf[21] != 0x6E { return __syscall(93, 13, 0, 0, 0, 0, 0) } // 'n'
161 if p.buf[22] != 0 { return __syscall(93, 14, 0, 0, 0, 0, 0) } // NUL
162
163 // Total terminator should be the last 4 bytes = 0.
164 if p.buf[p.len - 1] != 0 { return __syscall(93, 15, 0, 0, 0, 0, 0) }
165 if p.buf[p.len - 4] != 0 { return __syscall(93, 16, 0, 0, 0, 0, 0) }
166
167 return 0
168}