From 4844b6c2d2fdcdac48b289cc9cd40db65086e477 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 01:26:18 +0200 Subject: [PATCH] feat(elf): add no_std ELF64 parser for loading process binaries --- Cargo.toml | 3 + crates/elf/Cargo.toml | 6 ++ crates/elf/src/lib.rs | 167 ++++++++++++++++++++++++++++++++++++++++ crates/elf/src/tests.rs | 132 +++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 crates/elf/Cargo.toml create mode 100644 crates/elf/src/lib.rs create mode 100644 crates/elf/src/tests.rs diff --git a/Cargo.toml b/Cargo.toml index 3083fd4..010f18d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/apps/snake", "crates/domain", + "crates/elf", "crates/kernel", "crates/platform", "crates/server-framework", @@ -41,6 +42,7 @@ resolver = "2" # kernel types = { path = "crates/types" } domain = { path = "crates/domain" } +elf = { path = "crates/elf" } platform = { path = "crates/platform" } kernel = { path = "crates/kernel" } server-framework = { path = "crates/server-framework" } @@ -77,6 +79,7 @@ user-manager = { path = "crates/servers/user-manager" } # apps snake = { path = "crates/apps/snake" } + [profile.release] strip = true lto = true diff --git a/crates/elf/Cargo.toml b/crates/elf/Cargo.toml new file mode 100644 index 0000000..8b4be9a --- /dev/null +++ b/crates/elf/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "elf" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/crates/elf/src/lib.rs b/crates/elf/src/lib.rs new file mode 100644 index 0000000..d4fb188 --- /dev/null +++ b/crates/elf/src/lib.rs @@ -0,0 +1,167 @@ +#![no_std] + +#[cfg(test)] +mod tests; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ElfError { + TooSmall, + InvalidMagic, + Not64Bit, + NotLittleEndian, + NotExecutable, + NoProgramHeaders, + HeaderOutOfBounds, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Segment { + pub vaddr: usize, + pub file_offset: usize, + pub file_size: usize, + pub mem_size: usize, + pub flags: SegmentFlags, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SegmentFlags { + pub read: bool, + pub write: bool, + pub execute: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ElfInfo { + pub entry_point: usize, + pub segments: [Option; MAX_SEGMENTS], + pub segment_count: usize, +} + +pub const MAX_SEGMENTS: usize = 8; + +const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F']; +const ELFCLASS64: u8 = 2; +const ELFDATA2LSB: u8 = 1; +const ET_EXEC: u16 = 2; +const PT_LOAD: u32 = 1; + +const ELF_HEADER_SIZE: usize = 64; +const PHDR_SIZE: usize = 56; + +pub fn parse(data: &[u8]) -> Result { + if data.len() < ELF_HEADER_SIZE { + return Err(ElfError::TooSmall); + } + + if data[0..4] != ELF_MAGIC { + return Err(ElfError::InvalidMagic); + } + + if data[4] != ELFCLASS64 { + return Err(ElfError::Not64Bit); + } + + if data[5] != ELFDATA2LSB { + return Err(ElfError::NotLittleEndian); + } + + let e_type = read_u16(data, 16); + if e_type != ET_EXEC { + return Err(ElfError::NotExecutable); + } + + let entry_point = read_u64(data, 24) as usize; + let ph_offset = read_u64(data, 32) as usize; + let ph_entry_size = read_u16(data, 54) as usize; + let ph_count = read_u16(data, 56) as usize; + + if ph_count == 0 { + return Err(ElfError::NoProgramHeaders); + } + + let mut segments = [None; MAX_SEGMENTS]; + let mut segment_count = 0; + + for i in 0..ph_count { + let offset = ph_offset + i * ph_entry_size; + if offset + PHDR_SIZE > data.len() { + return Err(ElfError::HeaderOutOfBounds); + } + + let p_type = read_u32(data, offset); + if p_type != PT_LOAD { + continue; + } + + if segment_count >= MAX_SEGMENTS { + break; + } + + let p_flags = read_u32(data, offset + 4); + let p_offset = read_u64(data, offset + 8) as usize; + let p_vaddr = read_u64(data, offset + 16) as usize; + let p_filesz = read_u64(data, offset + 32) as usize; + let p_memsz = read_u64(data, offset + 40) as usize; + + segments[segment_count] = Some(Segment { + vaddr: p_vaddr, + file_offset: p_offset, + file_size: p_filesz, + mem_size: p_memsz, + flags: SegmentFlags { + execute: p_flags & 1 != 0, + write: p_flags & 2 != 0, + read: p_flags & 4 != 0, + }, + }); + segment_count += 1; + } + + Ok(ElfInfo { + entry_point, + segments, + segment_count, + }) +} + +impl ElfInfo { + pub fn segments(&self) -> impl Iterator { + self.segments[..self.segment_count] + .iter() + .filter_map(|s| s.as_ref()) + } + + pub fn total_pages(&self) -> usize { + let mut total = 0; + for seg in self.segments() { + total += (seg.mem_size + 4095) / 4096; + } + total + } +} + +fn read_u16(data: &[u8], offset: usize) -> u16 { + u16::from_le_bytes([data[offset], data[offset + 1]]) +} + +fn read_u32(data: &[u8], offset: usize) -> u32 { + u32::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + ]) +} + +fn read_u64(data: &[u8], offset: usize) -> u64 { + u64::from_le_bytes([ + data[offset], + data[offset + 1], + data[offset + 2], + data[offset + 3], + data[offset + 4], + data[offset + 5], + data[offset + 6], + data[offset + 7], + ]) +} diff --git a/crates/elf/src/tests.rs b/crates/elf/src/tests.rs new file mode 100644 index 0000000..9c7fd1d --- /dev/null +++ b/crates/elf/src/tests.rs @@ -0,0 +1,132 @@ +use super::*; + +fn minimal_elf64() -> [u8; 120] { + let mut buf = [0u8; 120]; + + // ELF magic + buf[0] = 0x7f; + buf[1] = b'E'; + buf[2] = b'L'; + buf[3] = b'F'; + + // EI_CLASS = ELFCLASS64 + buf[4] = 2; + // EI_DATA = ELFDATA2LSB + buf[5] = 1; + // EI_VERSION + buf[6] = 1; + + // e_type = ET_EXEC (2) + buf[16] = 2; + buf[17] = 0; + + // e_entry = 0x1000 + let entry: u64 = 0x1000; + buf[24..32].copy_from_slice(&entry.to_le_bytes()); + + // e_phoff = 64 (right after ELF header) + let phoff: u64 = 64; + buf[32..40].copy_from_slice(&phoff.to_le_bytes()); + + // e_phentsize = 56 + buf[54] = 56; + buf[55] = 0; + + // e_phnum = 1 + buf[56] = 1; + buf[57] = 0; + + // Program header at offset 64: + let ph = 64; + + // p_type = PT_LOAD (1) + buf[ph] = 1; + + // p_flags = PF_R | PF_X (4 | 1 = 5) + buf[ph + 4] = 5; + + // p_offset = 0 + let p_offset: u64 = 0; + buf[ph + 8..ph + 16].copy_from_slice(&p_offset.to_le_bytes()); + + // p_vaddr = 0x1000 + let p_vaddr: u64 = 0x1000; + buf[ph + 16..ph + 24].copy_from_slice(&p_vaddr.to_le_bytes()); + + // p_filesz = 120 + let p_filesz: u64 = 120; + buf[ph + 32..ph + 40].copy_from_slice(&p_filesz.to_le_bytes()); + + // p_memsz = 4096 + let p_memsz: u64 = 4096; + buf[ph + 40..ph + 48].copy_from_slice(&p_memsz.to_le_bytes()); + + buf +} + +#[test] +fn parse_valid_elf64() { + let data = minimal_elf64(); + let info = parse(&data).unwrap(); + + assert_eq!(info.entry_point, 0x1000); + assert_eq!(info.segment_count, 1); + + let seg = info.segments().next().unwrap(); + assert_eq!(seg.vaddr, 0x1000); + assert_eq!(seg.file_size, 120); + assert_eq!(seg.mem_size, 4096); + assert!(seg.flags.read); + assert!(seg.flags.execute); + assert!(!seg.flags.write); +} + +#[test] +fn parse_too_small_fails() { + let data = [0u8; 10]; + assert_eq!(parse(&data), Err(ElfError::TooSmall)); +} + +#[test] +fn parse_bad_magic_fails() { + let mut data = minimal_elf64(); + data[0] = 0; + assert_eq!(parse(&data), Err(ElfError::InvalidMagic)); +} + +#[test] +fn parse_32bit_fails() { + let mut data = minimal_elf64(); + data[4] = 1; // ELFCLASS32 + assert_eq!(parse(&data), Err(ElfError::Not64Bit)); +} + +#[test] +fn parse_big_endian_fails() { + let mut data = minimal_elf64(); + data[5] = 2; // ELFDATA2MSB + assert_eq!(parse(&data), Err(ElfError::NotLittleEndian)); +} + +#[test] +fn parse_shared_object_fails() { + let mut data = minimal_elf64(); + data[16] = 3; // ET_DYN + assert_eq!(parse(&data), Err(ElfError::NotExecutable)); +} + +#[test] +fn total_pages_rounds_up() { + let data = minimal_elf64(); + let info = parse(&data).unwrap(); + assert_eq!(info.total_pages(), 1); +} + +#[test] +fn skips_non_load_segments() { + let mut data = minimal_elf64(); + // Change p_type to PT_NOTE (4) + data[64] = 4; + let info = parse(&data).unwrap(); + assert_eq!(info.segment_count, 0); +}