diff --git a/a.out b/a.out new file mode 100755 index 0000000..b4f462c Binary files /dev/null and b/a.out differ diff --git a/elven-forest/src/main.rs b/elven-forest/src/main.rs index 3c9e4c9..ad97e88 100644 --- a/elven-forest/src/main.rs +++ b/elven-forest/src/main.rs @@ -3,8 +3,7 @@ use std::{fmt::Display, fs::File}; use anyhow::Context; use elven_parser::{ consts::{self as c, DynamicTag, ShType, SymbolVisibility, RX86_64}, - defs::{Addr, Elf, Sym, SymInfo}, - ElfParseError, + read::{Addr, ElfReadError, ElfReader, Sym, SymInfo}, }; use memmap2::Mmap; use tabled::{object::Rows, Disable, Style, Table, Tabled}; @@ -63,7 +62,7 @@ fn print_file(path: &str) -> anyhow::Result<()> { let file = File::open(path)?; let mmap = unsafe { Mmap::map(&file) }?; - let elf = Elf::new(&mmap)?; + let elf = ElfReader::new(&mmap)?; println!("\nHeader"); @@ -98,7 +97,7 @@ fn print_file(path: &str) -> anyhow::Result<()> { offset: sh.offset.0, }) }) - .collect::, ElfParseError>>()?; + .collect::, ElfReadError>>()?; print_table(Table::new(sections)); @@ -125,7 +124,7 @@ fn print_file(path: &str) -> anyhow::Result<()> { value: sym.value, }) }) - .collect::, ElfParseError>>()?; + .collect::, ElfReadError>>()?; print_table(Table::new(symbols)); @@ -152,7 +151,7 @@ fn print_file(path: &str) -> anyhow::Result<()> { addend, }) }) - .collect::, ElfParseError>>()?; + .collect::, ElfReadError>>()?; print_table(Table::new(relas)); @@ -171,7 +170,7 @@ fn print_file(path: &str) -> anyhow::Result<()> { Ok(()) } -fn sym_display_name(elf: Elf<'_>, sym: &Sym) -> Result { +fn sym_display_name(elf: ElfReader<'_>, sym: &Sym) -> Result { Ok(if sym.info.r#type() == c::STT_SECTION { elf.sh_string(elf.section_header(sym.shndx)?.name)? .to_string() diff --git a/elven-parser/src/consts.rs b/elven-parser/src/consts.rs index 02a7743..bf415eb 100644 --- a/elven-parser/src/consts.rs +++ b/elven-parser/src/consts.rs @@ -124,14 +124,18 @@ pub const EI_PAD: usize = 9; /* Byte index of padding bytes */ pub const EI_NIDENT: usize = 16; -pub const ET_NONE: u16 = 0; -pub const ET_REL: u16 = 1; -pub const ET_EXEC: u16 = 2; -pub const ET_DYN: u16 = 3; -pub const ET_CORE: u16 = 4; +const_group_with_fmt! { + pub struct Type(u16): "type" + + pub const ET_NONE = 0; + pub const ET_REL = 1; + pub const ET_EXEC = 2; + pub const ET_DYN = 3; + pub const ET_CORE = 4; +} const_group_with_fmt! { - pub struct Machine(u16): "Machine" + pub struct Machine(u16): "machine" pub const EM_NONE = 0; /* No machine */ pub const EM_X86_64 = 62; /* AMD x86-64 architecture */ diff --git a/elven-parser/src/idx.rs b/elven-parser/src/idx.rs index 27b50eb..d7911df 100644 --- a/elven-parser/src/idx.rs +++ b/elven-parser/src/idx.rs @@ -3,7 +3,7 @@ use std::{ slice::SliceIndex, }; -use crate::{ElfParseError, Result}; +use crate::read::{ElfReadError, Result}; macro_rules! define_idx { ( @@ -95,6 +95,6 @@ impl ElfIndexExt for [T] { ) -> Result<&>::Output> { let bound = idx.bound(); self.get(idx.to_slice_idx()) - .ok_or(ElfParseError::IndexOutOfBounds(msg, bound)) + .ok_or(ElfReadError::IndexOutOfBounds(msg, bound)) } } diff --git a/elven-parser/src/lib.rs b/elven-parser/src/lib.rs index 819009c..7cbf244 100644 --- a/elven-parser/src/lib.rs +++ b/elven-parser/src/lib.rs @@ -1,35 +1,6 @@ #![allow(clippy::must_use_candidate, clippy::missing_errors_doc)] -use consts::{DynamicTag, ShType}; - pub mod consts; -pub mod defs; mod idx; - -#[derive(Debug, Clone, thiserror::Error)] -pub enum ElfParseError { - #[error("The file is too small. Expected at least {0} bytes, found {1} bytes")] - FileTooSmall(usize, usize), - #[error("The input is not aligned in memory. Expected align {0}, found align {1}")] - UnalignedInput(usize, usize), - #[error("The magic of the file did not match. Maybe it's not an ELF file?. Found {0:x?}")] - WrongMagic([u8; 4]), - #[error("A program header entry has a different size than expected. Expected {0}, found {1}")] - InvalidPhEntSize(usize, usize), - #[error("A section header entry has a different size than expected. Expected {0}, found {1}")] - InvalidShEntSize(usize, usize), - #[error("The string table section is marked as UNDEF")] - StrTableSectionNotPresent, - #[error("An index is out of bounds: {0}: {1}")] - IndexOutOfBounds(&'static str, usize), - #[error("String in string table does not end with a nul terminator: String offset: {0}")] - NoStringNulTerm(usize), - #[error("The {0} section was not found")] - SectionTypeNotFound(ShType), - #[error("The section with the name {0:?} was not found")] - SectionNotFound(std::result::Result>), - #[error("Dynamic entry not found: {0}")] - DynEntryNotFound(DynamicTag), -} - -pub type Result = std::result::Result; +pub mod read; +pub mod write; diff --git a/elven-parser/src/defs.rs b/elven-parser/src/read.rs similarity index 76% rename from elven-parser/src/defs.rs rename to elven-parser/src/read.rs index 14cbc9f..3a14a04 100644 --- a/elven-parser/src/defs.rs +++ b/elven-parser/src/read.rs @@ -5,7 +5,6 @@ use crate::{ consts::{self as c, DynamicTag, ShType}, idx::{define_idx, ElfIndexExt, ToIdxUsize}, - ElfParseError, Result, }; use bstr::BStr; @@ -17,9 +16,36 @@ use std::{ use bytemuck::{Pod, PodCastError, Zeroable}; +#[derive(Debug, Clone, thiserror::Error)] +pub enum ElfReadError { + #[error("An index into {2} is out of bounds. Expected at least {0} bytes, found {1} bytes")] + RegionOutOfBounds(usize, usize, String), + #[error("The input is not aligned in memory. Expected align {0}, found align {1}")] + UnalignedInput(usize, usize), + #[error("The magic of the file did not match. Maybe it's not an ELF file?. Found {0:x?}")] + WrongMagic([u8; 4]), + #[error("A program header entry has a different size than expected. Expected {0}, found {1}")] + InvalidPhEntSize(usize, usize), + #[error("A section header entry has a different size than expected. Expected {0}, found {1}")] + InvalidShEntSize(usize, usize), + #[error("The string table section is marked as UNDEF")] + StrTableSectionNotPresent, + #[error("An index is out of bounds: {0}: {1}")] + IndexOutOfBounds(&'static str, usize), + #[error("String in string table does not end with a nul terminator: String offset: {0}")] + NoStringNulTerm(usize), + #[error("The {0} section was not found")] + SectionTypeNotFound(ShType), + #[error("The {0} with the name {1:?} was not found")] + NotFoundByName(&'static str, std::result::Result>), + #[error("Dynamic entry not found: {0}")] + DynEntryNotFound(DynamicTag), +} + +pub type Result = std::result::Result; + #[derive(Clone, Copy, PartialEq, Eq, Hash, Zeroable, Pod)] #[repr(transparent)] - pub struct Addr(pub u64); impl Debug for Addr { @@ -58,7 +84,7 @@ define_idx! { /// A raw ELF. Does not come with cute ears for now. #[derive(Debug, Clone, Copy)] -pub struct Elf<'a> { +pub struct ElfReader<'a> { pub data: &'a [u8], } @@ -66,7 +92,7 @@ pub struct Elf<'a> { #[repr(C)] pub struct ElfHeader { pub ident: ElfIdent, - pub r#type: u16, + pub r#type: c::Type, pub machine: c::Machine, pub version: u32, pub entry: Addr, @@ -81,6 +107,16 @@ pub struct ElfHeader { pub shstrndex: c::SectionIdx, } +pub(crate) const HEADER_ENTRY_OFFSET: usize = 24; + +#[test] +fn elf_header_entry_offset() { + let mut header = ElfHeader::zeroed(); + let buf = bytemuck::cast_mut::() / 8]>(&mut header); + buf[HEADER_ENTRY_OFFSET / 8] = 0x001122334455667788; + assert_eq!(header.entry, Addr(0x001122334455667788)); +} + #[derive(Debug, Clone, Copy, Zeroable, Pod)] #[repr(C)] pub struct ElfIdent { @@ -202,25 +238,25 @@ pub struct Dyn { pub val: u64, } -impl<'a> Elf<'a> { +impl<'a> ElfReader<'a> { pub fn new(data: &'a [u8]) -> Result { let magic = data[..c::SELFMAG].try_into().map_err(|_| { let mut padded = [0, 0, 0, 0]; padded.copy_from_slice(data); - ElfParseError::WrongMagic(padded) + ElfReadError::WrongMagic(padded) })?; if magic != *c::ELFMAG { - return Err(ElfParseError::WrongMagic(magic)); + return Err(ElfReadError::WrongMagic(magic)); } - let elf = Elf { data }; + let elf = ElfReader { data }; Ok(elf) } pub fn header(&self) -> Result<&ElfHeader> { - load_ref(self.data) + load_ref(self.data, "header") } pub fn program_headers(&self) -> Result<&[Phdr]> { @@ -233,7 +269,7 @@ impl<'a> Elf<'a> { let expected_ent_size = mem::size_of::(); let actual_ent_size = usize::from(header.phentsize); if actual_ent_size != expected_ent_size { - return Err(ElfParseError::InvalidPhEntSize( + return Err(ElfReadError::InvalidPhEntSize( expected_ent_size, actual_ent_size, )); @@ -242,6 +278,7 @@ impl<'a> Elf<'a> { load_slice( self.data.get_elf(header.phoff.., "program header offset")?, header.phnum.into(), + "program headers", ) } @@ -255,14 +292,15 @@ impl<'a> Elf<'a> { let expected_ent_size = mem::size_of::(); let actual_ent_size = usize::from(header.shentsize); if actual_ent_size != expected_ent_size { - return Err(ElfParseError::InvalidPhEntSize( + return Err(ElfReadError::InvalidPhEntSize( expected_ent_size, actual_ent_size, )); } load_slice( - self.data.get_elf(header.shoff.., "sectoin header offset")?, + self.data.get_elf(header.shoff.., "section header offset")?, header.shnum.into(), + "section headers", ) } @@ -279,7 +317,8 @@ impl<'a> Elf<'a> { } } let name = name.to_vec(); - Err(ElfParseError::SectionNotFound( + Err(ElfReadError::NotFoundByName( + "section", string::String::from_utf8(name).map_err(FromUtf8Error::into_bytes), )) } @@ -288,7 +327,7 @@ impl<'a> Elf<'a> { self.section_headers()? .iter() .find(|sh| sh.r#type == ty) - .ok_or(ElfParseError::SectionTypeNotFound(ShType(ty))) + .ok_or(ElfReadError::SectionTypeNotFound(ShType(ty))) } pub fn section_content(&self, sh: &Shdr) -> Result<&[u8]> { @@ -306,7 +345,7 @@ impl<'a> Elf<'a> { let shstrndex = header.shstrndex; if shstrndex == c::SHN_UNDEF { - return Err(ElfParseError::StrTableSectionNotPresent); + return Err(ElfReadError::StrTableSectionNotPresent); } if shstrndex >= c::SHN_LORESERVE { @@ -334,7 +373,7 @@ impl<'a> Elf<'a> { let end = indexed .iter() .position(|&c| c == b'\0') - .ok_or(ElfParseError::NoStringNulTerm(idx.to_idx_usize()))?; + .ok_or(ElfReadError::NoStringNulTerm(idx.to_idx_usize()))?; Ok(BStr::new(&indexed[..end])) } @@ -344,7 +383,7 @@ impl<'a> Elf<'a> { let end = indexed .iter() .position(|&c| c == b'\0') - .ok_or(ElfParseError::NoStringNulTerm(idx.to_idx_usize()))?; + .ok_or(ElfReadError::NoStringNulTerm(idx.to_idx_usize()))?; Ok(BStr::new(&indexed[..end])) } @@ -361,7 +400,7 @@ impl<'a> Elf<'a> { let end = indexed .iter() .position(|&c| c == b'\0') - .ok_or(ElfParseError::NoStringNulTerm(idx.to_idx_usize()))?; + .ok_or(ElfReadError::NoStringNulTerm(idx.to_idx_usize()))?; Ok(BStr::new(&indexed[..end])) } @@ -372,7 +411,11 @@ impl<'a> Elf<'a> { .filter(|sh| sh.r#type == c::SHT_RELA) .map(|sh| { let content = self.section_content(sh)?; - let relas = load_slice::(content, content.len() / mem::size_of::())?; + let relas = load_slice::( + content, + content.len() / mem::size_of::(), + "relocations", + )?; Ok((sh, relas)) }) .collect::>>()? @@ -385,13 +428,27 @@ impl<'a> Elf<'a> { let data = self.section_content(sh)?; - load_slice(data, data.len() / mem::size_of::()) + load_slice(data, data.len() / mem::size_of::(), "symbols") } pub fn symbol(&self, idx: SymIdx) -> Result<&Sym> { self.symbols()?.get_elf(idx, "symbol index") } + pub fn symbol_by_name(&self, name: &[u8]) -> Result<&Sym> { + for symbol in self.symbols()? { + let sym_name = self.string(symbol.name)?; + if sym_name == name { + return Ok(symbol); + } + } + + Err(ElfReadError::NotFoundByName( + "symbol", + string::String::from_utf8(name.to_vec()).map_err(FromUtf8Error::into_bytes), + )) + } + pub fn dyn_symbols(&self) -> Result<&[Sym]> { let addr = self.dyn_entry_by_tag(c::DT_SYMTAB)?; let size = self.dyn_entry_by_tag(c::DT_SYMENT)?; @@ -400,7 +457,7 @@ impl<'a> Elf<'a> { let data = self.dyn_content(addr.val, size.val)?; - load_slice(data, data.len() / mem::size_of::()) + load_slice(data, data.len() / mem::size_of::(), "dyn symbols") } pub fn dyn_symbol(&self, idx: SymIdx) -> Result<&Sym> { @@ -411,14 +468,14 @@ impl<'a> Elf<'a> { let sh = self.section_header_by_name(b".dynamic")?; let data = self.section_content(sh)?; - load_slice(data, data.len() / mem::size_of::()) + load_slice(data, data.len() / mem::size_of::(), "dyn entries") } pub fn dyn_entry_by_tag(&self, tag: u64) -> Result<&Dyn> { self.dyn_entries()? .iter() .find(|dy| dy.tag == tag) - .ok_or(ElfParseError::DynEntryNotFound(DynamicTag(tag))) + .ok_or(ElfReadError::DynEntryNotFound(DynamicTag(tag))) } pub fn dyn_content(&self, addr: u64, size: u64) -> Result<&[u8]> { @@ -428,16 +485,24 @@ impl<'a> Elf<'a> { } } -fn load_ref(data: &[u8]) -> Result<&T> { - load_slice(data, 1).map(|slice| &slice[0]) +fn load_ref<'a, T: Pod>(data: &'a [u8], kind: impl Into) -> Result<&'a T> { + load_slice(data, 1, kind).map(|slice| &slice[0]) } -fn load_slice(data: &[u8], amount_of_elems: usize) -> Result<&[T]> { +fn load_slice<'a, T: Pod>( + data: &'a [u8], + amount_of_elems: usize, + kind: impl Into, +) -> Result<&'a [T]> { let size = mem::size_of::() * amount_of_elems; let align = mem::align_of::(); if data.len() < size { - return Err(ElfParseError::FileTooSmall(size, data.len())); + return Err(ElfReadError::RegionOutOfBounds( + size, + data.len(), + kind.into(), + )); } let data_addr = (data as *const [u8]).cast::() as usize; @@ -452,7 +517,7 @@ fn load_slice(data: &[u8], amount_of_elems: usize) -> Result<&[T]> { unreachable!("already checked for these errors: {e}") } PodCastError::TargetAlignmentGreaterAndInputNotAligned => { - ElfParseError::UnalignedInput(align, data_align) + ElfReadError::UnalignedInput(align, data_align) } }) } @@ -485,7 +550,7 @@ mod tests { #[test] fn rust_hello_world_bin() -> super::Result<()> { let file = load_test_file("hello_world"); - let elf = Elf::new(&file)?; + let elf = ElfReader::new(&file)?; let header = elf.header()?; assert_eq!(header.ident.class, c::ELFCLASS64); @@ -507,8 +572,8 @@ mod tests { #[test] fn c_hello_world_object() -> super::Result<()> { - let file = load_test_file("hello_world_obj"); - let elf = Elf::new(&file)?; + let file = load_test_file("hello_world_obj.o"); + let elf = ElfReader::new(&file)?; let header = elf.header()?; assert_eq!(header.ident.class, c::ELFCLASS64); diff --git a/elven-parser/src/write.rs b/elven-parser/src/write.rs new file mode 100644 index 0000000..82263ad --- /dev/null +++ b/elven-parser/src/write.rs @@ -0,0 +1,256 @@ +use crate::consts::{Machine, SectionIdx, ShType, Type, SHT_NULL, SHT_STRTAB}; +use crate::read::{self, Addr, ElfIdent, Offset, ShStringIdx}; +use std::io; +use std::mem::size_of; +use std::num::NonZeroU64; + +#[derive(Debug, thiserror::Error)] +pub enum WriteElfError { + #[error("Too many {0}")] + TooMany(&'static str), + #[error("Writer IO error")] + Io(#[from] io::Error), +} + +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub struct ElfWriter { + header: read::ElfHeader, + entry: Entry, + sections_headers: Vec
, + programs_headers: Vec, +} + +#[derive(Debug, Clone)] +pub struct Header { + pub ident: ElfIdent, + pub r#type: Type, + pub machine: Machine, +} + +#[derive(Debug, Clone)] +pub struct Entry { + pub section: SectionIdx, + pub rel_offset: Offset, +} + +#[derive(Debug, Clone)] +pub struct Section { + pub name: read::ShStringIdx, + pub r#type: ShType, + pub flags: u64, + pub fixed_entsize: Option, + pub content: Vec, +} + +#[derive(Debug, Clone)] +pub struct ProgramHeader { + pub content: Vec, +} + +const SH_STRTAB: usize = 1; + +impl ElfWriter { + pub fn new(header: Header) -> Self { + let header = read::ElfHeader { + ident: header.ident, + r#type: header.r#type, + machine: header.machine, + version: 1, + entry: Addr(u64::MAX), + phoff: Offset(0), + shoff: Offset(0), + flags: u32::MAX, + ehsize: size_of:: as u16, + phentsize: size_of::() as u16, + phnum: u16::MAX, + shentsize: size_of::() as u16, + shnum: u16::MAX, + // Set below. + shstrndex: SectionIdx(SH_STRTAB as u16), + }; + + let null_section = Section { + // The null string. + name: read::ShStringIdx(0), + r#type: ShType(SHT_NULL), + flags: 0, + content: Vec::new(), + fixed_entsize: None, + }; + + let shstrtab = Section { + // The first string which happens to be .shstrtab below. + name: read::ShStringIdx(1), + r#type: ShType(SHT_STRTAB), + flags: 0, + // Set up the null string and also the .shstrtab, our section. + content: b"\0.shstrtab\0".to_vec(), + fixed_entsize: None, + }; + + Self { + header, + entry: Entry { + section: SectionIdx(0), + rel_offset: Offset(0), + }, + sections_headers: vec![null_section, shstrtab], + programs_headers: Vec::new(), + } + } + + pub fn set_entry(&mut self, entry: Entry) { + self.entry = entry; + } + + pub fn add_sh_string(&mut self, content: &[u8]) -> ShStringIdx { + let shstrtab = &mut self.sections_headers[SH_STRTAB]; + let idx = shstrtab.content.len(); + shstrtab.content.extend(content); + shstrtab.content.push(0); + ShStringIdx(idx as u32) + } + + pub fn add_section(&mut self, section: Section) { + self.sections_headers.push(section); + } +} + +mod writing { + use bytemuck::Pod; + + use crate::read::{Addr, ElfHeader, Offset, Shdr, HEADER_ENTRY_OFFSET}; + use std::{io::Write, mem::size_of, num::NonZeroU64}; + + use super::{ElfWriter, Result, WriteElfError}; + + impl ElfWriter { + pub fn write(&self) -> Result> { + let mut output = Vec::new(); + + let mut current_known_position = 0; + + let mut header = self.header; + + header.shnum = self + .sections_headers + .len() + .try_into() + .map_err(|_| WriteElfError::TooMany("sections"))?; + + header.phnum = self + .programs_headers + .len() + .try_into() + .map_err(|_| WriteElfError::TooMany("program headers"))?; + + // We know the size of the header. + current_known_position += size_of::() as u64; + + // We put the section headers directly after the header. + if !self.sections_headers.is_empty() { + header.shoff = Offset(current_known_position); + } + + // There will be all the section headers right after the header. + current_known_position += (header.shentsize * header.shnum) as u64; + + // We put the program headers directly after the section headers. + if !self.programs_headers.is_empty() { + header.phoff = Offset(current_known_position); + } + + // There will be all the program headers right after the section headers. + current_known_position += (header.phentsize * header.phnum) as u64; + + write_pod(&header, &mut output); + + for (sh_idx, section) in self.sections_headers.iter().enumerate() { + let header = Shdr { + name: section.name, + r#type: section.r#type, + flags: section.flags, + addr: Addr(0), + offset: Offset(current_known_position), + size: section.content.len() as u64, + link: 0, + info: 0, + addralign: 0, + entsize: section.fixed_entsize.map(NonZeroU64::get).unwrap_or(0), + }; + + if sh_idx == self.entry.section.0 as usize { + let base = current_known_position; + let entry = base + self.entry.rel_offset.0; + let entry_pos = &mut output[HEADER_ENTRY_OFFSET..][..size_of::()]; + let entry_ref = bytemuck::cast_slice_mut::(entry_pos); + entry_ref[0] = entry; + } + + // We will write the content for this section at that offset and also make sure to align the next one. + // FIXME: Align to the alignment of the next section. + current_known_position += align_up(section.content.len() as u64, 8); + + write_pod(&header, &mut output); + } + + assert_eq!(self.programs_headers.len(), 0); // FIXME: yeah + + for section in &self.sections_headers { + let section_size = section.content.len() as u64; + let aligned_size = align_up(section_size, 8); + let padding = aligned_size - section_size; + + output.write_all(§ion.content)?; + for _ in 0..padding { + output.write_all(&[0u8])?; + } + } + + Ok(output) + } + } + + fn write_pod(data: &T, output: &mut Vec) { + let data = std::slice::from_ref(data); + write_pod_slice(data, output); + } + + fn write_pod_slice(data: &[T], output: &mut Vec) { + let data = bytemuck::cast_slice::(data); + output.extend(data); + } + + fn align_up(n: u64, align: u64) -> u64 { + // n=0b0101, align=0b0100 + let required_mask = align - 1; // 0b0011 + let masked = n & required_mask; // 0b0001 + + if masked == 0 { + return n; + } + + let next_down = n - masked; // 0b0100 + next_down + align // 0b0110 + } + + #[cfg(test)] + mod tests { + use super::align_up; + + #[test] + fn align_up_correct() { + assert_eq!(align_up(0b0101, 0b0010), 0b0110); + assert_eq!(align_up(16, 8), 16); + assert_eq!(align_up(15, 8), 16); + assert_eq!(align_up(14, 8), 16); + assert_eq!(align_up(11, 8), 16); + assert_eq!(align_up(10, 8), 16); + assert_eq!(align_up(9, 8), 16); + assert_eq!(align_up(8, 8), 8); + assert_eq!(align_up(0, 1), 0); + } + } +} diff --git a/elven-wald/src/lib.rs b/elven-wald/src/lib.rs index 050ebdf..0fb4df2 100644 --- a/elven-wald/src/lib.rs +++ b/elven-wald/src/lib.rs @@ -3,9 +3,17 @@ extern crate tracing; use anyhow::{bail, Context, Result}; use clap::Parser; -use elven_parser::defs::Elf; +use elven_parser::{ + consts::{self as c, ShType, SHT_PROGBITS}, + read::{ElfIdent, ElfReader, Offset}, + write::{self, ElfWriter, Entry, Section}, +}; use memmap2::Mmap; -use std::{fs::File, path::PathBuf}; +use std::{ + fs, + io::{BufWriter, Write}, + path::PathBuf, +}; #[derive(Debug, Clone, Parser)] pub struct Opts { @@ -17,7 +25,8 @@ pub fn run(opts: Opts) -> Result<()> { .objs .iter() .map(|path| { - let file = File::open(path).with_context(|| format!("opening {}", path.display()))?; + let file = + fs::File::open(path).with_context(|| format!("opening {}", path.display()))?; unsafe { Mmap::map(&file).with_context(|| format!("memory mapping {}", path.display())) } @@ -38,11 +47,73 @@ pub fn run(opts: Opts) -> Result<()> { .iter() .zip(&opts.objs) .map(|(mmap, path)| { - Elf::new(mmap).with_context(|| format!("parsing ELF file {}", path.display())) + ElfReader::new(mmap).with_context(|| format!("parsing ELF file {}", path.display())) }) .collect::, anyhow::Error>>()?; - let main_elf = elfs[0]; + let elf = elfs[0]; + + let text_sh = elf.section_header_by_name(b".text")?; + let text_content = elf.section_content(text_sh)?; + + let _start_sym = elf.symbol_by_name(b"_start")?; + + let section = _start_sym.shndx; + + let entry = Entry { + section, + rel_offset: Offset(_start_sym.value.0), + }; + + write_output(text_content, entry)?; + + Ok(()) +} + +fn write_output(text: &[u8], entry: Entry) -> Result<()> { + let ident = ElfIdent { + magic: *c::ELFMAG, + class: c::Class(c::ELFCLASS64), + data: c::Data(c::ELFDATA2LSB), + version: 1, + osabi: c::OsAbi(c::ELFOSABI_SYSV), + abiversion: 0, + _pad: [0; 7], + }; + + let header = write::Header { + ident, + r#type: c::Type(c::ET_DYN), + machine: c::Machine(c::EM_X86_64), + }; + + let mut write = ElfWriter::new(header); + + let text_name = write.add_sh_string(b".text"); + write.add_section(Section { + name: text_name, + r#type: ShType(SHT_PROGBITS), + flags: 0, + fixed_entsize: None, + content: text.to_vec(), + }); + + write.set_entry(entry); + + let output = write.write().context("writing output file")?; + + let mut output_file = fs::File::create("a.out").context("creating ./a.out")?; + BufWriter::new(&mut output_file).write_all(&output)?; + + #[allow(unused_mut)] + let mut permissions = output_file.metadata()?.permissions(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mode = permissions.mode(); + permissions.set_mode(mode | 0o111); + }; + output_file.set_permissions(permissions)?; Ok(()) }