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}