code wiki / (root) / nx_elf_exec.nx

nx_elf_exec.nx source

↩ module page · 292 lines · 10318 B

1// nx_elf_exec.nx -- executable ELF writer. 2// 3// Consumes the layout output from nx_link.nx (or any caller that 4// provides aligned text/rodata/data/bss segments + entry vaddr) and 5// emits a complete static ELF64 executable suitable for nx_loader 6// to consume. 7// 8// Output layout (file offsets): 9// 10// 0x00 - 0x40 Elf64_Ehdr 64 bytes 11// 0x40 - 0x? Program header table 2 * 56 bytes (RX, RW) 12// page boundary 13// 0x1000+ RX segment (text + rodata) 14// page boundary 15// 0xN000+ RW segment (data; bss is NOBITS) 16// 17// We emit STATIC executables only (no dynamic linker, no PT_INTERP, 18// no PT_DYNAMIC, no PHDR self-reference). That matches what the 19// off-C path produces today. 20// 21// Pairs with nx_link (provides the layout) + nx_loader (consumes 22// the output) + nx_rv64_sim (executes the loaded image). 23 24// nx_safety_envelope: 25// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 26// sil_target: SIL1 27// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 28// verdict: NOT_YET_EVALUATED 29 30import "syscalls.nx" 31const NX_MAGIC_65536: i64 = 65536 32 33// ---- ELF constants ------------------------------------------------ 34 35const NX_ELFEXEC_PAGE: i64 = 0x1000 36 37const NX_ELFEXEC_EHDR_BYTES: i64 = 64 38const NX_ELFEXEC_PHDR_BYTES: i64 = 56 39 40// e_ident[0..16] 41const NX_ELFEXEC_EI_MAG0: i64 = 0x7F 42const NX_ELFEXEC_EI_MAG1: i64 = 0x45 43const NX_ELFEXEC_EI_MAG2: i64 = 0x4C 44const NX_ELFEXEC_EI_MAG3: i64 = 0x46 45const NX_ELFEXEC_EI_CLASS_64: i64 = 2 46const NX_ELFEXEC_EI_DATA_LSB: i64 = 1 47const NX_ELFEXEC_EI_VERSION: i64 = 1 48 49// e_type 50const NX_ELFEXEC_ET_EXEC: i64 = 2 51 52// e_machine 53const NX_ELFEXEC_EM_RISCV: i64 = 0xF3 54 55// p_type / p_flags 56const NX_ELFEXEC_PT_LOAD: i64 = 1 57const NX_ELFEXEC_PF_X: i64 = 1 58const NX_ELFEXEC_PF_W: i64 = 2 59const NX_ELFEXEC_PF_R: i64 = 4 60 61// ---- byte writers ------------------------------------------------- 62 63func nx_elfexec_write_u8(buf: *u8, off: i64, v: i64) -> i64 { 64 buf[off] = v & 0xFF 65 return off + 1 66} 67 68func nx_elfexec_write_u16(buf: *u8, off: i64, v: i64) -> i64 { 69 buf[off] = v & 0xFF 70 buf[off + 1] = (v >> 8) & 0xFF 71 return off + 2 72} 73 74func nx_elfexec_write_u32(buf: *u8, off: i64, v: i64) -> i64 { 75 buf[off] = v & 0xFF 76 buf[off + 1] = (v >> 8) & 0xFF 77 buf[off + 2] = (v >> 16) & 0xFF 78 buf[off + 3] = (v >> 24) & 0xFF 79 return off + 4 80} 81 82func nx_elfexec_write_u64(buf: *u8, off: i64, v: i64) -> i64 { 83 buf[off] = v & 0xFF 84 buf[off + 1] = (v >> 8) & 0xFF 85 buf[off + 2] = (v >> 16) & 0xFF 86 buf[off + 3] = (v >> 24) & 0xFF 87 buf[off + 4] = (v >> 32) & 0xFF 88 buf[off + 5] = (v >> 40) & 0xFF 89 buf[off + 6] = (v >> 48) & 0xFF 90 buf[off + 7] = (v >> 56) & 0xFF 91 return off + 8 92} 93 94// ---- Elf64_Ehdr writer ------------------------------------------- 95 96func nx_elfexec_write_ehdr(buf: *u8, entry_vaddr: i64, e_phoff: i64, e_phnum: i64) -> i64 { 97 var i: i64 = 0 98 while i < NX_ELFEXEC_EHDR_BYTES { buf[i] = 0; i = i + 1 } 99 100 nx_elfexec_write_u8(buf, 0, NX_ELFEXEC_EI_MAG0) 101 nx_elfexec_write_u8(buf, 1, NX_ELFEXEC_EI_MAG1) 102 nx_elfexec_write_u8(buf, 2, NX_ELFEXEC_EI_MAG2) 103 nx_elfexec_write_u8(buf, 3, NX_ELFEXEC_EI_MAG3) 104 nx_elfexec_write_u8(buf, 4, NX_ELFEXEC_EI_CLASS_64) 105 nx_elfexec_write_u8(buf, 5, NX_ELFEXEC_EI_DATA_LSB) 106 nx_elfexec_write_u8(buf, 6, NX_ELFEXEC_EI_VERSION) 107 // bytes 7..15 left 0 (OSABI=System V, padding) 108 109 nx_elfexec_write_u16(buf, 16, NX_ELFEXEC_ET_EXEC) 110 nx_elfexec_write_u16(buf, 18, NX_ELFEXEC_EM_RISCV) 111 nx_elfexec_write_u32(buf, 20, 1) // e_version 112 nx_elfexec_write_u64(buf, 24, entry_vaddr) // e_entry 113 nx_elfexec_write_u64(buf, 32, e_phoff) // e_phoff 114 nx_elfexec_write_u64(buf, 40, 0) // e_shoff (no SHT in execs we emit) 115 nx_elfexec_write_u32(buf, 48, 0) // e_flags 116 nx_elfexec_write_u16(buf, 52, NX_ELFEXEC_EHDR_BYTES) // e_ehsize 117 nx_elfexec_write_u16(buf, 54, NX_ELFEXEC_PHDR_BYTES) // e_phentsize 118 nx_elfexec_write_u16(buf, 56, e_phnum) // e_phnum 119 nx_elfexec_write_u16(buf, 58, 0) // e_shentsize 120 nx_elfexec_write_u16(buf, 60, 0) // e_shnum 121 nx_elfexec_write_u16(buf, 62, 0) // e_shstrndx 122 return NX_ELFEXEC_EHDR_BYTES 123} 124 125// ---- Elf64_Phdr writer ------------------------------------------- 126 127func nx_elfexec_write_phdr( 128 buf: *u8, off: i64, 129 p_type: i64, p_flags: i64, 130 p_offset: i64, p_vaddr: i64, 131 p_filesz: i64, p_memsz: i64, 132 p_align: i64 133) -> i64 { 134 nx_elfexec_write_u32(buf, off, p_type) 135 nx_elfexec_write_u32(buf, off + 4, p_flags) 136 nx_elfexec_write_u64(buf, off + 8, p_offset) 137 nx_elfexec_write_u64(buf, off + 16, p_vaddr) 138 nx_elfexec_write_u64(buf, off + 24, p_vaddr) // p_paddr = p_vaddr for static 139 nx_elfexec_write_u64(buf, off + 32, p_filesz) 140 nx_elfexec_write_u64(buf, off + 40, p_memsz) 141 nx_elfexec_write_u64(buf, off + 48, p_align) 142 return off + NX_ELFEXEC_PHDR_BYTES 143} 144 145// ---- top-level emit ---------------------------------------------- 146 147// Emit a complete executable ELF given two linker-resolved segments: 148// - RX = text + rodata, base = rx_vaddr, bytes = rx_buf, len = rx_size 149// - RW = data, base = rw_vaddr, bytes = rw_buf, len = rw_size 150// + extra bss bytes that the loader zero-fills (bss_extra) 151// - entry = program entry virtual address (resolved `_start`) 152// 153// Output goes into out_buf; returns total bytes written, or a 154// negative error code. The caller mmap's out_buf; we never 155// allocate past out_cap. 156// 157// File layout: 158// 0..64 Ehdr 159// 64..(64+2*56) 2 program headers (RX, RW) 160// PAGE-aligned RX bytes 161// PAGE-aligned RW bytes 162func nx_elfexec_emit( 163 out_buf: *u8, out_cap: i64, 164 rx_buf: *u8, rx_size: i64, rx_vaddr: i64, 165 rw_buf: *u8, rw_size: i64, rw_vaddr: i64, 166 bss_extra: i64, 167 entry: i64 168) -> i64 { 169 let phdr_off: i64 = NX_ELFEXEC_EHDR_BYTES 170 let n_phdr: i64 = 2 171 let after_phdr: i64 = phdr_off + n_phdr * NX_ELFEXEC_PHDR_BYTES 172 173 // RX segment file offset must be PAGE-aligned AND congruent to 174 // p_vaddr modulo page size (Linux loader requirement). 175 var rx_file_off: i64 = (after_phdr + (NX_ELFEXEC_PAGE - 1)) & (~(NX_ELFEXEC_PAGE - 1)) 176 let rx_skew: i64 = rx_vaddr & (NX_ELFEXEC_PAGE - 1) 177 rx_file_off = rx_file_off + rx_skew 178 179 let rx_end: i64 = rx_file_off + rx_size 180 var rw_file_off: i64 = (rx_end + (NX_ELFEXEC_PAGE - 1)) & (~(NX_ELFEXEC_PAGE - 1)) 181 let rw_skew: i64 = rw_vaddr & (NX_ELFEXEC_PAGE - 1) 182 rw_file_off = rw_file_off + rw_skew 183 184 let total: i64 = rw_file_off + rw_size 185 if total > out_cap { return -1 } 186 187 // Zero the entire output up front so any inter-segment gap is 188 // already-cleared. 189 var i: i64 = 0 190 while i < total { 191 out_buf[i] = 0 192 i = i + 1 193 } 194 195 // Ehdr. 196 nx_elfexec_write_ehdr(out_buf, entry, phdr_off, n_phdr) 197 198 // Phdr 0: RX (text + rodata). 199 nx_elfexec_write_phdr(out_buf, phdr_off, 200 NX_ELFEXEC_PT_LOAD, 201 NX_ELFEXEC_PF_R | NX_ELFEXEC_PF_X, 202 rx_file_off, rx_vaddr, 203 rx_size, rx_size, 204 NX_ELFEXEC_PAGE) 205 206 // Phdr 1: RW (data + bss). 207 nx_elfexec_write_phdr(out_buf, phdr_off + NX_ELFEXEC_PHDR_BYTES, 208 NX_ELFEXEC_PT_LOAD, 209 NX_ELFEXEC_PF_R | NX_ELFEXEC_PF_W, 210 rw_file_off, rw_vaddr, 211 rw_size, rw_size + bss_extra, 212 NX_ELFEXEC_PAGE) 213 214 // Copy RX bytes. 215 var k: i64 = 0 216 while k < rx_size { 217 out_buf[rx_file_off + k] = rx_buf[k] 218 k = k + 1 219 } 220 221 // Copy RW bytes. 222 var m: i64 = 0 223 while m < rw_size { 224 out_buf[rw_file_off + m] = rw_buf[m] 225 m = m + 1 226 } 227 228 return total 229} 230 231// ---- self-test --------------------------------------------------- 232 233func main() -> i64 { 234 let out: *u8 = sys_mmap(NX_MAGIC_65536) 235 236 // Tiny RX segment: 12 bytes of synthetic "text" (li a0,42; li a7,93; ecall) 237 let rx: *u8 = sys_mmap(64) 238 rx[0] = 0x13; rx[1] = 0x05; rx[2] = 0xA0; rx[3] = 0x02 239 rx[4] = 0x93; rx[5] = 0x08; rx[6] = 0xD0; rx[7] = 0x05 240 rx[8] = 0x73; rx[9] = 0x00; rx[10] = 0x00; rx[11] = 0x00 241 242 // Tiny RW segment: 8 bytes of "data". 243 let rw: *u8 = sys_mmap(64) 244 rw[0] = 0x41; rw[1] = 0x42; rw[2] = 0x43; rw[3] = 0x44 245 rw[4] = 0x45; rw[5] = 0x46; rw[6] = 0x47; rw[7] = 0x48 246 247 let total: i64 = nx_elfexec_emit( 248 out, NX_MAGIC_65536, 249 rx, 12, 0x10000, 250 rw, 8, 0x12000, 251 128, 252 0x10000) 253 254 if total <= 0 { return __syscall(93, 1, 0, 0, 0, 0, 0) } 255 if total > NX_MAGIC_65536 { return __syscall(93, 2, 0, 0, 0, 0, 0) } 256 257 // Verify magic. 258 if out[0] != 0x7F { return __syscall(93, 3, 0, 0, 0, 0, 0) } 259 if out[1] != 0x45 { return __syscall(93, 4, 0, 0, 0, 0, 0) } 260 if out[2] != 0x4C { return __syscall(93, 5, 0, 0, 0, 0, 0) } 261 if out[3] != 0x46 { return __syscall(93, 6, 0, 0, 0, 0, 0) } 262 if out[4] != 2 { return __syscall(93, 7, 0, 0, 0, 0, 0) } // ELFCLASS64 263 if out[5] != 1 { return __syscall(93, 8, 0, 0, 0, 0, 0) } // ELFDATA2LSB 264 265 // e_machine at offset 18 = RISC-V (0xF3). 266 if out[18] != 0xF3 { return __syscall(93, 9, 0, 0, 0, 0, 0) } 267 268 // e_entry at offset 24 = 0x10000 LE. 269 if out[24] != 0x00 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 270 if out[25] != 0x00 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 271 if out[26] != 0x01 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 272 273 // e_phnum at offset 56 = 2. 274 if out[56] != 2 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 275 if out[57] != 0 { return __syscall(93, 14, 0, 0, 0, 0, 0) } 276 277 // First PHDR p_type at offset 64 = PT_LOAD = 1. 278 if out[64] != 1 { return __syscall(93, 15, 0, 0, 0, 0, 0) } 279 280 // First PHDR p_flags at offset 68 = R|X = 5. 281 if out[68] != 5 { return __syscall(93, 16, 0, 0, 0, 0, 0) } 282 283 // RX bytes should appear at file offset 0x1000 (because rx_skew = 0). 284 if out[0x1000] != 0x13 { return __syscall(93, 17, 0, 0, 0, 0, 0) } 285 if out[0x100B] != 0x00 { return __syscall(93, 18, 0, 0, 0, 0, 0) } 286 287 // RW bytes should appear at file offset 0x2000. 288 if out[0x2000] != 0x41 { return __syscall(93, 19, 0, 0, 0, 0, 0) } 289 if out[0x2007] != 0x48 { return __syscall(93, 20, 0, 0, 0, 0, 0) } 290 291 return 0 292}