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}