code wiki / (root) / nx_loader.nx

nx_loader.nx source

↩ module page · 209 lines · 7327 B

1// nx_loader.nx -- ELF segment loader for nx_rv64_sim. 2// 3// Closes the loop: nx_elf_read parses, nx_loader copies PT_LOAD 4// segments into a memory buffer, nx_rv64_sim executes from there. 5// 6// With these three modules we can run an RV64 ELF entirely in 7// NishiLang -- no qemu, no docker, no Linux process boundary. 8// This is THE off-C bootstrap-verification capability. 9// 10// What we do: 11// 12// 1. Parse the ELF header. 13// 2. For each program header: 14// * If type = PT_LOAD, copy `p_filesz` bytes from 15// ELF[p_offset..p_offset+p_filesz) to mem[p_vaddr...] 16// * Then zero `p_memsz - p_filesz` bytes (BSS region). 17// 3. Return the entry point so the simulator can set PC. 18// 19// Memory model: we treat `mem_base[0..mem_size)` as a flat region 20// where addresses are file-vaddrs. If p_vaddr is 0x10000, then 21// mem_base[0x10000] is where the segment lands. The caller 22// allocates a mem buffer big enough to hold all loaded segments; 23// today that means `>= max_vaddr + max_segment_size`. 24// 25// PT_LOAD is the only program-header type we handle; PT_PHDR / 26// PT_INTERP / etc. are silently skipped (irrelevant for static 27// RV64 ELFs from elf_writer). 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" 36import "nx_elf_read.nx" 37 38// ---- program header (decoded) ----------------------------------- 39 40struct NxElfPhdr { 41 p_type: i64, 42 p_flags: i64, 43 p_offset: i64, 44 p_vaddr: i64, 45 p_paddr: i64, 46 p_filesz: i64, 47 p_memsz: i64, 48 p_align: i64, 49} 50 51const NX_ELF_PHDR_BYTES: i64 = 64 52 53const NX_PT_NULL: i64 = 0 54const NX_PT_LOAD: i64 = 1 55const NX_PT_DYNAMIC: i64 = 2 56const NX_PT_INTERP: i64 = 3 57const NX_PT_PHDR: i64 = 6 58 59// ---- decode a single Elf64_Phdr at file offset `off` -------------- 60 61func nx_loader_read_phdr(buf: *u8, off: i64) -> *NxElfPhdr { 62 let raw: *u8 = sys_mmap(NX_ELF_PHDR_BYTES) 63 let p: *NxElfPhdr = raw as *NxElfPhdr 64 p.p_type = nx_elf_get_u32_le(buf, off + 0) 65 p.p_flags = nx_elf_get_u32_le(buf, off + 4) 66 p.p_offset = nx_elf_get_u64_le(buf, off + 8) 67 p.p_vaddr = nx_elf_get_u64_le(buf, off + 16) 68 p.p_paddr = nx_elf_get_u64_le(buf, off + 24) 69 p.p_filesz = nx_elf_get_u64_le(buf, off + 32) 70 p.p_memsz = nx_elf_get_u64_le(buf, off + 40) 71 p.p_align = nx_elf_get_u64_le(buf, off + 48) 72 return p 73} 74 75// ---- byte-copy helper -------------------------------------------- 76 77func nx_loader_memcpy(dst: *u8, src: *u8, n: i64) -> i64 { 78 var i: i64 = 0 79 while i < n { 80 dst[i] = src[i] 81 i = i + 1 82 } 83 return 0 84} 85 86func nx_loader_memzero(dst: *u8, n: i64) -> i64 { 87 var i: i64 = 0 88 while i < n { 89 dst[i] = 0 90 i = i + 1 91 } 92 return 0 93} 94 95// ---- top-level loader -------------------------------------------- 96// 97// Loads `elf_buf` (length `elf_len`) into `mem_base` (size 98// `mem_size`). Returns the entry-point vaddr, or -1 on error. 99// Bounds-checks every segment copy to avoid overrunning mem. 100 101func nx_loader_load(elf_buf: *u8, elf_len: i64, 102 mem_base: *u8, mem_size: i64) -> i64 { 103 let h: *NxElfHeader = nx_elf_parse_header(elf_buf, elf_len) 104 if h.valid != 1 { return -1 } 105 if h.is_64bit != 1 { return -1 } 106 if h.is_little != 1 { return -1 } 107 if h.e_machine != 0xF3 { return -1 } // EM_RISCV 108 109 var i: i64 = 0 110 while i < h.e_phnum { 111 let phoff: i64 = h.e_phoff + i * h.e_phentsize 112 let p: *NxElfPhdr = nx_loader_read_phdr(elf_buf, phoff) 113 if p.p_type == NX_PT_LOAD { 114 // Bounds: p_vaddr + p_memsz must fit inside mem. 115 if p.p_vaddr + p.p_memsz > mem_size { return -1 } 116 // Bounds: p_offset + p_filesz must fit inside elf_buf. 117 if p.p_offset + p.p_filesz > elf_len { return -1 } 118 // Copy file bytes. 119 let dst_addr: i64 = (mem_base as i64) + p.p_vaddr 120 let src_addr: i64 = (elf_buf as i64) + p.p_offset 121 let dst: *u8 = dst_addr as *u8 122 let src: *u8 = src_addr as *u8 123 nx_loader_memcpy(dst, src, p.p_filesz) 124 // Zero the BSS tail (memsz > filesz). 125 if p.p_memsz > p.p_filesz { 126 let bss_addr: i64 = dst_addr + p.p_filesz 127 nx_loader_memzero(bss_addr as *u8, p.p_memsz - p.p_filesz) 128 } 129 } 130 i = i + 1 131 } 132 133 return h.e_entry 134} 135 136// ---- self-test: build a tiny ELF in memory + load it ------------- 137 138func main() -> i64 { 139 // Construct a minimal ELF in memory: 140 // header (64) + 1 program header (56) + 12 bytes of code 141 // total = 132 bytes; vaddr = 0x10000; entry = 0x10000 + 120 142 let elf: *u8 = sys_mmap(256) 143 var i: i64 = 0 144 while i < 256 { elf[i] = 0; i = i + 1 } 145 146 // ELF header 147 elf[0] = 0x7F; elf[1] = 0x45; elf[2] = 0x4C; elf[3] = 0x46 148 elf[4] = 2 // ELF64 149 elf[5] = 1 // little-endian 150 elf[6] = 1 // version 151 elf[16] = 2 // ET_EXEC 152 elf[18] = 0xF3 // EM_RISCV 153 // e_entry = 0x10078 (vaddr 0x10000 + 120 byte offset) 154 elf[24] = 0x78; elf[25] = 0x00; elf[26] = 0x01; elf[27] = 0x00 155 // e_phoff = 64 156 elf[32] = 64 157 // e_ehsize = 64, phentsize = 56, phnum = 1 158 elf[52] = 64 159 elf[54] = 56 160 elf[56] = 1 161 162 // Program header at offset 64. 163 // p_type = PT_LOAD = 1 164 elf[64] = 1 165 // p_flags = R|X = 5 166 elf[68] = 5 167 // p_offset = 120 (where code begins) 168 elf[72] = 120 169 // p_vaddr = 0x10000 170 elf[80] = 0x00; elf[81] = 0x00; elf[82] = 0x01; elf[83] = 0x00 171 // p_filesz = p_memsz = 12 172 elf[96] = 12 173 elf[104] = 12 174 175 // Code bytes at offset 120. 176 elf[120] = 0x13; elf[121] = 0x05; elf[122] = 0xA0; elf[123] = 0x02 177 elf[124] = 0x93; elf[125] = 0x08; elf[126] = 0xD0; elf[127] = 0x05 178 elf[128] = 0x73; elf[129] = 0x00; elf[130] = 0x00; elf[131] = 0x00 179 180 // Now load into a 1 MB memory buffer. 181 let mem: *u8 = sys_mmap(0x20000) 182 let entry: i64 = nx_loader_load(elf, 132, mem, 0x20000) 183 184 // Entry must be 0x10078. 185 if entry != 0x10078 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 186 187 // The 12 code bytes must have landed at mem[0x10000..0x1000C). 188 if mem[0x10000] != 0x13 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 189 if mem[0x10001] != 0x05 { return __syscall(93, 21, 0, 0, 0, 0, 0) } 190 if mem[0x10003] != 0x02 { return __syscall(93, 22, 0, 0, 0, 0, 0) } 191 if mem[0x10008] != 0x73 { return __syscall(93, 23, 0, 0, 0, 0, 0) } 192 193 // Bytes outside the loaded segment should be zero (mmap default). 194 if mem[0x10010] != 0 { return __syscall(93, 30, 0, 0, 0, 0, 0) } 195 196 // Bad ELF: not a RISC-V machine -> reject. 197 elf[18] = 0x3E // EM_X86_64 198 let bad: i64 = nx_loader_load(elf, 132, mem, 0x20000) 199 if bad != -1 { return __syscall(93, 40, 0, 0, 0, 0, 0) } 200 elf[18] = 0xF3 // restore 201 202 // Out-of-bounds segment -> reject (vaddr way past mem_size). 203 elf[80] = 0x00; elf[81] = 0x00; elf[82] = 0x00; elf[83] = 0x00 204 elf[84] = 0x00; elf[85] = 0x00; elf[86] = 0x00; elf[87] = 0x80 205 let oob: i64 = nx_loader_load(elf, 132, mem, 0x20000) 206 if oob != -1 { return __syscall(93, 50, 0, 0, 0, 0, 0) } 207 208 return 0 209}