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}