use object::write::{Object as Writer, SymbolId};
use object::{SectionFlags, SectionKind, SymbolFlags, elf};
use crate::section::{Array, Binding, Property, Reference, Tls, Visibility};
pub(crate) fn r_type(reference: Reference) -> Option<elf::RelocationType> {
Some(match reference {
Reference::Call => elf::R_X86_64_PLT32,
Reference::Data | Reference::Away => elf::R_X86_64_PC32,
Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
Reference::GotBare => elf::R_X86_64_GOTPCRELX,
Reference::GotKept => elf::R_X86_64_GOTPCREL,
Reference::Thread => elf::R_X86_64_GOTTPOFF,
Reference::AwayWide => elf::R_X86_64_PC64,
Reference::Address { bytes: 8 } => elf::R_X86_64_64,
Reference::Address { bytes: 4 } => elf::R_X86_64_32,
Reference::Address { bytes: 2 } => elf::R_X86_64_16,
Reference::Address { bytes: 1 } => elf::R_X86_64_8,
Reference::Short => elf::R_X86_64_PC16,
Reference::Signed => elf::R_X86_64_32S,
Reference::Address { .. } | Reference::Image | Reference::Section | Reference::Field(_) => {
return None;
}
Reference::GotOffset
| Reference::GotFront
| Reference::Slot
| Reference::SlotKept
| Reference::Tls(_) => {
return None;
}
})
}
pub(crate) fn r_type_i386(reference: Reference) -> Option<elf::RelocationType> {
Some(match reference {
Reference::Call => elf::R_386_PLT32,
Reference::Data | Reference::Away => elf::R_386_PC32,
Reference::GotOffset => elf::R_386_GOTOFF,
Reference::GotFront => elf::R_386_GOTPC,
Reference::Slot => elf::R_386_GOT32X,
Reference::SlotKept => elf::R_386_GOT32,
Reference::Tls(Tls::General) => elf::R_386_TLS_GD,
Reference::Tls(Tls::Module) => elf::R_386_TLS_LDM,
Reference::Tls(Tls::InModule) => elf::R_386_TLS_LDO_32,
Reference::Tls(Tls::Slot) => elf::R_386_TLS_GOTIE,
Reference::Tls(Tls::SlotAddress) => elf::R_386_TLS_IE,
Reference::Tls(Tls::SlotNegated) => elf::R_386_TLS_IE_32,
Reference::Tls(Tls::Offset) => elf::R_386_TLS_LE,
Reference::Tls(Tls::Negated) => elf::R_386_TLS_LE_32,
Reference::Address { bytes: 4 } | Reference::Signed => elf::R_386_32,
Reference::Address { bytes: 2 } => elf::R_386_16,
Reference::Address { bytes: 1 } => elf::R_386_8,
Reference::Short => elf::R_386_PC16,
Reference::Got
| Reference::GotBare
| Reference::GotKept
| Reference::Thread
| Reference::AwayWide
| Reference::Address { .. }
| Reference::Image
| Reference::Section
| Reference::Field(_) => return None,
})
}
pub(crate) fn width_i386(r_type: elf::RelocationType) -> Option<usize> {
match r_type {
elf::R_386_8 => Some(1),
elf::R_386_16 | elf::R_386_PC16 => Some(2),
elf::R_386_32
| elf::R_386_PC32
| elf::R_386_PLT32
| elf::R_386_GOTOFF
| elf::R_386_GOTPC
| elf::R_386_GOT32
| elf::R_386_GOT32X
| elf::R_386_TLS_GD
| elf::R_386_TLS_LDM
| elf::R_386_TLS_LDO_32
| elf::R_386_TLS_GOTIE
| elf::R_386_TLS_IE
| elf::R_386_TLS_IE_32
| elf::R_386_TLS_LE
| elf::R_386_TLS_LE_32 => Some(4),
_ => None,
}
}
pub(crate) fn r_type_aarch64(reference: Reference) -> Option<elf::RelocationType> {
Some(match reference {
Reference::Field(fixup) => elf::RelocationType(fixup.elf()?),
Reference::Address { bytes: 8 } => elf::R_AARCH64_ABS64,
Reference::Address { bytes: 4 } => elf::R_AARCH64_ABS32,
Reference::Data | Reference::Away => elf::R_AARCH64_PREL32,
Reference::AwayWide => elf::R_AARCH64_PREL64,
_ => return None,
})
}
pub(crate) fn see(obj: &mut Writer<'_>, id: SymbolId, binding: Binding, visibility: Visibility) {
if binding == Binding::Local {
return;
}
let wanted = match visibility {
Visibility::Default => elf::STV_DEFAULT,
Visibility::Hidden => elf::STV_HIDDEN,
Visibility::Protected => elf::STV_PROTECTED,
};
if let SymbolFlags::Elf { st_other, .. } = obj.symbol_flags_mut(id) {
*st_other = st_other.with_visibility(wanted);
}
}
pub(crate) fn indirect(obj: &mut Writer<'_>, id: SymbolId) {
if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
*st_info = st_info.st_bind() | elf::STT_GNU_IFUNC;
}
let (abi_version, e_flags) = match obj.flags {
object::FileFlags::Elf { abi_version, e_flags, .. } => (abi_version, e_flags),
_ => (0, elf::FileFlags(0)),
};
obj.flags = object::FileFlags::Elf { os_abi: elf::ELFOSABI_GNU, abi_version, e_flags };
}
pub(crate) fn gathered(array: Array) -> SectionFlags {
SectionFlags::Elf {
sh_type: match array {
Array::Init => elf::SHT_INIT_ARRAY,
Array::Fini => elf::SHT_FINI_ARRAY,
Array::Preinit => elf::SHT_PREINIT_ARRAY,
},
sh_flags: elf::SHF_ALLOC | elf::SHF_WRITE,
}
}
pub(crate) fn ordered() -> SectionFlags {
SectionFlags::Elf {
sh_type: elf::SHT_PROGBITS,
sh_flags: elf::SHF_ALLOC | elf::SHF_WRITE | elf::SHF_LINK_ORDER,
}
}
pub(crate) const PATCHABLE: &str = "__patchable_function_entries";
pub(crate) const FRAMES: (&str, u64) = (".eh_frame", 8);
pub(crate) const REL_RO_LOCAL: Option<&str> = Some(".data.rel.ro.local");
pub(crate) fn marker(obj: &mut Writer<'_>) {
obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
}
pub(crate) fn link(bytes: &mut [u8], ordered: &[String]) {
if ordered.is_empty() {
return;
}
let headers = Headers::read(bytes);
let mut wanted = ordered.iter();
for header in &headers.list {
if header.name != PATCHABLE {
continue;
}
let Some(target) = wanted.next() else { break };
let Some(at) = headers.list.iter().position(|other| other.name == *target) else {
continue;
};
let at = u32::try_from(at).expect("a file with this many sections in it");
let sh_link = header.at + headers.link();
bytes[sh_link..sh_link + 4].copy_from_slice(&at.to_le_bytes());
}
debug_assert!(wanted.next().is_none(), "a record whose header nothing found");
}
pub(crate) struct Headers {
wide: bool,
pub(crate) list: Vec<Header>,
}
pub(crate) struct Header {
pub(crate) at: usize,
pub(crate) name: String,
pub(crate) sh_type: u32,
pub(crate) offset: usize,
}
impl Headers {
pub(crate) fn read(bytes: &[u8]) -> Headers {
let wide = bytes[4] == elf::ELFCLASS64.0;
let word = |at: usize, width: usize| {
bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
as usize
};
let (table, each, count, names) = if wide {
(word(0x28, 8), 0x3a, 0x3c, 0x3e)
} else {
(word(0x20, 4), 0x2e, 0x30, 0x32)
};
let (each, count, names) = (word(each, 2), word(count, 2), word(names, 2));
let offset = |header: usize| if wide { word(header + 24, 8) } else { word(header + 16, 4) };
let strings = offset(table + names * each);
let list = (0..count)
.map(|nth| {
let at = table + nth * each;
let from = strings + word(at, 4);
let end =
bytes[from..].iter().position(|byte| *byte == 0).map_or(from, |len| from + len);
Header {
at,
name: String::from_utf8_lossy(&bytes[from..end]).into_owned(),
sh_type: word(at + 4, 4) as u32,
offset: offset(at),
}
})
.collect();
Headers { wide, list }
}
pub(crate) fn link(&self) -> usize {
if self.wide { 40 } else { 24 }
}
pub(crate) fn entry_size(&self) -> (usize, usize) {
if self.wide { (56, 8) } else { (36, 4) }
}
}
pub(crate) fn record(property: Property, align: u32) -> Vec<u8> {
let size = 12u32.next_multiple_of(align);
let head = [4, size, elf::NT_GNU_PROPERTY_TYPE_0.0];
let desc = [Property::X86_FEATURES, 4, property.features];
let mut out = Vec::with_capacity(12 + 4 + size as usize);
for word in head {
out.extend_from_slice(&word.to_le_bytes());
}
out.extend_from_slice(b"GNU\0");
for word in desc {
out.extend_from_slice(&word.to_le_bytes());
}
out.resize(16 + size as usize, 0);
out
}