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.