⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

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}