use object::{Object, ObjectSection, ObjectSymbol};
use target_lexicon::BinaryFormat;
use std::collections::{HashMap, HashSet};
use std::convert::TryInto;
use std::num::TryFromIntError;
use wasmer_types::{entity::PrimaryMap, CompileError, SourceLoc};
use wasmer_compiler::types::{
address_map::{FunctionAddressMap, InstructionAddressMap},
function::{CompiledFunctionFrameInfo, CustomSections, FunctionBody},
relocation::{Relocation, RelocationKind, RelocationTarget},
section::{CustomSection, CustomSectionProtection, SectionBody, SectionIndex},
};
use wasmer_vm::libcalls::LibCall;
fn map_tryfromint_err(error: TryFromIntError) -> CompileError {
CompileError::Codegen(format!("int doesn't fit: {error}"))
}
fn map_object_err(error: object::read::Error) -> CompileError {
CompileError::Codegen(format!("error parsing object file: {error}"))
}
#[derive(Debug)]
pub struct CompiledFunction {
pub compiled_function: wasmer_compiler::types::function::CompiledFunction,
pub custom_sections: CustomSections,
pub eh_frame_section_indices: Vec<SectionIndex>,
pub compact_unwind_section_indices: Vec<SectionIndex>,
}
static LIBCALLS_ELF: phf::Map<&'static str, LibCall> = phf::phf_map! {
"ceilf" => LibCall::CeilF32,
"ceil" => LibCall::CeilF64,
"floorf" => LibCall::FloorF32,
"floor" => LibCall::FloorF64,
"nearbyintf" => LibCall::NearestF32,
"nearbyint" => LibCall::NearestF64,
"truncf" => LibCall::TruncF32,
"trunc" => LibCall::TruncF64,
"wasmer_vm_f32_ceil" => LibCall::CeilF32,
"wasmer_vm_f64_ceil" => LibCall::CeilF64,
"wasmer_vm_f32_floor" => LibCall::FloorF32,
"wasmer_vm_f64_floor" => LibCall::FloorF64,
"wasmer_vm_f32_nearest" => LibCall::NearestF32,
"wasmer_vm_f64_nearest" => LibCall::NearestF64,
"wasmer_vm_f32_trunc" => LibCall::TruncF32,
"wasmer_vm_f64_trunc" => LibCall::TruncF64,
"wasmer_vm_memory32_size" => LibCall::Memory32Size,
"wasmer_vm_imported_memory32_size" => LibCall::ImportedMemory32Size,
"wasmer_vm_table_copy" => LibCall::TableCopy,
"wasmer_vm_table_init" => LibCall::TableInit,
"wasmer_vm_table_fill" => LibCall::TableFill,
"wasmer_vm_table_size" => LibCall::TableSize,
"wasmer_vm_imported_table_size" => LibCall::ImportedTableSize,
"wasmer_vm_table_get" => LibCall::TableGet,
"wasmer_vm_imported_table_get" => LibCall::ImportedTableGet,
"wasmer_vm_table_set" => LibCall::TableSet,
"wasmer_vm_imported_table_set" => LibCall::ImportedTableSet,
"wasmer_vm_table_grow" => LibCall::TableGrow,
"wasmer_vm_imported_table_grow" => LibCall::ImportedTableGrow,
"wasmer_vm_func_ref" => LibCall::FuncRef,
"wasmer_vm_elem_drop" => LibCall::ElemDrop,
"wasmer_vm_memory32_copy" => LibCall::Memory32Copy,
"wasmer_vm_imported_memory32_copy" => LibCall::ImportedMemory32Copy,
"wasmer_vm_memory32_fill" => LibCall::Memory32Fill,
"wasmer_vm_imported_memory32_fill" => LibCall::ImportedMemory32Fill,
"wasmer_vm_memory32_init" => LibCall::Memory32Init,
"wasmer_vm_data_drop" => LibCall::DataDrop,
"wasmer_vm_raise_trap" => LibCall::RaiseTrap,
"wasmer_vm_memory32_atomic_wait32" => LibCall::Memory32AtomicWait32,
"wasmer_vm_imported_memory32_atomic_wait32" => LibCall::ImportedMemory32AtomicWait32,
"wasmer_vm_memory32_atomic_wait64" => LibCall::Memory32AtomicWait64,
"wasmer_vm_imported_memory32_atomic_wait64" => LibCall::ImportedMemory32AtomicWait64,
"wasmer_vm_memory32_atomic_notify" => LibCall::Memory32AtomicNotify,
"wasmer_vm_imported_memory32_atomic_notify" => LibCall::ImportedMemory32AtomicNotify,
"wasmer_vm_throw" => LibCall::Throw,
"wasmer_vm_rethrow" => LibCall::Rethrow,
"wasmer_vm_alloc_exception" => LibCall::AllocException,
"wasmer_vm_delete_exception" => LibCall::DeleteException,
"wasmer_vm_read_exception" => LibCall::ReadException,
"wasmer_vm_dbg_usize" => LibCall::DebugUsize,
"wasmer_eh_personality" => LibCall::EHPersonality,
};
static LIBCALLS_MACHO: phf::Map<&'static str, LibCall> = phf::phf_map! {
"_ceilf" => LibCall::CeilF32,
"_ceil" => LibCall::CeilF64,
"_floorf" => LibCall::FloorF32,
"_floor" => LibCall::FloorF64,
"_nearbyintf" => LibCall::NearestF32,
"_nearbyint" => LibCall::NearestF64,
"_truncf" => LibCall::TruncF32,
"_trunc" => LibCall::TruncF64,
"_wasmer_vm_f32_ceil" => LibCall::CeilF32,
"_wasmer_vm_f64_ceil" => LibCall::CeilF64,
"_wasmer_vm_f32_floor" => LibCall::FloorF32,
"_wasmer_vm_f64_floor" => LibCall::FloorF64,
"_wasmer_vm_f32_nearest" => LibCall::NearestF32,
"_wasmer_vm_f64_nearest" => LibCall::NearestF64,
"_wasmer_vm_f32_trunc" => LibCall::TruncF32,
"_wasmer_vm_f64_trunc" => LibCall::TruncF64,
"_wasmer_vm_memory32_size" => LibCall::Memory32Size,
"_wasmer_vm_imported_memory32_size" => LibCall::ImportedMemory32Size,
"_wasmer_vm_table_copy" => LibCall::TableCopy,
"_wasmer_vm_table_init" => LibCall::TableInit,
"_wasmer_vm_table_fill" => LibCall::TableFill,
"_wasmer_vm_table_size" => LibCall::TableSize,
"_wasmer_vm_imported_table_size" => LibCall::ImportedTableSize,
"_wasmer_vm_table_get" => LibCall::TableGet,
"_wasmer_vm_imported_table_get" => LibCall::ImportedTableGet,
"_wasmer_vm_table_set" => LibCall::TableSet,
"_wasmer_vm_imported_table_set" => LibCall::ImportedTableSet,
"_wasmer_vm_table_grow" => LibCall::TableGrow,
"_wasmer_vm_imported_table_grow" => LibCall::ImportedTableGrow,
"_wasmer_vm_func_ref" => LibCall::FuncRef,
"_wasmer_vm_elem_drop" => LibCall::ElemDrop,
"_wasmer_vm_memory32_copy" => LibCall::Memory32Copy,
"_wasmer_vm_imported_memory32_copy" => LibCall::ImportedMemory32Copy,
"_wasmer_vm_memory32_fill" => LibCall::Memory32Fill,
"_wasmer_vm_imported_memory32_fill" => LibCall::ImportedMemory32Fill,
"_wasmer_vm_memory32_init" => LibCall::Memory32Init,
"_wasmer_vm_data_drop" => LibCall::DataDrop,
"_wasmer_vm_raise_trap" => LibCall::RaiseTrap,
"_wasmer_vm_memory32_atomic_wait32" => LibCall::Memory32AtomicWait32,
"_wasmer_vm_imported_memory32_atomic_wait32" => LibCall::ImportedMemory32AtomicWait32,
"_wasmer_vm_memory32_atomic_wait64" => LibCall::Memory32AtomicWait64,
"_wasmer_vm_imported_memory32_atomic_wait64" => LibCall::ImportedMemory32AtomicWait64,
"_wasmer_vm_memory32_atomic_notify" => LibCall::Memory32AtomicNotify,
"_wasmer_vm_imported_memory32_atomic_notify" => LibCall::ImportedMemory32AtomicNotify,
"_wasmer_vm_throw" => LibCall::Throw,
"_wasmer_vm_rethrow" => LibCall::Rethrow,
"_wasmer_vm_alloc_exception" => LibCall::AllocException,
"_wasmer_vm_delete_exception" => LibCall::DeleteException,
"_wasmer_vm_read_exception" => LibCall::ReadException,
"_wasmer_vm_dbg_usize" => LibCall::DebugUsize,
"___gxx_personality_v0" => LibCall::EHPersonality,
};
pub fn load_object_file<F>(
contents: &[u8],
root_section: &str,
root_section_reloc_target: RelocationTarget,
mut symbol_name_to_relocation_target: F,
binary_fmt: BinaryFormat,
) -> Result<CompiledFunction, CompileError>
where
F: FnMut(&str) -> Result<Option<RelocationTarget>, CompileError>,
{
let obj = object::File::parse(contents).map_err(map_object_err)?;
let libcalls = match binary_fmt {
BinaryFormat::Elf => &LIBCALLS_ELF,
BinaryFormat::Macho => &LIBCALLS_MACHO,
_ => {
return Err(CompileError::UnsupportedTarget(format!(
"Unsupported binary format {binary_fmt:?}"
)))
}
};
let mut visited: HashSet<object::read::SectionIndex> = HashSet::new();
let mut worklist: Vec<object::read::SectionIndex> = Vec::new();
let mut section_targets: HashMap<object::read::SectionIndex, RelocationTarget> = HashMap::new();
let root_section_index = obj
.section_by_name(root_section)
.ok_or_else(|| CompileError::Codegen(format!("no section named {root_section}")))?
.index();
let mut section_to_custom_section = HashMap::new();
section_targets.insert(root_section_index, root_section_reloc_target);
let mut next_custom_section: u32 = 0;
let mut elf_section_to_target = |elf_section_index: object::read::SectionIndex| {
*section_targets.entry(elf_section_index).or_insert_with(|| {
let next = SectionIndex::from_u32(next_custom_section);
section_to_custom_section.insert(elf_section_index, next);
let target = RelocationTarget::CustomSection(next);
next_custom_section += 1;
target
})
};
let mut relocations: HashMap<object::read::SectionIndex, Vec<Relocation>> = HashMap::new();
worklist.push(root_section_index);
visited.insert(root_section_index);
let mut eh_frame_section_indices = vec![];
let mut compact_unwind_section_indices = vec![];
for section in obj.sections() {
let index = section.index();
if section.kind() == object::SectionKind::Elf(object::elf::SHT_X86_64_UNWIND)
|| section.name().unwrap_or_default() == "__eh_frame"
{
worklist.push(index);
eh_frame_section_indices.push(index);
elf_section_to_target(index);
} else if section.name().unwrap_or_default() == "__compact_unwind" {
worklist.push(index);
compact_unwind_section_indices.push(index);
elf_section_to_target(index);
}
}
while let Some(section_index) = worklist.pop() {
let sec = obj
.section_by_index(section_index)
.map_err(map_object_err)?;
let relocs = sec.relocations();
for (offset, reloc) in relocs {
let mut addend = reloc.addend();
let target = match reloc.target() {
object::read::RelocationTarget::Symbol(index) => {
let symbol = obj.symbol_by_index(index).map_err(map_object_err)?;
let symbol_name = symbol.name().map_err(map_object_err)?;
if symbol.kind() == object::SymbolKind::Section {
match symbol.section() {
object::SymbolSection::Section(section_index) => {
if section_index == root_section_index {
root_section_reloc_target
} else {
if visited.insert(section_index) {
worklist.push(section_index);
}
elf_section_to_target(section_index)
}
}
_ => {
return Err(CompileError::Codegen(format!(
"relocation targets unknown section {reloc:?}",
)));
}
}
} else if let Some(libcall) = libcalls.get(symbol_name) {
RelocationTarget::LibCall(*libcall)
} else if let Ok(Some(reloc_target)) =
symbol_name_to_relocation_target(symbol_name)
{
reloc_target
} else if let object::SymbolSection::Section(section_index) = symbol.section() {
if matches!(
reloc.kind(),
object::RelocationKind::MachO {
value: object::macho::ARM64_RELOC_GOT_LOAD_PAGEOFF12,
relative: false
} | object::RelocationKind::MachO {
value: object::macho::ARM64_RELOC_POINTER_TO_GOT,
relative: true
} | object::RelocationKind::MachO {
value: object::macho::ARM64_RELOC_GOT_LOAD_PAGE21,
relative: true
} | object::RelocationKind::MachO {
value: object::macho::ARM64_RELOC_PAGE21,
relative: true
} | object::RelocationKind::MachO {
value: object::macho::ARM64_RELOC_PAGEOFF12,
relative: false
}
) {
let symbol_sec = obj
.section_by_index(section_index)
.map_err(map_object_err)?;
addend = addend
.wrapping_add((symbol.address() - symbol_sec.address()) as i64);
} else {
addend = addend.wrapping_add(symbol.address() as i64);
}
if section_index == root_section_index {
root_section_reloc_target
} else {
if visited.insert(section_index) {
worklist.push(section_index);
}
elf_section_to_target(section_index)
}
} else {
return Err(CompileError::Codegen(format!(
"relocation {reloc:?} targets unknown symbol '{symbol:?}'",
)));
}
}
object::read::RelocationTarget::Section(index) => {
if index == root_section_index {
root_section_reloc_target
} else {
if visited.insert(index) {
worklist.push(index);
}
elf_section_to_target(index)
}
}
object::read::RelocationTarget::Absolute => {
return Err(CompileError::Codegen(format!(
"relocation targets absolute address {reloc:?}",
)));
}
t => {
return Err(CompileError::Codegen(format!(
"relocation target is unknown `{t:?}`",
)));
}
};
let kind = match (obj.architecture(), reloc.kind(), reloc.size()) {
(_, object::RelocationKind::Absolute, 64) => RelocationKind::Abs8,
(_, object::RelocationKind::Absolute, 32) => RelocationKind::Abs4,
(
object::Architecture::X86_64,
object::RelocationKind::Elf(object::elf::R_X86_64_PC64),
0,
) => RelocationKind::X86PCRel8,
(object::Architecture::Aarch64, object::RelocationKind::PltRelative, 26) => {
RelocationKind::Arm64Call
}
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_MOVW_UABS_G0_NC),
0,
) => RelocationKind::Arm64Movw0,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_MOVW_UABS_G1_NC),
0,
) => RelocationKind::Arm64Movw1,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_MOVW_UABS_G2_NC),
0,
) => RelocationKind::Arm64Movw2,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_MOVW_UABS_G3),
0,
) => RelocationKind::Arm64Movw3,
(
object::Architecture::Riscv64,
object::RelocationKind::Elf(object::elf::R_RISCV_CALL_PLT),
0,
) => RelocationKind::RiscvCall,
(
object::Architecture::Riscv64,
object::RelocationKind::Elf(object::elf::R_RISCV_PCREL_HI20),
0,
) => RelocationKind::RiscvPCRelHi20,
(
object::Architecture::Riscv64,
object::RelocationKind::Elf(object::elf::R_RISCV_PCREL_LO12_I),
0,
) => RelocationKind::RiscvPCRelLo12I,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_ABS_HI20),
0,
) => RelocationKind::LArchAbsHi20,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_ABS_LO12),
0,
) => RelocationKind::LArchAbsLo12,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_ABS64_HI12),
0,
) => RelocationKind::LArchAbs64Hi12,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_ABS64_LO20),
0,
) => RelocationKind::LArchAbs64Lo20,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(110),
0,
) => RelocationKind::LArchCall36,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_PCALA_HI20),
0,
) => RelocationKind::LArchPCAlaHi20,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_PCALA_LO12),
0,
) => RelocationKind::LArchPCAlaLo12,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_PCALA64_HI12),
0,
) => RelocationKind::LArchPCAla64Hi12,
(
object::Architecture::LoongArch64,
object::RelocationKind::Elf(object::elf::R_LARCH_PCALA64_LO20),
0,
) => RelocationKind::LArchPCAla64Lo20,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_ADR_PREL_LO21),
0,
) => RelocationKind::Aarch64AdrPrelLo21,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_ADR_PREL_PG_HI21),
0,
) => RelocationKind::Aarch64AdrPrelPgHi21,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_LDST128_ABS_LO12_NC),
0,
) => RelocationKind::Aarch64Ldst128AbsLo12Nc,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_ADD_ABS_LO12_NC),
0,
) => RelocationKind::Aarch64AddAbsLo12Nc,
(
object::Architecture::Aarch64,
object::RelocationKind::Elf(object::elf::R_AARCH64_LDST64_ABS_LO12_NC),
0,
) => RelocationKind::Aarch64Ldst64AbsLo12Nc,
(object::Architecture::Aarch64, object::RelocationKind::MachO { value, .. }, _) => {
match value {
object::macho::ARM64_RELOC_UNSIGNED => {
RelocationKind::MachoArm64RelocUnsigned
}
object::macho::ARM64_RELOC_SUBTRACTOR => {
RelocationKind::MachoArm64RelocSubtractor
}
object::macho::ARM64_RELOC_BRANCH26 => {
RelocationKind::MachoArm64RelocBranch26
}
object::macho::ARM64_RELOC_PAGE21 => RelocationKind::MachoArm64RelocPage21,
object::macho::ARM64_RELOC_PAGEOFF12 => {
RelocationKind::MachoArm64RelocPageoff12
}
object::macho::ARM64_RELOC_GOT_LOAD_PAGE21 => {
RelocationKind::MachoArm64RelocGotLoadPage21
}
object::macho::ARM64_RELOC_GOT_LOAD_PAGEOFF12 => {
RelocationKind::MachoArm64RelocGotLoadPageoff12
}
object::macho::ARM64_RELOC_POINTER_TO_GOT => {
RelocationKind::MachoArm64RelocPointerToGot
}
object::macho::ARM64_RELOC_TLVP_LOAD_PAGE21 => {
RelocationKind::MachoArm64RelocTlvpLoadPage21
}
object::macho::ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
RelocationKind::MachoArm64RelocTlvpLoadPageoff12
}
object::macho::ARM64_RELOC_ADDEND => RelocationKind::MachoArm64RelocAddend,
_ => {
return Err(CompileError::Codegen(format!(
"unknown relocation {reloc:?}",
)))
}
}
}
(object::Architecture::X86_64, object::RelocationKind::MachO { value, .. }, _) => {
match value {
object::macho::X86_64_RELOC_UNSIGNED => {
RelocationKind::MachoX86_64RelocUnsigned
}
object::macho::X86_64_RELOC_SIGNED => {
RelocationKind::MachoX86_64RelocSigned
}
object::macho::X86_64_RELOC_BRANCH => {
RelocationKind::MachoX86_64RelocBranch
}
object::macho::X86_64_RELOC_GOT_LOAD => {
RelocationKind::MachoX86_64RelocGotLoad
}
object::macho::X86_64_RELOC_GOT => RelocationKind::MachoX86_64RelocGot,
object::macho::X86_64_RELOC_SUBTRACTOR => {
RelocationKind::MachoX86_64RelocSubtractor
}
object::macho::X86_64_RELOC_SIGNED_1 => {
RelocationKind::MachoX86_64RelocSigned1
}
object::macho::X86_64_RELOC_SIGNED_2 => {
RelocationKind::MachoX86_64RelocSigned2
}
object::macho::X86_64_RELOC_SIGNED_4 => {
RelocationKind::MachoX86_64RelocSigned4
}
object::macho::X86_64_RELOC_TLV => RelocationKind::MachoX86_64RelocTlv,
_ => {
return Err(CompileError::Codegen(format!(
"unknown relocation {reloc:?}"
)))
}
}
}
_ => {
return Err(CompileError::Codegen(format!(
"unknown relocation {reloc:?}",
)));
}
};
relocations
.entry(section_index)
.or_default()
.push(Relocation {
kind,
reloc_target: target,
offset: offset.try_into().map_err(map_tryfromint_err)?,
addend,
});
}
}
let eh_frame_section_indices = eh_frame_section_indices
.iter()
.map(|index| {
section_to_custom_section.get(index).map_or_else(
|| {
Err(CompileError::Codegen(format!(
".eh_frame section with index={index:?} was never loaded",
)))
},
|idx| Ok(*idx),
)
})
.collect::<Result<Vec<SectionIndex>, _>>()?;
let compact_unwind_section_indices = compact_unwind_section_indices
.iter()
.map(|index| {
section_to_custom_section.get(index).map_or_else(
|| {
Err(CompileError::Codegen(format!(
"_compact_unwind section with index={index:?} was never loaded",
)))
},
|idx| Ok(*idx),
)
})
.collect::<Result<Vec<SectionIndex>, _>>()?;
let mut custom_sections = section_to_custom_section
.iter()
.map(|(elf_section_index, custom_section_index)| {
(
custom_section_index,
CustomSection {
protection: CustomSectionProtection::Read,
bytes: SectionBody::new_with_vec(
obj.section_by_index(*elf_section_index)
.unwrap()
.data()
.unwrap()
.to_vec(),
),
relocations: relocations
.remove_entry(elf_section_index)
.map_or(vec![], |(_, v)| v),
},
)
})
.collect::<Vec<_>>();
custom_sections.sort_unstable_by_key(|a| a.0);
let custom_sections = custom_sections
.into_iter()
.map(|(_, v)| v)
.collect::<PrimaryMap<SectionIndex, _>>();
let function_body = FunctionBody {
body: obj
.section_by_index(root_section_index)
.unwrap()
.data()
.unwrap()
.to_vec(),
unwind_info: None,
};
let address_map = FunctionAddressMap {
instructions: vec![InstructionAddressMap {
srcloc: SourceLoc::default(),
code_offset: 0,
code_len: function_body.body.len(),
}],
start_srcloc: SourceLoc::default(),
end_srcloc: SourceLoc::default(),
body_offset: 0,
body_len: function_body.body.len(),
};
Ok(CompiledFunction {
compiled_function: wasmer_compiler::types::function::CompiledFunction {
body: function_body,
relocations: relocations
.remove_entry(&root_section_index)
.map_or(vec![], |(_, v)| v),
frame_info: CompiledFunctionFrameInfo {
address_map,
traps: vec![],
},
},
custom_sections,
eh_frame_section_indices,
compact_unwind_section_indices,
})
}