#![deny(clippy::arithmetic_side_effects)]
use crate::elf::arch::thunk::Thunks;
use crate::elf::layout::{Layout, LayoutInput};
use crate::elf::object::SectionKind;
use crate::elf::read::consts::{SHF_ALLOC, SHF_EXECINSTR};
use crate::elf::refs::Def;
use crate::elf::synth::Owner;
use crate::symbols::SymbolFlags;
use super::{Branch, is_thunk_branch, pc_bias, thunk_key};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Destination {
pub address: u64,
pub function: bool,
pub via_stub: bool,
}
#[must_use]
pub fn destination<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
file: usize,
symbol: u32,
addend: i64,
r_type: u32,
) -> Option<Destination> {
let refs = &input.refs;
let target = refs.target(file, symbol as usize)?;
let owner = match target.global {
Some(id) => Owner::Global(id),
None => Owner::Local {
file: u32::try_from(file).unwrap_or(u32::MAX),
symbol,
},
};
let bias = pc_bias(r_type);
let stub = |address: u64| {
Some(Destination {
address: address.wrapping_add_signed(addend).wrapping_add(bias),
function: true,
via_stub: true,
})
};
if target.is_ifunc()
&& let Some(address) = crate::elf::values::iplt_address(input.synth, layout, owner)
{
return stub(address);
}
let flags = target
.global
.map_or(SymbolFlags::EMPTY, |id| refs.symbols.flags(id));
if flags.contains(SymbolFlags::NEEDS_PLT | crate::elf::export::PREEMPTIBLE)
&& let Some(address) = crate::elf::values::plt_address(input.synth, layout, owner)
{
return stub(address);
}
let address = match target.def {
Def::Section {
file,
section,
value,
} => {
let id = refs.sections.id(file, section)?;
if layout.section_shndx.get(id.index()).copied().unwrap_or(0) == 0 {
return None;
}
layout
.section_addr
.get(id.index())
.copied()?
.wrapping_add(value)
}
Def::Absolute(value) => value,
_ => return None,
};
let function = target.raw.is_some_and(|raw| {
matches!(
raw.kind(),
crate::elf::read::consts::STT_FUNC | crate::elf::read::consts::STT_GNU_IFUNC
)
});
Some(Destination {
address: address.wrapping_add_signed(addend).wrapping_add(bias),
function,
via_stub: false,
})
}
#[must_use]
pub fn key_of(branch: &Branch, pic: bool) -> Option<u64> {
let plan = branch.plan();
if !plan.thunk {
return None;
}
let destination = (branch.destination & !1) | u64::from(plan.thumb_target);
Some(thunk_key(destination, branch.thumb_caller(), pic))
}
#[must_use]
pub fn plan<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
previous: &Thunks,
) -> Thunks {
let refs = &input.refs;
let pic = input.mode.pic;
let mut needed: Vec<(u32, u64)> = Vec::new();
for (file_index, file) in refs.files.iter().enumerate() {
let Some(object) = &file.object else {
continue;
};
for (section_index, section) in object.sections.iter().enumerate() {
let section_index = u32::try_from(section_index).unwrap_or(u32::MAX);
let flags = section.header.sh_flags;
if section.relocs == 0
|| flags & (SHF_ALLOC | SHF_EXECINSTR) != (SHF_ALLOC | SHF_EXECINSTR)
|| section.kind == SectionKind::Ignored
|| !refs.sections.is_live_in(file_index, section_index)
{
continue;
}
let Some(id) = refs.sections.id(file_index, section_index) else {
continue;
};
let shndx = layout.section_shndx.get(id.index()).copied().unwrap_or(0);
let Some(out) = shndx
.checked_sub(1)
.and_then(|p| layout.sections.get(p as usize))
else {
continue;
};
let output = out.output;
let base = layout.section_addr.get(id.index()).copied().unwrap_or(0);
let Some(Ok(Some(relocations))) = object
.section(section.relocs)
.map(|r| object.elf.relocation_section(section.relocs, &r.header))
else {
continue;
};
let Ok(data) = object.section_data(section) else {
continue;
};
crate::elf::arch::for_each_relocation!(
crate::elf::arch::Arch::Arm,
relocations.relocations,
data,
|rel| {
if !is_thunk_branch(rel.r_type) {
continue;
}
let Some(destination) = destination(
input, layout, file_index, rel.symbol, rel.addend, rel.r_type,
) else {
continue;
};
let at = usize::try_from(rel.offset).unwrap_or(usize::MAX);
let insn = match super::patch_of(rel.r_type) {
super::Patch::Insn(field) => field.read(data, at).unwrap_or(0),
_ => 0,
};
let branch = Branch {
r_type: rel.r_type,
insn,
place: base.wrapping_add(rel.offset),
destination: destination.address,
function: destination.function,
via_stub: destination.via_stub,
};
if let Some(key) = key_of(&branch, pic) {
needed.push((output, key));
}
}
);
}
}
if needed.is_empty() {
return Thunks::default();
}
let pool_start = |output: u32| -> u64 {
let size = layout
.sections
.iter()
.find(|s| s.output == output)
.map_or(0, |s| s.size);
let base = size.saturating_sub(previous.size_of(output));
base.saturating_add(3) & !3
};
Thunks::build_for(crate::elf::arch::Arch::Arm, needed, &pool_start)
}