use crate::Arch;
use crate::common::{RelocEntry, SectionKind, SymbolDesc};
use crate::dylib::data::DataMetaData;
use crate::dylib::dynamic::DynamicMetadata;
use crate::dylib::layout::ElfLayout;
use crate::dylib::reloc::RelocMetaData;
use crate::dylib::shdr::{SectionAllocator, ShdrManager};
use crate::dylib::symtab::SymTabMetadata;
use crate::dylib::text::CodeMetaData;
use crate::dylib::tls::TlsMetaData;
use anyhow::Result;
use byteorder::{LittleEndian, WriteBytesExt};
use object::elf::*;
use std::path::Path;
mod data;
mod dynamic;
mod layout;
pub(crate) mod reloc;
pub(crate) mod shdr;
pub(crate) mod symtab;
pub(crate) mod text;
mod tls;
fn align_up(val: u64, align: u64) -> u64 {
val.div_ceil(align) * align
}
pub(crate) struct StringTable {
data: Vec<u8>,
}
impl StringTable {
pub(crate) fn new() -> Self {
Self { data: vec![0u8] } }
pub(crate) fn add(&mut self, s: &str) -> u32 {
let idx = self.data.len() as u32;
self.data.extend_from_slice(s.as_bytes());
self.data.push(0);
idx
}
pub(crate) fn cur_idx(&self) -> u32 {
self.data.len() as u32
}
pub(crate) fn data(&self) -> &[u8] {
&self.data
}
}
#[derive(Clone)]
pub struct ElfWriterConfig {
pub base_addr: u64,
pub page_size: u64,
pub ifunc_resolver_val: Option<u64>,
}
impl Default for ElfWriterConfig {
fn default() -> Self {
Self {
base_addr: 0,
page_size: 0x1000,
ifunc_resolver_val: None,
}
}
}
impl ElfWriterConfig {
pub fn with_base_addr(mut self, addr: u64) -> Self {
self.base_addr = addr;
self
}
pub fn with_page_size(mut self, size: u64) -> Self {
self.page_size = size;
self
}
pub fn with_ifunc_resolver_val(mut self, val: u64) -> Self {
self.ifunc_resolver_val = Some(val);
self
}
}
#[derive(Clone, Debug)]
pub struct RelocationInfo {
pub vaddr: u64,
pub r_type: u32,
pub sym_idx: u64,
pub addend: i64,
pub sym_size: u64,
}
#[derive(Clone)]
pub struct ElfWriteOutput {
pub data: Vec<u8>,
pub base_addr: u64,
pub data_vaddr: u64,
pub text_vaddr: u64,
pub relocations: Vec<RelocationInfo>,
pub ifunc_resolver_val: u64,
}
const EHDR_SIZE_64: u64 = 64;
const EHDR_SIZE_32: u64 = 52;
const PHDR_SIZE_64: u64 = 56;
const PHDR_SIZE_32: u64 = 32;
const SHDR_SIZE_64: u64 = 64;
const SHDR_SIZE_32: u64 = 40;
const SYM_SIZE_64: u64 = 24;
const SYM_SIZE_32: u64 = 16;
const RELA_SIZE_64: u64 = 24;
const RELA_SIZE_32: u64 = 12;
const REL_SIZE_64: u64 = 16;
const REL_SIZE_32: u64 = 8;
const DYN_SIZE_64: u64 = 16;
const DYN_SIZE_32: u64 = 8;
const HASH_SIZE: u64 = 4;
pub struct DylibWriter {
arch: Arch,
config: ElfWriterConfig,
}
impl DylibWriter {
pub fn new(arch: Arch) -> Self {
let config = ElfWriterConfig::default();
Self { arch, config }
}
pub fn with_config(arch: Arch, config: ElfWriterConfig) -> Self {
Self { arch, config }
}
pub fn write_file(
&self,
out_path: &Path,
relocs: &[RelocEntry],
symbols: &[SymbolDesc],
) -> Result<ElfWriteOutput> {
let output = self.write(relocs, symbols)?;
std::fs::write(out_path, &output.data)?;
Ok(output)
}
pub fn write(
&self,
raw_relocs: &[RelocEntry],
symbols: &[SymbolDesc],
) -> Result<ElfWriteOutput> {
let is_64 = self.arch.is_64();
let final_relocs = RelocMetaData::preprocess(self.arch, raw_relocs);
let mut allocator = SectionAllocator::new();
let mut symtab = SymTabMetadata::new(self.arch, symbols, &final_relocs, &mut allocator);
let mut reloc = RelocMetaData::new(self.arch, &final_relocs, &symtab, &mut allocator)?;
let data = DataMetaData::new(&reloc, &symtab, &mut allocator);
let mut text = CodeMetaData::new(&symtab, &mut allocator);
let tls = TlsMetaData::new(&symtab, &mut allocator);
let mut sections = vec![];
text.create_sections(&mut sections);
data.create_sections(&mut sections);
tls.create_section(&mut sections);
symtab.create_sections(&mut sections);
reloc.create_sections(&mut sections)?;
let mut dyn_meta = DynamicMetadata::new(self.arch, §ions, &mut allocator);
dyn_meta.create_section(&mut sections);
let mut shdr_manager = ShdrManager::new();
for sec in sections {
shdr_manager.add_section(sec.header, sec.data);
}
shdr_manager.create_shstrtab_section(&mut allocator);
let mut layout = ElfLayout::new(&self.config);
let ehdr_size = if is_64 { EHDR_SIZE_64 } else { EHDR_SIZE_32 };
let ph_size =
(if is_64 { PHDR_SIZE_64 } else { PHDR_SIZE_32 }) * shdr_manager.get_phnum() as u64;
layout.add_header(ehdr_size, ph_size);
shdr_manager.layout(&mut layout);
let shdr_map = shdr_manager.get_shdr_map();
let plt_vaddr = shdr_manager.get_vaddr(SectionKind::Plt);
let text_vaddr = shdr_manager.get_vaddr(SectionKind::Text);
let data_vaddr = shdr_manager.get_vaddr(SectionKind::Data);
let got_plt_vaddr = shdr_manager.get_vaddr(SectionKind::GotPlt);
symtab.patch_symtab(plt_vaddr, text_vaddr, data_vaddr, &shdr_map, &mut allocator);
let resolver_val = self.config.ifunc_resolver_val.unwrap_or(plt_vaddr);
text.patch_text(
plt_vaddr,
text_vaddr,
got_plt_vaddr,
resolver_val,
&symtab,
&reloc,
&shdr_manager,
&mut allocator,
);
reloc.patch_all(&shdr_manager, &symtab, &mut allocator)?;
dyn_meta.patch_dynamic(&shdr_manager, &reloc, &mut allocator, got_plt_vaddr)?;
let mut out_bytes = vec![];
let ehdr = ElfHeader {
ident: self.get_ident(),
type_: ET_DYN,
machine: self.get_machine(),
version: EV_CURRENT as u32,
entry: 0,
phoff: ehdr_size,
shoff: layout.file_off,
flags: self.get_flags(),
ehsize: ehdr_size as u16,
phentsize: (if is_64 { PHDR_SIZE_64 } else { PHDR_SIZE_32 }) as u16,
phnum: shdr_manager.get_phnum(),
shentsize: (if is_64 { SHDR_SIZE_64 } else { SHDR_SIZE_32 }) as u16,
shnum: (shdr_manager.get_shdr_count() + 1) as u16,
shstrndx: *shdr_map.get(&SectionKind::ShStrTab).unwrap() as u16,
};
ehdr.write(&mut out_bytes, is_64)?;
shdr_manager.write_phdrs(&mut out_bytes, is_64, self.config.page_size)?;
for sec in shdr_manager.get_sections() {
self.write_at(&mut out_bytes, sec.header.offset, allocator.get(&sec.data));
}
let mut shdr_buf = vec![];
shdr_manager.write_shdrs(&mut shdr_buf, is_64)?;
self.write_at(&mut out_bytes, layout.file_off, &shdr_buf);
Ok(ElfWriteOutput {
data: out_bytes,
base_addr: self.config.base_addr,
data_vaddr,
text_vaddr,
relocations: reloc.get_relocation_infos(),
ifunc_resolver_val: resolver_val,
})
}
fn get_ident(&self) -> [u8; 16] {
let mut ident = [0u8; 16];
ident[0] = 0x7f;
ident[1] = b'E';
ident[2] = b'L';
ident[3] = b'F';
ident[4] = if self.arch.is_64() { 2 } else { 1 };
ident[5] = 1; ident[6] = 1; ident[7] = 0; ident
}
fn write_at(&self, buf: &mut Vec<u8>, offset: u64, data: &[u8]) {
let offset = offset as usize;
if buf.len() < offset + data.len() {
buf.resize(offset + data.len(), 0);
}
buf[offset..offset + data.len()].copy_from_slice(data);
}
fn get_flags(&self) -> u32 {
match self.arch {
Arch::Riscv64 | Arch::Riscv32 => {
0x0001 | 0x0004
}
Arch::Arm => {
0x05000000 | 0x00000400
}
_ => 0,
}
}
fn get_machine(&self) -> u16 {
match self.arch {
Arch::X86_64 => EM_X86_64,
Arch::X86 => EM_386,
Arch::Aarch64 => EM_AARCH64,
Arch::Riscv64 => EM_RISCV,
Arch::Riscv32 => EM_RISCV,
Arch::Arm => EM_ARM,
Arch::Loongarch64 => EM_LOONGARCH,
}
}
}
struct ElfHeader {
ident: [u8; 16],
type_: u16,
machine: u16,
version: u32,
entry: u64,
phoff: u64,
shoff: u64,
flags: u32,
ehsize: u16,
phentsize: u16,
phnum: u16,
shentsize: u16,
shnum: u16,
shstrndx: u16,
}
impl ElfHeader {
fn write(&self, buf: &mut Vec<u8>, is_64: bool) -> Result<()> {
buf.extend_from_slice(&self.ident);
buf.write_u16::<LittleEndian>(self.type_)?;
buf.write_u16::<LittleEndian>(self.machine)?;
buf.write_u32::<LittleEndian>(self.version)?;
if is_64 {
buf.write_u64::<LittleEndian>(self.entry)?;
buf.write_u64::<LittleEndian>(self.phoff)?;
buf.write_u64::<LittleEndian>(self.shoff)?;
} else {
buf.write_u32::<LittleEndian>(self.entry as u32)?;
buf.write_u32::<LittleEndian>(self.phoff as u32)?;
buf.write_u32::<LittleEndian>(self.shoff as u32)?;
}
buf.write_u32::<LittleEndian>(self.flags)?;
buf.write_u16::<LittleEndian>(self.ehsize)?;
buf.write_u16::<LittleEndian>(self.phentsize)?;
buf.write_u16::<LittleEndian>(self.phnum)?;
buf.write_u16::<LittleEndian>(self.shentsize)?;
buf.write_u16::<LittleEndian>(self.shnum)?;
buf.write_u16::<LittleEndian>(self.shstrndx)?;
Ok(())
}
}