veridian_kernel/userspace/embedded.rs
1//! Embedded minimal ELF binaries for user-space init and shell
2//!
3//! This module provides pre-built minimal ELF64 binaries that are embedded
4//! directly in the kernel image. During bootstrap, these binaries are written
5//! to the RamFS so that the ELF loader can find them at standard paths
6//! (`/sbin/init`, `/bin/vsh`).
7//!
8//! Machine code is provided for all three architectures:
9//! - x86_64: SYSCALL-based (ABI: rax=sysno, rdi/rsi/rdx=args)
10//! - AArch64: SVC-based (ABI: x8=sysno, x0/x1/x2=args)
11//! - RISC-V: ECALL-based (ABI: a7=sysno, a0/a1/a2=args)
12
13#[cfg(feature = "alloc")]
14extern crate alloc;
15
16#[cfg(feature = "alloc")]
17use alloc::vec::Vec;
18
19// ---------------------------------------------------------------------------
20// ELF64 layout constants
21// ---------------------------------------------------------------------------
22
23/// Size of ELF64 header (bytes).
24const ELF64_EHDR_SIZE: usize = 64;
25
26/// Size of one ELF64 program header entry (bytes).
27const ELF64_PHDR_SIZE: usize = 56;
28
29/// File offset where code begins (immediately after headers).
30const CODE_OFFSET: usize = ELF64_EHDR_SIZE + ELF64_PHDR_SIZE; // 0x78
31
32// ---------------------------------------------------------------------------
33// ELF machine type per architecture
34// ---------------------------------------------------------------------------
35
36/// ELF machine type for the current architecture.
37#[cfg(target_arch = "x86_64")]
38const ELF_MACHINE: u16 = 62; // EM_X86_64
39
40#[cfg(target_arch = "aarch64")]
41const ELF_MACHINE: u16 = 183; // EM_AARCH64
42
43#[cfg(target_arch = "riscv64")]
44const ELF_MACHINE: u16 = 243; // EM_RISCV
45
46// ---------------------------------------------------------------------------
47// x86_64 machine code for the init process
48// ---------------------------------------------------------------------------
49
50/// x86_64 machine code for the minimal init process.
51///
52/// Writes "VeridianOS init started\n" to stdout (fd 1) using VeridianOS
53/// syscall FileWrite (53), then exits with code 0 via ProcessExit (11).
54///
55/// Disassembly:
56/// ```text
57/// 0: bf 01 00 00 00 mov edi, 1 ; fd = stdout
58/// 5: 48 8d 35 15 00 00 00 lea rsi, [rip+0x15] ; buf = &msg
59/// 12: ba 18 00 00 00 mov edx, 24 ; len = 24
60/// 17: b8 35 00 00 00 mov eax, 53 ; SYS_WRITE (FileWrite)
61/// 22: 0f 05 syscall
62/// 24: 31 ff xor edi, edi ; exit_code = 0
63/// 26: b8 0b 00 00 00 mov eax, 11 ; SYS_EXIT (ProcessExit)
64/// 31: 0f 05 syscall
65/// 33: "VeridianOS init started\n"
66/// ```
67#[cfg(target_arch = "x86_64")]
68const INIT_CODE: &[u8] = &[
69 // mov edi, 1
70 0xBF, 0x01, 0x00, 0x00, 0x00,
71 // lea rsi, [rip+0x15] (displacement = 33 - 12 = 21 = 0x15)
72 0x48, 0x8D, 0x35, 0x15, 0x00, 0x00, 0x00, // mov edx, 24
73 0xBA, 0x18, 0x00, 0x00, 0x00, // mov eax, 53 (FileWrite)
74 0xB8, 0x35, 0x00, 0x00, 0x00, // syscall
75 0x0F, 0x05, // xor edi, edi
76 0x31, 0xFF, // mov eax, 11 (ProcessExit)
77 0xB8, 0x0B, 0x00, 0x00, 0x00, // syscall
78 0x0F, 0x05, // msg: "VeridianOS init started\n" (24 bytes)
79 b'V', b'e', b'r', b'i', b'd', b'i', b'a', b'n', b'O', b'S', b' ', b'i', b'n', b'i', b't', b' ',
80 b's', b't', b'a', b'r', b't', b'e', b'd', b'\n',
81];
82
83// ---------------------------------------------------------------------------
84// x86_64 machine code for the shell (vsh)
85// ---------------------------------------------------------------------------
86
87/// x86_64 machine code for the minimal shell process.
88///
89/// Writes "vsh> " to stdout, then exits with code 0. A future sprint will
90/// extend this to read input and dispatch commands.
91///
92/// Disassembly:
93/// ```text
94/// 0: bf 01 00 00 00 mov edi, 1 ; fd = stdout
95/// 5: 48 8d 35 15 00 00 00 lea rsi, [rip+0x15] ; buf = &msg
96/// 12: ba 05 00 00 00 mov edx, 5 ; len = 5
97/// 17: b8 35 00 00 00 mov eax, 53 ; SYS_WRITE (FileWrite)
98/// 22: 0f 05 syscall
99/// 24: 31 ff xor edi, edi ; exit_code = 0
100/// 26: b8 0b 00 00 00 mov eax, 11 ; SYS_EXIT (ProcessExit)
101/// 31: 0f 05 syscall
102/// 33: "vsh> \n"
103/// ```
104#[cfg(target_arch = "x86_64")]
105const SHELL_CODE: &[u8] = &[
106 // mov edi, 1
107 0xBF, 0x01, 0x00, 0x00, 0x00,
108 // lea rsi, [rip+0x15] (displacement = 33 - 12 = 21 = 0x15)
109 0x48, 0x8D, 0x35, 0x15, 0x00, 0x00, 0x00, // mov edx, 6
110 0xBA, 0x06, 0x00, 0x00, 0x00, // mov eax, 53 (FileWrite)
111 0xB8, 0x35, 0x00, 0x00, 0x00, // syscall
112 0x0F, 0x05, // xor edi, edi
113 0x31, 0xFF, // mov eax, 11 (ProcessExit)
114 0xB8, 0x0B, 0x00, 0x00, 0x00, // syscall
115 0x0F, 0x05, // msg: "vsh> \n" (6 bytes)
116 b'v', b's', b'h', b'>', b' ', b'\n',
117];
118
119// ---------------------------------------------------------------------------
120// x86_64 machine code for the hello test program
121// ---------------------------------------------------------------------------
122
123/// x86_64 machine code for the minimal hello test program.
124///
125/// Writes "Hello from VeridianOS!\n" to stdout, then exits with code 0.
126///
127/// Disassembly:
128/// ```text
129/// 0: bf 01 00 00 00 mov edi, 1 ; fd = stdout
130/// 5: 48 8d 35 15 00 00 00 lea rsi, [rip+0x15] ; buf = &msg
131/// 12: ba 18 00 00 00 mov edx, 24 ; len = 24
132/// 17: b8 35 00 00 00 mov eax, 53 ; SYS_WRITE (FileWrite)
133/// 22: 0f 05 syscall
134/// 24: 31 ff xor edi, edi ; exit_code = 0
135/// 26: b8 0b 00 00 00 mov eax, 11 ; SYS_EXIT (ProcessExit)
136/// 31: 0f 05 syscall
137/// 33: "Hello from VeridianOS!\n"
138/// ```
139#[cfg(target_arch = "x86_64")]
140const HELLO_CODE: &[u8] = &[
141 // mov edi, 1
142 0xBF, 0x01, 0x00, 0x00, 0x00,
143 // lea rsi, [rip+0x15] (displacement = 33 - 12 = 21 = 0x15)
144 0x48, 0x8D, 0x35, 0x15, 0x00, 0x00, 0x00, // mov edx, 24
145 0xBA, 0x18, 0x00, 0x00, 0x00, // mov eax, 53 (FileWrite)
146 0xB8, 0x35, 0x00, 0x00, 0x00, // syscall
147 0x0F, 0x05, // xor edi, edi
148 0x31, 0xFF, // mov eax, 11 (ProcessExit)
149 0xB8, 0x0B, 0x00, 0x00, 0x00, // syscall
150 0x0F, 0x05, // msg: "Hello from VeridianOS!\n" (24 bytes)
151 b'H', b'e', b'l', b'l', b'o', b' ', b'f', b'r', b'o', b'm', b' ', b'V', b'e', b'r', b'i', b'd',
152 b'i', b'a', b'n', b'O', b'S', b'!', b'\n',
153];
154
155// ---------------------------------------------------------------------------
156// AArch64 machine code for the init process
157// ---------------------------------------------------------------------------
158
159/// AArch64 machine code for the minimal init process.
160///
161/// Writes "VeridianOS init started\n" to stdout (fd 1) using VeridianOS
162/// syscall FileWrite (53) via SVC, then exits with code 0 via
163/// ProcessExit (11).
164///
165/// Disassembly:
166/// ```text
167/// 0x00: d2800020 mov x0, #1 ; fd = stdout
168/// 0x04: 100000e1 adr x1, #28 ; buf = &msg (PC-relative)
169/// 0x08: d2800302 mov x2, #24 ; len = 24
170/// 0x0c: d28006a8 mov x8, #53 ; SYS_WRITE (FileWrite)
171/// 0x10: d4000001 svc #0 ; syscall
172/// 0x14: d2800000 mov x0, #0 ; exit_code = 0
173/// 0x18: d2800168 mov x8, #11 ; SYS_EXIT (ProcessExit)
174/// 0x1c: d4000001 svc #0 ; syscall
175/// 0x20: "VeridianOS init started\n"
176/// ```
177#[cfg(target_arch = "aarch64")]
178const INIT_CODE: &[u8] = &[
179 // mov x0, #1 (fd = stdout)
180 0x20, 0x00, 0x80, 0xD2, // adr x1, #28 (msg is 28 bytes ahead of this instruction)
181 0xE1, 0x00, 0x00, 0x10, // mov x2, #24 (len = 24)
182 0x02, 0x03, 0x80, 0xD2, // mov x8, #53 (FileWrite)
183 0xA8, 0x06, 0x80, 0xD2, // svc #0
184 0x01, 0x00, 0x00, 0xD4, // mov x0, #0 (exit_code = 0)
185 0x00, 0x00, 0x80, 0xD2, // mov x8, #11 (ProcessExit)
186 0x68, 0x01, 0x80, 0xD2, // svc #0
187 0x01, 0x00, 0x00, 0xD4, // msg: "VeridianOS init started\n" (24 bytes)
188 b'V', b'e', b'r', b'i', b'd', b'i', b'a', b'n', b'O', b'S', b' ', b'i', b'n', b'i', b't', b' ',
189 b's', b't', b'a', b'r', b't', b'e', b'd', b'\n',
190];
191
192// ---------------------------------------------------------------------------
193// AArch64 machine code for the shell (vsh)
194// ---------------------------------------------------------------------------
195
196/// AArch64 machine code for the minimal shell process.
197///
198/// Writes "vsh> \n" to stdout via SVC, then exits with code 0.
199///
200/// Disassembly:
201/// ```text
202/// 0x00: d2800020 mov x0, #1 ; fd = stdout
203/// 0x04: 100000e1 adr x1, #28 ; buf = &msg (PC-relative)
204/// 0x08: d28000c2 mov x2, #6 ; len = 6
205/// 0x0c: d28006a8 mov x8, #53 ; SYS_WRITE (FileWrite)
206/// 0x10: d4000001 svc #0 ; syscall
207/// 0x14: d2800000 mov x0, #0 ; exit_code = 0
208/// 0x18: d2800168 mov x8, #11 ; SYS_EXIT (ProcessExit)
209/// 0x1c: d4000001 svc #0 ; syscall
210/// 0x20: "vsh> \n"
211/// ```
212#[cfg(target_arch = "aarch64")]
213const SHELL_CODE: &[u8] = &[
214 // mov x0, #1 (fd = stdout)
215 0x20, 0x00, 0x80, 0xD2, // adr x1, #28 (msg is 28 bytes ahead of this instruction)
216 0xE1, 0x00, 0x00, 0x10, // mov x2, #6 (len = 6)
217 0xC2, 0x00, 0x80, 0xD2, // mov x8, #53 (FileWrite)
218 0xA8, 0x06, 0x80, 0xD2, // svc #0
219 0x01, 0x00, 0x00, 0xD4, // mov x0, #0 (exit_code = 0)
220 0x00, 0x00, 0x80, 0xD2, // mov x8, #11 (ProcessExit)
221 0x68, 0x01, 0x80, 0xD2, // svc #0
222 0x01, 0x00, 0x00, 0xD4, // msg: "vsh> \n" (6 bytes)
223 b'v', b's', b'h', b'>', b' ', b'\n',
224];
225
226// ---------------------------------------------------------------------------
227// RISC-V 64-bit machine code for the init process
228// ---------------------------------------------------------------------------
229
230/// RISC-V 64-bit machine code for the minimal init process.
231///
232/// Writes "VeridianOS init started\n" to stdout (fd 1) using VeridianOS
233/// syscall FileWrite (53) via ECALL, then exits with code 0 via
234/// ProcessExit (11).
235///
236/// Disassembly:
237/// ```text
238/// 0x00: 00100513 addi a0, zero, 1 ; fd = stdout
239/// 0x04: 00000597 auipc a1, 0 ; a1 = PC
240/// 0x08: 02058593 addi a1, a1, 32 ; a1 = &msg (32 bytes from auipc)
241/// 0x0c: 01800613 addi a2, zero, 24 ; len = 24
242/// 0x10: 03500893 addi a7, zero, 53 ; SYS_WRITE (FileWrite)
243/// 0x14: 00000073 ecall ; syscall
244/// 0x18: 00000513 addi a0, zero, 0 ; exit_code = 0
245/// 0x1c: 00b00893 addi a7, zero, 11 ; SYS_EXIT (ProcessExit)
246/// 0x20: 00000073 ecall ; syscall
247/// 0x24: "VeridianOS init started\n"
248/// ```
249#[cfg(target_arch = "riscv64")]
250const INIT_CODE: &[u8] = &[
251 // addi a0, zero, 1 (fd = stdout)
252 0x13, 0x05, 0x10, 0x00, // auipc a1, 0 (a1 = PC of this instruction)
253 0x97, 0x05, 0x00, 0x00, // addi a1, a1, 32 (a1 += 32 → points to msg)
254 0x93, 0x85, 0x05, 0x02, // addi a2, zero, 24 (len = 24)
255 0x13, 0x06, 0x80, 0x01, // addi a7, zero, 53 (FileWrite)
256 0x93, 0x08, 0x50, 0x03, // ecall
257 0x73, 0x00, 0x00, 0x00, // addi a0, zero, 0 (exit_code = 0)
258 0x13, 0x05, 0x00, 0x00, // addi a7, zero, 11 (ProcessExit)
259 0x93, 0x08, 0xB0, 0x00, // ecall
260 0x73, 0x00, 0x00, 0x00, // msg: "VeridianOS init started\n" (24 bytes)
261 b'V', b'e', b'r', b'i', b'd', b'i', b'a', b'n', b'O', b'S', b' ', b'i', b'n', b'i', b't', b' ',
262 b's', b't', b'a', b'r', b't', b'e', b'd', b'\n',
263];
264
265// ---------------------------------------------------------------------------
266// RISC-V 64-bit machine code for the shell (vsh)
267// ---------------------------------------------------------------------------
268
269/// RISC-V 64-bit machine code for the minimal shell process.
270///
271/// Writes "vsh> \n" to stdout via ECALL, then exits with code 0.
272///
273/// Disassembly:
274/// ```text
275/// 0x00: 00100513 addi a0, zero, 1 ; fd = stdout
276/// 0x04: 00000597 auipc a1, 0 ; a1 = PC
277/// 0x08: 02058593 addi a1, a1, 32 ; a1 = &msg (32 bytes from auipc)
278/// 0x0c: 00600613 addi a2, zero, 6 ; len = 6
279/// 0x10: 03500893 addi a7, zero, 53 ; SYS_WRITE (FileWrite)
280/// 0x14: 00000073 ecall ; syscall
281/// 0x18: 00000513 addi a0, zero, 0 ; exit_code = 0
282/// 0x1c: 00b00893 addi a7, zero, 11 ; SYS_EXIT (ProcessExit)
283/// 0x20: 00000073 ecall ; syscall
284/// 0x24: "vsh> \n"
285/// ```
286#[cfg(target_arch = "riscv64")]
287const SHELL_CODE: &[u8] = &[
288 // addi a0, zero, 1 (fd = stdout)
289 0x13, 0x05, 0x10, 0x00, // auipc a1, 0 (a1 = PC of this instruction)
290 0x97, 0x05, 0x00, 0x00, // addi a1, a1, 32 (a1 += 32 → points to msg)
291 0x93, 0x85, 0x05, 0x02, // addi a2, zero, 6 (len = 6)
292 0x13, 0x06, 0x60, 0x00, // addi a7, zero, 53 (FileWrite)
293 0x93, 0x08, 0x50, 0x03, // ecall
294 0x73, 0x00, 0x00, 0x00, // addi a0, zero, 0 (exit_code = 0)
295 0x13, 0x05, 0x00, 0x00, // addi a7, zero, 11 (ProcessExit)
296 0x93, 0x08, 0xB0, 0x00, // ecall
297 0x73, 0x00, 0x00, 0x00, // msg: "vsh> \n" (6 bytes)
298 b'v', b's', b'h', b'>', b' ', b'\n',
299];
300
301// ---------------------------------------------------------------------------
302// Public accessors for raw machine code
303// ---------------------------------------------------------------------------
304
305/// Return the raw machine code for the init process.
306///
307/// This is the position-independent code that will be copied to a user-space
308/// page. It uses PC-relative addressing so it can run at any address.
309pub fn init_code_bytes() -> &'static [u8] {
310 INIT_CODE
311}
312
313// ---------------------------------------------------------------------------
314// ELF64 builder
315// ---------------------------------------------------------------------------
316
317/// Build a minimal ELF64 executable from raw machine code.
318///
319/// Returns a `Vec<u8>` containing a valid ELF64 binary with one PT_LOAD
320/// segment. The binary is laid out as:
321///
322/// | Offset | Content | Size |
323/// |---------|-----------------------|----------|
324/// | 0x00 | ELF64 header | 64 bytes |
325/// | 0x40 | Program header (LOAD) | 56 bytes |
326/// | 0x78 | Machine code | variable |
327///
328/// The entry point is set to `load_addr + 0x78` so execution begins at the
329/// first byte of `code`.
330#[cfg(feature = "alloc")]
331fn build_minimal_elf(code: &[u8], load_addr: u64) -> Vec<u8> {
332 let total_size = CODE_OFFSET + code.len();
333 let entry_point = load_addr + CODE_OFFSET as u64;
334
335 let mut elf = Vec::with_capacity(total_size);
336
337 // -----------------------------------------------------------------------
338 // ELF64 Header (64 bytes)
339 // -----------------------------------------------------------------------
340
341 // e_ident[0..4]: magic
342 elf.extend_from_slice(&[0x7F, b'E', b'L', b'F']);
343 // e_ident[4]: class = ELFCLASS64
344 elf.push(2);
345 // e_ident[5]: data = ELFDATA2LSB (little-endian)
346 elf.push(1);
347 // e_ident[6]: version = EV_CURRENT
348 elf.push(1);
349 // e_ident[7]: OS/ABI = ELFOSABI_NONE
350 elf.push(0);
351 // e_ident[8..16]: padding
352 elf.extend_from_slice(&[0u8; 8]);
353
354 // e_type: ET_EXEC (2)
355 elf.extend_from_slice(&2u16.to_le_bytes());
356 // e_machine: architecture-specific
357 elf.extend_from_slice(&ELF_MACHINE.to_le_bytes());
358 // e_version: EV_CURRENT (1)
359 elf.extend_from_slice(&1u32.to_le_bytes());
360 // e_entry: entry point
361 elf.extend_from_slice(&entry_point.to_le_bytes());
362 // e_phoff: program header offset (immediately after ELF header)
363 elf.extend_from_slice(&(ELF64_EHDR_SIZE as u64).to_le_bytes());
364 // e_shoff: section header offset (none)
365 elf.extend_from_slice(&0u64.to_le_bytes());
366 // e_flags
367 elf.extend_from_slice(&0u32.to_le_bytes());
368 // e_ehsize: ELF header size
369 elf.extend_from_slice(&(ELF64_EHDR_SIZE as u16).to_le_bytes());
370 // e_phentsize: program header entry size
371 elf.extend_from_slice(&(ELF64_PHDR_SIZE as u16).to_le_bytes());
372 // e_phnum: number of program headers
373 elf.extend_from_slice(&1u16.to_le_bytes());
374 // e_shentsize: section header entry size (none)
375 elf.extend_from_slice(&0u16.to_le_bytes());
376 // e_shnum: number of section headers
377 elf.extend_from_slice(&0u16.to_le_bytes());
378 // e_shstrndx: section name string table index
379 elf.extend_from_slice(&0u16.to_le_bytes());
380
381 debug_assert_eq!(elf.len(), ELF64_EHDR_SIZE);
382
383 // -----------------------------------------------------------------------
384 // Program Header: PT_LOAD (56 bytes)
385 // -----------------------------------------------------------------------
386
387 // p_type: PT_LOAD (1)
388 elf.extend_from_slice(&1u32.to_le_bytes());
389 // p_flags: PF_R | PF_X (0x5)
390 elf.extend_from_slice(&5u32.to_le_bytes());
391 // p_offset: load from start of file
392 elf.extend_from_slice(&0u64.to_le_bytes());
393 // p_vaddr: virtual load address
394 elf.extend_from_slice(&load_addr.to_le_bytes());
395 // p_paddr: physical load address (same as vaddr)
396 elf.extend_from_slice(&load_addr.to_le_bytes());
397 // p_filesz: file size of segment
398 elf.extend_from_slice(&(total_size as u64).to_le_bytes());
399 // p_memsz: memory size of segment (same as filesz)
400 elf.extend_from_slice(&(total_size as u64).to_le_bytes());
401 // p_align: alignment
402 elf.extend_from_slice(&0x1000u64.to_le_bytes());
403
404 debug_assert_eq!(elf.len(), CODE_OFFSET);
405
406 // -----------------------------------------------------------------------
407 // Code
408 // -----------------------------------------------------------------------
409
410 elf.extend_from_slice(code);
411
412 debug_assert_eq!(elf.len(), total_size);
413 elf
414}
415
416// ---------------------------------------------------------------------------
417// RamFS population
418// ---------------------------------------------------------------------------
419
420/// Populate the RamFS with embedded init and shell binaries.
421///
422/// Creates `/sbin/init` and `/bin/vsh` in the VFS so that
423/// `load_init_process()` and `load_shell()` can find real ELF executables
424/// instead of falling back to stub processes.
425///
426/// Must be called after VFS is initialized (Stage 4) but before
427/// `create_init_process()` (Stage 6).
428#[cfg(feature = "alloc")]
429pub fn populate_initramfs() -> Result<(), crate::error::KernelError> {
430 use crate::fs;
431
432 // User-space load address for both binaries.
433 // 0x40_0000 (4 MiB) is a conventional user-space base address,
434 // well within the lower-half user address space.
435 const LOAD_ADDR: u64 = 0x40_0000;
436
437 let init_elf = build_minimal_elf(INIT_CODE, LOAD_ADDR);
438 let shell_elf = build_minimal_elf(SHELL_CODE, LOAD_ADDR);
439
440 #[cfg(target_arch = "x86_64")]
441 let hello_elf = build_minimal_elf(HELLO_CODE, LOAD_ADDR);
442
443 // /sbin and /bin are already created by fs::init(), but guard against
444 // them being absent. Ignore AlreadyExists errors.
445 let vfs = fs::get_vfs().read();
446 let _ = vfs.mkdir("/sbin", fs::Permissions::default());
447 let _ = vfs.mkdir("/bin", fs::Permissions::default());
448 drop(vfs);
449
450 // Write init binary to /sbin/init
451 fs::write_file("/sbin/init", &init_elf)?;
452
453 // Write shell binary to /bin/vsh
454 fs::write_file("/bin/vsh", &shell_elf)?;
455
456 // Write hello test program to /bin/hello (x86_64 only for now)
457 #[cfg(target_arch = "x86_64")]
458 fs::write_file("/bin/hello", &hello_elf)?;
459
460 Ok(())
461}