feat(core): implement ELF64 header and program segment parser

This commit is contained in:
katR 2026-09-29 17:03:17 +05:30
parent 58618eb3c0
commit 2d0cf8c478
3 changed files with 136 additions and 0 deletions

View file

@ -0,0 +1,107 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ElfError {
#[error("File is smaller than minimum ELF64 header (64 bytes)")]
TooSmall,
#[error("Invalid ELF magic header: expected 0x7F 'ELF'")]
InvalidMagic,
#[error("Unsupported ELF class: expected 64-bit")]
UnsupportedClass,
#[error("Unsupported endianness: expected Little Endian")]
UnsupportedEndianness,
#[error("Unsupported architecture: expected x86_64 (62), found {0}")]
UnsupportedMachine(u16),
#[error("Invalid ELF type: expected ET_DYN (3), found {0}")]
UnsupportedType(u16),
}
#[derive(Debug, Clone)]
pub struct ProgramHeader {
pub p_type: u32,
pub p_flags: u32,
pub p_offset: u64,
pub p_vaddr: u64,
pub p_paddr: u64,
pub p_filesz: u64,
pub p_memsz: u64,
pub p_align: u64,
}
pub const PT_LOAD: u32 = 1;
pub const PT_DYNAMIC: u32 = 2;
pub const PT_TLS: u32 = 7;
#[derive(Debug, Clone)]
pub struct ElfBinary<'a> {
pub raw: &'a [u8],
pub entry_point: u64,
pub program_headers: Vec<ProgramHeader>,
}
impl<'a> ElfBinary<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, ElfError> {
if bytes.len() < 64 {
return Err(ElfError::TooSmall);
}
if &bytes[0..4] != b"\x7fELF" {
return Err(ElfError::InvalidMagic);
}
if bytes[4] != 2 {
return Err(ElfError::UnsupportedClass);
}
if bytes[5] != 1 {
return Err(ElfError::UnsupportedEndianness);
}
let e_type = u16::from_le_bytes([bytes[16], bytes[17]]);
if e_type != 3 {
return Err(ElfError::UnsupportedType(e_type));
}
let e_machine = u16::from_le_bytes([bytes[18], bytes[19]]);
if e_machine != 62 {
return Err(ElfError::UnsupportedMachine(e_machine));
}
let entry_point = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
let phoff = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
let phentsize = u16::from_le_bytes([bytes[54], bytes[55]]) as usize;
let phnum = u16::from_le_bytes([bytes[56], bytes[57]]) as usize;
let mut program_headers = Vec::with_capacity(phnum);
if phnum > 0 && phentsize >= 56 {
for i in 0..phnum {
let offset = phoff + i * phentsize;
if offset + 56 <= bytes.len() {
let ph = ProgramHeader {
p_type: u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()),
p_flags: u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()),
p_offset: u64::from_le_bytes(bytes[offset + 8..offset + 16].try_into().unwrap()),
p_vaddr: u64::from_le_bytes(bytes[offset + 16..offset + 24].try_into().unwrap()),
p_paddr: u64::from_le_bytes(bytes[offset + 24..offset + 32].try_into().unwrap()),
p_filesz: u64::from_le_bytes(bytes[offset + 32..offset + 40].try_into().unwrap()),
p_memsz: u64::from_le_bytes(bytes[offset + 40..offset + 48].try_into().unwrap()),
p_align: u64::from_le_bytes(bytes[offset + 48..offset + 56].try_into().unwrap()),
};
program_headers.push(ph);
}
}
}
Ok(Self {
raw: bytes,
entry_point,
program_headers,
})
}
pub fn load_segments(&self) -> impl Iterator<Item = &ProgramHeader> {
self.program_headers.iter().filter(|ph| ph.p_type == PT_LOAD)
}
}

View file

@ -1,2 +1,3 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pub mod elf;
pub mod symbols;

View file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use molotov_core::elf::ElfBinary;
#[test]
fn test_rejects_non_elf_magic() {
let dummy = b"NOT_AN_ELF_BINARY";
let res = ElfBinary::parse(dummy);
assert!(res.is_err(), "Must reject files without 0x7F 'E' 'L' 'F' magic header");
}
#[test]
fn test_parses_valid_minimal_elf64() {
let mut header = vec![0u8; 64];
// e_ident
header[0..4].copy_from_slice(b"\x7fELF");
header[4] = 2; // ELFCLASS64
header[5] = 1; // ELFDATA2LSB (little endian)
header[6] = 1; // EV_CURRENT
// e_type = ET_DYN (3)
header[16] = 3;
// e_machine = EM_X86_64 (62)
header[18] = 62;
// e_phentsize = 56
header[54] = 56;
let res = ElfBinary::parse(&header);
assert!(res.is_ok(), "Valid minimal ELF64 dynamic library header must be accepted");
}