code wiki / (root) / elf_writer.nx

elf_writer.nx source

↩ module page · 247 lines · 9296 B

1// elf_writer.nx -- emit a minimal RV64 Linux ELF executable. 2// 3// Library form: `write_elf(code, code_len, fd)` produces a statically- 4// loadable ELF64 on `fd` that executes `code` when run. No linker, 5// no libc, no gcc `as`. With this + nxasm + nxc2.nx we have the full 6// binary-up chain in pure NishiLang. 7// 8// Layout: 9// [0..64) ELF header (e_ehsize = 64) 10// [64..120) Program header (PT_LOAD, 56 bytes) 11// [120..120+N) Code -- N bytes of RV64 machine code 12// 13// Virtual address base = 0x10000 (standard for statically-linked 14// Linux RV64). Entry point = 0x10000 + 120 so execution starts at 15// the first code byte. 16 17import "../runtime/nx_syscalls.nx" 18 19// Encode a 64-bit little-endian value into `dst[off..off+8]`. 20func put_u64_le(dst: *u8, off: i64, v: i64) -> i64 { 21 dst[off + 0] = (v ) & 0xFF 22 dst[off + 1] = (v >> 8 ) & 0xFF 23 dst[off + 2] = (v >> 16 ) & 0xFF 24 dst[off + 3] = (v >> 24 ) & 0xFF 25 dst[off + 4] = (v >> 32 ) & 0xFF 26 dst[off + 5] = (v >> 40 ) & 0xFF 27 dst[off + 6] = (v >> 48 ) & 0xFF 28 dst[off + 7] = (v >> 56 ) & 0xFF 29 return 0 30} 31 32// Parameterised variant: build an ELF with custom base vaddr. The 33// kernel needs vaddr = 0x80000000 (bare-metal RV64 M-mode convention); 34// Linux userspace uses 0x10000. Same layout otherwise. 35// 36// Returns total byte count (120 + code_len). 37func build_elf_image_at(out: *u8, code: *u8, code_len: i64, 38 base_vaddr: i64) -> i64 { 39 let entry_vaddr: i64 = base_vaddr + 120 40 // ---- ELF header (64 bytes) ---- 41 out[0] = 0x7F 42 out[1] = 0x45 43 out[2] = 0x4C 44 out[3] = 0x46 45 out[4] = 2 46 out[5] = 1 47 out[6] = 1 48 out[7] = 0 49 out[8] = 0; out[9] = 0; out[10] = 0; out[11] = 0 50 out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 0 51 out[16] = 2; out[17] = 0 // e_type = ET_EXEC 52 out[18] = 0xF3; out[19] = 0 // e_machine = EM_RISCV 53 out[20] = 1; out[21] = 0; out[22] = 0; out[23] = 0 54 put_u64_le(out, 24, entry_vaddr) 55 put_u64_le(out, 32, 64) // e_phoff 56 put_u64_le(out, 40, 0) // e_shoff 57 out[48] = 0; out[49] = 0; out[50] = 0; out[51] = 0 58 out[52] = 64; out[53] = 0 // e_ehsize 59 out[54] = 56; out[55] = 0 // e_phentsize 60 out[56] = 1; out[57] = 0 // e_phnum 61 out[58] = 0; out[59] = 0 // e_shentsize 62 out[60] = 0; out[61] = 0 // e_shnum 63 out[62] = 0; out[63] = 0 // e_shstrndx 64 // ---- Program header (56 bytes, offset 64) ---- 65 out[64] = 1; out[65] = 0; out[66] = 0; out[67] = 0 // PT_LOAD 66 out[68] = 5; out[69] = 0; out[70] = 0; out[71] = 0 // PF_R|PF_X 67 put_u64_le(out, 72, 0) // p_offset 68 put_u64_le(out, 80, base_vaddr) // p_vaddr 69 put_u64_le(out, 88, base_vaddr) // p_paddr 70 put_u64_le(out, 96, 120 + code_len) // p_filesz 71 put_u64_le(out, 104, 120 + code_len) // p_memsz 72 put_u64_le(out, 112, 0x1000) // p_align 73 // ---- Code ---- 74 var i: i64 = 0 75 while i < code_len { 76 out[120 + i] = code[i] 77 i = i + 1 78 } 79 return 120 + code_len 80} 81 82// Write ELF at a custom base vaddr to fd. 83func write_elf_at(code: *u8, code_len: i64, base_vaddr: i64, 84 fd: i64) -> i64 { 85 let total: i64 = 120 + code_len 86 let img: *u8 = sys_mmap(total + 64) 87 build_elf_image_at(img, code, code_len, base_vaddr) 88 return sys_write(fd, img, total) 89} 90 91// Kernel-mode ELF: page-aligned code at file offset 0x1000. 92// 93// Bare-metal RV64 (QEMU `virt` -bios none -kernel ELF) puts the CPU's 94// reset vector at vaddr 0x80000000 via a fixed mask ROM that ignores 95// e_entry. So vaddr 0x80000000 MUST be the first byte of executable 96// code, not the ELF magic. The userspace layout (file_offset=0 -> 97// vaddr=base) puts the ELF header at 0x80000000 -- silent garbage 98// execution. 99// 100// This layout matches what GNU `ld` produces for the same kernel: 101// File [0..0x40) ELF header 102// File [0x40..0x78) Program header (PT_LOAD, 56 bytes) 103// File [0x78..0x1000) Zero padding (page-alignment fill) 104// File [0x1000..) Code (loaded at p_vaddr = base_vaddr) 105// 106// Entry point = base_vaddr (= start of code in memory). 107// 108// Returns total byte count (0x1000 + code_len). 109func build_elf_kernel_at(out: *u8, code: *u8, code_len: i64, 110 base_vaddr: i64) -> i64 { 111 // Zero the header + padding region [0..0x1000). 112 var z: i64 = 0 113 while z < 0x1000 { 114 out[z] = 0 115 z = z + 1 116 } 117 // ---- ELF header (64 bytes) ---- 118 out[0] = 0x7F 119 out[1] = 0x45 120 out[2] = 0x4C 121 out[3] = 0x46 122 out[4] = 2 123 out[5] = 1 124 out[6] = 1 125 out[7] = 0 126 out[16] = 2; out[17] = 0 // e_type = ET_EXEC 127 out[18] = 0xF3; out[19] = 0 // e_machine = EM_RISCV 128 out[20] = 1; out[21] = 0; out[22] = 0; out[23] = 0 129 put_u64_le(out, 24, base_vaddr) // e_entry = base (first code byte) 130 put_u64_le(out, 32, 64) // e_phoff 131 put_u64_le(out, 40, 0) // e_shoff = 0 132 out[52] = 64; out[53] = 0 // e_ehsize 133 out[54] = 56; out[55] = 0 // e_phentsize 134 out[56] = 1; out[57] = 0 // e_phnum 135 // ---- Program header (56 bytes, offset 64) ---- 136 out[64] = 1; out[65] = 0; out[66] = 0; out[67] = 0 // PT_LOAD 137 out[68] = 7; out[69] = 0; out[70] = 0; out[71] = 0 // PF_R|PF_W|PF_X 138 put_u64_le(out, 72, 0x1000) // p_offset (page-aligned) 139 put_u64_le(out, 80, base_vaddr) // p_vaddr 140 put_u64_le(out, 88, base_vaddr) // p_paddr 141 put_u64_le(out, 96, code_len) // p_filesz (code only) 142 put_u64_le(out, 104, code_len) // p_memsz 143 put_u64_le(out, 112, 0x1000) // p_align 144 // ---- Code at file offset 0x1000 ---- 145 var i: i64 = 0 146 while i < code_len { 147 out[0x1000 + i] = code[i] 148 i = i + 1 149 } 150 return 0x1000 + code_len 151} 152 153// Write a kernel-mode ELF (page-aligned code at file offset 0x1000) to fd. 154func write_elf_kernel_at(code: *u8, code_len: i64, base_vaddr: i64, 155 fd: i64) -> i64 { 156 let total: i64 = 0x1000 + code_len 157 let img: *u8 = sys_mmap(total + 0x1000) 158 build_elf_kernel_at(img, code, code_len, base_vaddr) 159 return sys_write(fd, img, total) 160} 161 162// Build the full ELF image (header + phdr + code) into `out`, return 163// the total byte count. Caller sizes `out` to at least code_len + 164// 120 bytes. 165func build_elf_image(out: *u8, code: *u8, code_len: i64) -> i64 { 166 // ---- ELF header (64 bytes) ---- 167 // e_ident[16]: magic + class64 + data2lsb + version1 + osabi=sysv 168 out[0] = 0x7F 169 out[1] = 0x45 // 'E' 170 out[2] = 0x4C // 'L' 171 out[3] = 0x46 // 'F' 172 out[4] = 2 // ELFCLASS64 173 out[5] = 1 // ELFDATA2LSB 174 out[6] = 1 // EV_CURRENT 175 out[7] = 0 // ELFOSABI_SYSV 176 out[8] = 0 // abiversion 177 out[9] = 0 178 out[10] = 0 179 out[11] = 0 180 out[12] = 0 181 out[13] = 0 182 out[14] = 0 183 out[15] = 0 184 // e_type (2) = ET_EXEC (2) 185 out[16] = 2; out[17] = 0 186 // e_machine (2) = EM_RISCV (243 = 0xF3) 187 out[18] = 0xF3; out[19] = 0 188 // e_version (4) = 1 189 out[20] = 1; out[21] = 0; out[22] = 0; out[23] = 0 190 // e_entry (8) = 0x10000 + 120 = 0x10078 191 put_u64_le(out, 24, 0x10078) 192 // e_phoff (8) = 64 193 put_u64_le(out, 32, 64) 194 // e_shoff (8) = 0 195 put_u64_le(out, 40, 0) 196 // e_flags (4) = 0 197 out[48] = 0; out[49] = 0; out[50] = 0; out[51] = 0 198 // e_ehsize (2) = 64 199 out[52] = 64; out[53] = 0 200 // e_phentsize (2) = 56 201 out[54] = 56; out[55] = 0 202 // e_phnum (2) = 1 203 out[56] = 1; out[57] = 0 204 // e_shentsize (2) = 0 205 out[58] = 0; out[59] = 0 206 // e_shnum (2) = 0 207 out[60] = 0; out[61] = 0 208 // e_shstrndx (2) = 0 209 out[62] = 0; out[63] = 0 210 211 // ---- Program header (56 bytes, offset 64) ---- 212 // p_type (4) = PT_LOAD (1) 213 out[64] = 1; out[65] = 0; out[66] = 0; out[67] = 0 214 // p_flags (4) = PF_R | PF_X = 5 215 out[68] = 5; out[69] = 0; out[70] = 0; out[71] = 0 216 // p_offset (8) = 0 217 put_u64_le(out, 72, 0) 218 // p_vaddr (8) = 0x10000 219 put_u64_le(out, 80, 0x10000) 220 // p_paddr (8) = 0x10000 221 put_u64_le(out, 88, 0x10000) 222 // p_filesz (8) = 120 + code_len 223 put_u64_le(out, 96, 120 + code_len) 224 // p_memsz (8) = 120 + code_len 225 put_u64_le(out, 104, 120 + code_len) 226 // p_align (8) = 0x1000 227 put_u64_le(out, 112, 0x1000) 228 229 // ---- Code (120 onwards) ---- 230 var i: i64 = 0 231 while i < code_len { 232 out[120 + i] = code[i] 233 i = i + 1 234 } 235 return 120 + code_len 236} 237 238// Write an ELF wrapping `code` to fd. Returns the byte count 239// written, or a negative errno on failure. 240func write_elf(code: *u8, code_len: i64, fd: i64) -> i64 { 241 let total: i64 = 120 + code_len 242 let img: *u8 = sys_mmap(total + 64) 243 build_elf_image(img, code, code_len) 244 return sys_write(fd, img, total) 245} 246 247// Library only; self-test + exit(42) demo lives in elf_writer_test.nx.