code wiki / (root) / nx_elf_read.nx

nx_elf_read.nx source

↩ module page · 234 lines · 8356 B

1// nx_elf_read.nx -- read & parse ELF64 binaries we produce. 2// 3// Closes the loop with elf_writer + nx_strtab + nx_symtab + nx_shtab 4// + nx_dwarf_line: this module READS what those modules WRITE. 5// Foundation for sovereign objdump, nm, readelf, and (future) 6// nx_dbg.nx debugger. 7// 8// Without nx_elf_read we depend on third-party tools to inspect our 9// output -- a sovereignty hole. With it, every byte produced is 10// also auditable in pure NishiLang. 11// 12// What we parse: 13// 14// * ELF header (64 bytes): magic, class, machine, entry, phoff, 15// shoff, ehsize, phentsize, phnum, shentsize, shnum, shstrndx. 16// * Program headers (each 56 bytes): PT_LOAD segments etc. 17// * Section headers (each 64 bytes): name, type, flags, addr, 18// offset, size, link, info, align, entsize. 19// * Symbol tables (each Elf64_Sym = 24 bytes). 20// * String tables (lookup by offset). 21// 22// NOT YET (separate commits): 23// 24// * .debug_line VM execution (decode line-program back to 25// (addr -> file/line) tuples). Land with nx_dbg.nx. 26// * .debug_info DIE walker. 27// * Relocations (.rela parsing) -- when nx_reloc.nx ships. 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 37// ---- LE unpack helpers -------------------------------------------- 38 39func nx_elf_get_u16_le(buf: *u8, off: i64) -> i64 { 40 let b0: i64 = buf[off + 0] 41 let b1: i64 = buf[off + 1] 42 return b0 | (b1 << 8) 43} 44 45func nx_elf_get_u32_le(buf: *u8, off: i64) -> i64 { 46 let b0: i64 = buf[off + 0] 47 let b1: i64 = buf[off + 1] 48 let b2: i64 = buf[off + 2] 49 let b3: i64 = buf[off + 3] 50 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24) 51} 52 53func nx_elf_get_u64_le(buf: *u8, off: i64) -> i64 { 54 let lo: i64 = nx_elf_get_u32_le(buf, off) 55 let hi: i64 = nx_elf_get_u32_le(buf, off + 4) 56 return lo | (hi << 32) 57} 58 59// ---- ELF header struct (decoded fields) --------------------------- 60 61struct NxElfHeader { 62 valid: i64, // 1 if magic + class match 63 is_64bit: i64, // 1 if EI_CLASS == 2 64 is_little: i64, // 1 if EI_DATA == 1 65 e_type: i64, // ET_EXEC=2, ET_DYN=3, ... 66 e_machine: i64, // EM_RISCV=0xF3, EM_X86_64=0x3E, ... 67 e_entry: i64, // entry point vaddr 68 e_phoff: i64, // program-header table offset 69 e_shoff: i64, // section-header table offset 70 e_ehsize: i64, // header size (typically 64) 71 e_phentsize: i64, 72 e_phnum: i64, 73 e_shentsize: i64, 74 e_shnum: i64, 75 e_shstrndx: i64, // section index of .shstrtab 76} 77 78const NX_ELF_HEADER_BYTES: i64 = 112 79 80func nx_elf_parse_header(buf: *u8, len: i64) -> *NxElfHeader { 81 let raw: *u8 = sys_mmap(NX_ELF_HEADER_BYTES) 82 let h: *NxElfHeader = raw as *NxElfHeader 83 h.valid = 0 84 85 if len < 64 { return h } 86 if buf[0] != 0x7F { return h } 87 if buf[1] != 0x45 { return h } // 'E' 88 if buf[2] != 0x4C { return h } // 'L' 89 if buf[3] != 0x46 { return h } // 'F' 90 91 if buf[4] == 2 { h.is_64bit = 1 } 92 if buf[5] == 1 { h.is_little = 1 } 93 94 h.e_type = nx_elf_get_u16_le(buf, 16) 95 h.e_machine = nx_elf_get_u16_le(buf, 18) 96 h.e_entry = nx_elf_get_u64_le(buf, 24) 97 h.e_phoff = nx_elf_get_u64_le(buf, 32) 98 h.e_shoff = nx_elf_get_u64_le(buf, 40) 99 h.e_ehsize = nx_elf_get_u16_le(buf, 52) 100 h.e_phentsize = nx_elf_get_u16_le(buf, 54) 101 h.e_phnum = nx_elf_get_u16_le(buf, 56) 102 h.e_shentsize = nx_elf_get_u16_le(buf, 58) 103 h.e_shnum = nx_elf_get_u16_le(buf, 60) 104 h.e_shstrndx = nx_elf_get_u16_le(buf, 62) 105 106 h.valid = 1 107 return h 108} 109 110// ---- section header decoded --------------------------------------- 111 112struct NxElfShdr { 113 sh_name: i64, 114 sh_type: i64, 115 sh_flags: i64, 116 sh_addr: i64, 117 sh_offset: i64, 118 sh_size: i64, 119 sh_link: i64, 120 sh_info: i64, 121 sh_addralign: i64, 122 sh_entsize: i64, 123} 124 125const NX_ELF_SHDR_BYTES: i64 = 80 126 127// Read section header at index `idx` from the section header table 128// rooted at `buf + h.e_shoff`. 129func nx_elf_read_shdr(buf: *u8, h: *NxElfHeader, idx: i64) -> *NxElfShdr { 130 let raw: *u8 = sys_mmap(NX_ELF_SHDR_BYTES) 131 let s: *NxElfShdr = raw as *NxElfShdr 132 let off: i64 = h.e_shoff + idx * h.e_shentsize 133 s.sh_name = nx_elf_get_u32_le(buf, off + 0) 134 s.sh_type = nx_elf_get_u32_le(buf, off + 4) 135 s.sh_flags = nx_elf_get_u64_le(buf, off + 8) 136 s.sh_addr = nx_elf_get_u64_le(buf, off + 16) 137 s.sh_offset = nx_elf_get_u64_le(buf, off + 24) 138 s.sh_size = nx_elf_get_u64_le(buf, off + 32) 139 s.sh_link = nx_elf_get_u32_le(buf, off + 40) 140 s.sh_info = nx_elf_get_u32_le(buf, off + 44) 141 s.sh_addralign = nx_elf_get_u64_le(buf, off + 48) 142 s.sh_entsize = nx_elf_get_u64_le(buf, off + 56) 143 return s 144} 145 146// ---- string-table lookup ----------------------------------------- 147// 148// Returns a *u8 into `buf` at the requested offset (no copy). 149// Caller can scan for NUL to compute length. 150func nx_elf_strtab_at(buf: *u8, strtab_off: i64, name_off: i64) -> *u8 { 151 let base: i64 = buf as i64 152 return (base + strtab_off + name_off) as *u8 153} 154 155// ---- symbol table decoded ---------------------------------------- 156 157struct NxElfSym { 158 st_name: i64, 159 st_info: i64, 160 st_other: i64, 161 st_shndx: i64, 162 st_value: i64, 163 st_size: i64, 164} 165 166const NX_ELF_SYM_BYTES: i64 = 56 167 168// Read Elf64_Sym at index `idx` from a symtab section. 169func nx_elf_read_sym(buf: *u8, sym_section: *NxElfShdr, idx: i64) -> *NxElfSym { 170 let raw: *u8 = sys_mmap(NX_ELF_SYM_BYTES) 171 let s: *NxElfSym = raw as *NxElfSym 172 let off: i64 = sym_section.sh_offset + idx * 24 // Elf64_Sym = 24 173 s.st_name = nx_elf_get_u32_le(buf, off + 0) 174 s.st_info = buf[off + 4] 175 s.st_other = buf[off + 5] 176 s.st_shndx = nx_elf_get_u16_le(buf, off + 6) 177 s.st_value = nx_elf_get_u64_le(buf, off + 8) 178 s.st_size = nx_elf_get_u64_le(buf, off + 16) 179 return s 180} 181 182func nx_elf_sym_bind(s: *NxElfSym) -> i64 { return (s.st_info >> 4) & 0xF } 183func nx_elf_sym_type(s: *NxElfSym) -> i64 { return s.st_info & 0xF } 184 185func nx_elf_sym_count(sym_section: *NxElfShdr) -> i64 { 186 if sym_section.sh_entsize == 0 { return 0 } 187 return sym_section.sh_size / sym_section.sh_entsize 188} 189 190// ---- self-test (round-trip with elf_writer-shape bytes) ----------- 191 192func main() -> i64 { 193 // Build a minimal ELF in memory matching elf_writer's layout: 194 // 64-byte header + 56-byte program header + 12 bytes of code. 195 let elf: *u8 = sys_mmap(256) 196 // ELF header 197 elf[0] = 0x7F 198 elf[1] = 0x45 199 elf[2] = 0x4C 200 elf[3] = 0x46 201 elf[4] = 2 // 64-bit 202 elf[5] = 1 // little-endian 203 elf[6] = 1 // version 204 elf[16] = 2 // ET_EXEC 205 elf[18] = 0xF3 // EM_RISCV 206 // Skip e_entry / phoff / etc. to keep the test minimal -- we 207 // verify the magic/class/machine fields decode correctly. 208 209 let h: *NxElfHeader = nx_elf_parse_header(elf, 64) 210 if h.valid != 1 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 211 if h.is_64bit != 1 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 212 if h.is_little != 1 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 213 if h.e_type != 2 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 214 if h.e_machine != 0xF3 { return __syscall(93, 14, 0, 0, 0, 0, 0) } 215 216 // Reject corrupted magic. 217 elf[0] = 0 218 let h_bad: *NxElfHeader = nx_elf_parse_header(elf, 64) 219 if h_bad.valid != 0 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 220 221 // Reject too-short input. 222 let h_short: *NxElfHeader = nx_elf_parse_header(elf, 16) 223 if h_short.valid != 0 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 224 225 // u16 / u32 / u64 LE decoders. 226 let bytes: *u8 = sys_mmap(16) 227 bytes[0] = 0x78; bytes[1] = 0x56; bytes[2] = 0x34; bytes[3] = 0x12 228 bytes[4] = 0xEF; bytes[5] = 0xBE; bytes[6] = 0xAD; bytes[7] = 0xDE 229 if nx_elf_get_u16_le(bytes, 0) != 0x5678 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 230 if nx_elf_get_u32_le(bytes, 0) != 0x12345678 { return __syscall(93, 31, 0, 0, 0, 0, 0) } 231 if nx_elf_get_u64_le(bytes, 0) != 0xDEADBEEF12345678 { return __syscall(93, 32, 0, 0, 0, 0, 0) } 232 233 return 0 234}