feat(elf): add no_std ELF64 parser for loading process binaries
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
members = [
|
members = [
|
||||||
"crates/apps/snake",
|
"crates/apps/snake",
|
||||||
"crates/domain",
|
"crates/domain",
|
||||||
|
"crates/elf",
|
||||||
"crates/kernel",
|
"crates/kernel",
|
||||||
"crates/platform",
|
"crates/platform",
|
||||||
"crates/server-framework",
|
"crates/server-framework",
|
||||||
@@ -41,6 +42,7 @@ resolver = "2"
|
|||||||
# kernel
|
# kernel
|
||||||
types = { path = "crates/types" }
|
types = { path = "crates/types" }
|
||||||
domain = { path = "crates/domain" }
|
domain = { path = "crates/domain" }
|
||||||
|
elf = { path = "crates/elf" }
|
||||||
platform = { path = "crates/platform" }
|
platform = { path = "crates/platform" }
|
||||||
kernel = { path = "crates/kernel" }
|
kernel = { path = "crates/kernel" }
|
||||||
server-framework = { path = "crates/server-framework" }
|
server-framework = { path = "crates/server-framework" }
|
||||||
@@ -77,6 +79,7 @@ user-manager = { path = "crates/servers/user-manager" }
|
|||||||
# apps
|
# apps
|
||||||
snake = { path = "crates/apps/snake" }
|
snake = { path = "crates/apps/snake" }
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
6
crates/elf/Cargo.toml
Normal file
6
crates/elf/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "elf"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
167
crates/elf/src/lib.rs
Normal file
167
crates/elf/src/lib.rs
Normal file
@@ -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<Segment>; 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<ElfInfo, ElfError> {
|
||||||
|
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<Item = &Segment> {
|
||||||
|
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],
|
||||||
|
])
|
||||||
|
}
|
||||||
132
crates/elf/src/tests.rs
Normal file
132
crates/elf/src/tests.rs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user