code wiki / _hdl_build / rv64im_min_symtab.nx
rv64im_min_symtab.nx source
↩ module page · 217 lines · 8954 B
1// rv64im_min_symtab.nx -- ELF64 symbol table parser.
2//
3// Walks an ELF64 byte buffer (the same one the loader consumed) +
4// extracts the symbol table. Builds an in-memory list of FUNC
5// symbols with (address, size, name) so the hot-function reporter
6// (rv64im_min_hot_report.nx) can attribute cycles to source-level
7// function names.
8//
9// This + per-PC cycle attribution + the GEMM/AES benchmarks =
10// silicon-feedback loop M3 closure per ZERO_TO_ADVANCED.md.
11//
12// Why we parse this ourselves instead of `nm` / `objdump`:
13// - sovereignty: external binutils tools are third-party deps per
14// [[feedback-open-source-licensed-is-still-third-party]]
15// - the ELF format spec is open; the IMPLEMENTATION is what's
16// licensed -- parsing it ourselves is bytes-up sovereign
17//
18// Status: SEED. 2026-05-26. Parses .symtab + .strtab; filters to
19// FUNC symbols; returns array of NxElfFuncSymbol.
20
21import "nx_syscalls.nx"
22import "nishi_hdl_primitives.nx"
23import "rv64im_min_elf_loader.nx"
24
25// ===== Section header types (per ELF spec gabi41.pdf ยง4.3) =================================================
26const NX_ELF_SHT_NULL: i64 = 0
27const NX_ELF_SHT_PROGBITS: i64 = 1
28const NX_ELF_SHT_SYMTAB: i64 = 2
29const NX_ELF_SHT_STRTAB: i64 = 3
30
31// ===== Section header field offsets (Shdr64, 64 bytes) =================================================
32const NX_ELF_SH_OFF_NAME: i64 = 0 // u32: offset into .shstrtab
33const NX_ELF_SH_OFF_TYPE: i64 = 4 // u32
34const NX_ELF_SH_OFF_FLAGS: i64 = 8
35const NX_ELF_SH_OFF_ADDR: i64 = 16
36const NX_ELF_SH_OFF_OFFSET: i64 = 24
37const NX_ELF_SH_OFF_SIZE: i64 = 32
38const NX_ELF_SH_OFF_LINK: i64 = 40 // u32: for SYMTAB, index of STRTAB
39const NX_ELF_SH_OFF_INFO: i64 = 44
40const NX_ELF_SH_OFF_ADDRALIGN: i64 = 48
41const NX_ELF_SH_OFF_ENTSIZE: i64 = 56
42const NX_ELF_SHDR_SIZE: i64 = 64
43
44// ===== Symbol entry field offsets (Sym64, 24 bytes) =================================================
45const NX_ELF_SYM_OFF_NAME: i64 = 0 // u32: offset into .strtab
46const NX_ELF_SYM_OFF_INFO: i64 = 4 // u8: binding | type (low 4 bits)
47const NX_ELF_SYM_OFF_OTHER: i64 = 5
48const NX_ELF_SYM_OFF_SHNDX: i64 = 6 // u16
49const NX_ELF_SYM_OFF_VALUE: i64 = 8 // u64: function address
50const NX_ELF_SYM_OFF_SIZE: i64 = 16 // u64: function byte count
51const NX_ELF_SYM_SIZE: i64 = 24
52
53// st_info type bits (low 4 bits of info byte)
54const NX_ELF_STT_NOTYPE: i64 = 0
55const NX_ELF_STT_OBJECT: i64 = 1
56const NX_ELF_STT_FUNC: i64 = 2
57const NX_ELF_STT_SECTION: i64 = 3
58const NX_ELF_STT_FILE: i64 = 4
59
60// ===== Verdicts =================================================
61const NX_SYMTAB_OK: i64 = 0
62const NX_SYMTAB_NO_SYMTAB_SECTION: i64 = 1
63const NX_SYMTAB_NO_STRTAB: i64 = 2
64const NX_SYMTAB_BAD_LINK: i64 = 3
65const NX_SYMTAB_BAD_ENTSIZE: i64 = 4
66const NX_SYMTAB_TRUNCATED: i64 = 5
67const NX_SYMTAB_OVERFLOW: i64 = 6
68
69// ===== Output entry =================================================
70//
71// One FUNC symbol. Caller allocates an array; nx_elf_load_symtab
72// fills it with up to cap entries.
73
74struct NxElfFuncSymbol {
75 addr: i64 // st_value (function entry PC)
76 size: i64 // st_size (function byte count; covers [addr, addr+size))
77 name_off: i64 // offset into strtab_buf
78 name_ptr: *u8 // dereferenced for convenience (= strtab_buf + name_off)
79}
80
81// ===== Parser state =================================================
82
83struct NxSymtab {
84 funcs: *NxElfFuncSymbol
85 n_funcs: i64
86 cap_funcs: i64
87 strtab: *u8 // pointer into the ELF buffer
88 strtab_sz: i64
89 valid: i64
90}
91
92// ===== Section lookup =================================================
93//
94// Walks the section-header table looking for a section of the given
95// type. Returns the shdr file-offset on hit, 0 - VERDICT on miss.
96
97func nx_elf_find_section_by_type(buf: *u8, buf_size: i64, target_type: i64) -> i64 {
98 let shoff: i64 = nx_elf_read_u64(buf, NX_ELF_OFF_E_SHOFF)
99 let shentsize: i64 = nx_elf_read_u16(buf, NX_ELF_OFF_E_SHENTSIZE)
100 let shnum: i64 = nx_elf_read_u16(buf, NX_ELF_OFF_E_SHNUM)
101 if shentsize != NX_ELF_SHDR_SIZE { return 0 - NX_SYMTAB_BAD_ENTSIZE }
102 if shoff + (shentsize * shnum) > buf_size { return 0 - NX_SYMTAB_TRUNCATED }
103 var i: i64 = 0
104 while i < shnum {
105 let sh_off: i64 = shoff + (i * shentsize)
106 let t: i64 = nx_elf_read_u32(buf, sh_off + NX_ELF_SH_OFF_TYPE)
107 if t == target_type { return sh_off }
108 i = i + 1
109 }
110 return 0 - NX_SYMTAB_NO_SYMTAB_SECTION
111}
112
113// Look up a section by its index in the section header table.
114func nx_elf_section_at(buf: *u8, idx: i64) -> i64 {
115 let shoff: i64 = nx_elf_read_u64(buf, NX_ELF_OFF_E_SHOFF)
116 let shentsize: i64 = nx_elf_read_u16(buf, NX_ELF_OFF_E_SHENTSIZE)
117 return shoff + (idx * shentsize)
118}
119
120// ===== Top-level load =================================================
121//
122// Parses the ELF, locates .symtab + its linked .strtab, walks every
123// Sym64 entry, filters to FUNC type with non-zero size, populates the
124// caller-supplied NxSymtab.funcs[] array.
125
126func nx_elf_load_symtab(buf: *u8, buf_size: i64,
127 out_funcs: *NxElfFuncSymbol, cap_funcs: i64,
128 symtab: *NxSymtab) -> i64 {
129 if (buf as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
130 if (out_funcs as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
131 if (symtab as i64) == 0 { return 0 - NX_HDL_BAD_KIND }
132
133 // Validate this is a usable ELF (reuse the loader's check).
134 let vv: i64 = nx_elf_validate_ident(buf, buf_size)
135 if vv != NX_ELF_OK { return vv }
136
137 // Find .symtab.
138 let symtab_sh: i64 = nx_elf_find_section_by_type(buf, buf_size, NX_ELF_SHT_SYMTAB)
139 if symtab_sh < 0 { return symtab_sh }
140
141 let symtab_off: i64 = nx_elf_read_u64(buf, symtab_sh + NX_ELF_SH_OFF_OFFSET)
142 let symtab_size: i64 = nx_elf_read_u64(buf, symtab_sh + NX_ELF_SH_OFF_SIZE)
143 let symtab_entsz: i64 = nx_elf_read_u64(buf, symtab_sh + NX_ELF_SH_OFF_ENTSIZE)
144 let strtab_link: i64 = nx_elf_read_u32(buf, symtab_sh + NX_ELF_SH_OFF_LINK)
145
146 if symtab_entsz != NX_ELF_SYM_SIZE { return 0 - NX_SYMTAB_BAD_ENTSIZE }
147 if symtab_off + symtab_size > buf_size { return 0 - NX_SYMTAB_TRUNCATED }
148
149 // Find the linked .strtab via the LINK field.
150 if strtab_link == 0 { return 0 - NX_SYMTAB_BAD_LINK }
151 let strtab_sh: i64 = nx_elf_section_at(buf, strtab_link)
152 let strtab_off: i64 = nx_elf_read_u64(buf, strtab_sh + NX_ELF_SH_OFF_OFFSET)
153 let strtab_size: i64 = nx_elf_read_u64(buf, strtab_sh + NX_ELF_SH_OFF_SIZE)
154 if strtab_off + strtab_size > buf_size { return 0 - NX_SYMTAB_TRUNCATED }
155
156 let strtab_ptr: *u8 = (buf as i64 + strtab_off) as *u8
157
158 // Walk symbol entries; filter to FUNC with size > 0.
159 let n_entries: i64 = symtab_size / symtab_entsz
160 var i: i64 = 0
161 var filled: i64 = 0
162 while i < n_entries {
163 let s_off: i64 = symtab_off + (i * symtab_entsz)
164 let info: i64 = nx_elf_read_u8(buf, s_off + NX_ELF_SYM_OFF_INFO)
165 let stype: i64 = info & 0xf
166 if stype == NX_ELF_STT_FUNC {
167 let value: i64 = nx_elf_read_u64(buf, s_off + NX_ELF_SYM_OFF_VALUE)
168 let size: i64 = nx_elf_read_u64(buf, s_off + NX_ELF_SYM_OFF_SIZE)
169 if size > 0 {
170 if filled >= cap_funcs { return 0 - NX_SYMTAB_OVERFLOW }
171 let name_o: i64 = nx_elf_read_u32(buf, s_off + NX_ELF_SYM_OFF_NAME)
172 out_funcs[filled].addr = value
173 out_funcs[filled].size = size
174 out_funcs[filled].name_off = name_o
175 out_funcs[filled].name_ptr = (strtab_ptr as i64 + name_o) as *u8
176 filled = filled + 1
177 }
178 }
179 i = i + 1
180 }
181
182 symtab.funcs = out_funcs
183 symtab.n_funcs = filled
184 symtab.cap_funcs = cap_funcs
185 symtab.strtab = strtab_ptr
186 symtab.strtab_sz = strtab_size
187 symtab.valid = 1
188 return NX_SYMTAB_OK
189}
190
191// ===== PC -> function lookup =================================================
192//
193// Given a PC and a populated NxSymtab, returns the index of the
194// FUNC symbol whose [addr, addr+size) contains pc; -1 if none.
195
196func nx_symtab_func_for_pc(symtab: *NxSymtab, pc: i64) -> i64 {
197 if symtab.valid != 1 { return 0 - 1 }
198 var i: i64 = 0
199 while i < symtab.n_funcs {
200 let lo: i64 = symtab.funcs[i].addr
201 let hi: i64 = lo + symtab.funcs[i].size
202 if pc >= lo { if pc < hi { return i } }
203 i = i + 1
204 }
205 return 0 - 1
206}
207
208// ===== Convenience: byte length of a NUL-terminated name =================================================
209//
210// strtab entries are NUL-terminated. Returns the byte count
211// (excluding the NUL) for printing or comparison.
212
213func nx_symtab_name_len(name_ptr: *u8) -> i64 {
214 var n: i64 = 0
215 while name_ptr[n] != (0 as u8) { n = n + 1 }
216 return n
217}