#![deny(clippy::arithmetic_side_effects)]
use crate::arch::aarch64;
use crate::elf::layout::{Layout, LayoutInput};
use crate::elf::object::SectionKind;
use crate::elf::read::Relocations;
use crate::elf::read::consts::{SHF_ALLOC, SHF_EXECINSTR};
use crate::elf::refs::Def;
use crate::elf::synth::Owner;
use crate::ids::SectionId;
use crate::symbols::SymbolFlags;
use super::aarch64_errata::{self, Site};
use super::{Arch, Branch};
pub const MAX_ROUNDS: u32 = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Thunk {
pub output: u32,
pub target: u64,
pub offset: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Patch {
pub site: Site,
pub offset: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Thunks {
pub entries: Vec<Thunk>,
pub patches: Vec<Patch>,
pub arch: Arch,
}
impl Thunks {
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty() && self.patches.is_empty()
}
#[must_use]
pub fn size_of(&self, output: u32) -> u64 {
self.thunk_bytes(output)
.saturating_add(self.patch_bytes(output))
}
fn thunk_bytes(&self, output: u32) -> u64 {
let count = self.entries.iter().filter(|t| t.output == output).count();
u64::try_from(count)
.unwrap_or(0)
.saturating_mul(self.arch.thunk_size())
}
#[must_use]
pub fn patch_bytes(&self, output: u32) -> u64 {
let count = self
.patches
.iter()
.filter(|p| p.site.output == output)
.count();
u64::try_from(count)
.unwrap_or(0)
.saturating_mul(aarch64::ERRATUM_PATCH_SIZE)
}
#[must_use]
pub fn offset_of(&self, output: u32, target: u64) -> Option<u64> {
let at = self
.entries
.binary_search_by_key(&(output, target), |t| (t.output, t.target))
.ok()?;
self.entries.get(at).map(|t| t.offset)
}
#[must_use]
pub fn build(needed: Vec<(u32, u64)>, pool_start: &dyn Fn(u32) -> u64) -> Self {
Self::build_for(Arch::AArch64, needed, pool_start)
}
#[must_use]
pub fn build_for(
arch: Arch,
mut needed: Vec<(u32, u64)>,
pool_start: &dyn Fn(u32) -> u64,
) -> Self {
let size = arch.thunk_size();
needed.sort_unstable();
needed.dedup();
let mut entries = Vec::with_capacity(needed.len());
let mut current = None;
let mut next = 0u64;
for (output, target) in needed {
if current != Some(output) {
current = Some(output);
next = pool_start(output);
}
entries.push(Thunk {
output,
target,
offset: next,
});
next = next.saturating_add(size);
}
Self {
entries,
patches: Vec::new(),
arch,
}
}
#[must_use]
pub fn with_patches(mut self, sites: Vec<Site>, pool_start: &dyn Fn(u32) -> u64) -> Self {
let mut current = None;
let mut next = 0u64;
for site in sites {
if current != Some(site.output) {
current = Some(site.output);
next = pool_start(site.output).saturating_add(self.thunk_bytes(site.output));
}
self.patches.push(Patch { site, offset: next });
next = next.saturating_add(aarch64::ERRATUM_PATCH_SIZE);
}
self
}
#[must_use]
pub fn render(&self, output: u32, base: u64) -> Vec<(u64, Vec<u8>)> {
let mut out = Vec::new();
let size = usize::try_from(self.arch.thunk_size()).unwrap_or(0);
for thunk in self.entries.iter().filter(|t| t.output == output) {
let mut bytes = vec![0u8; size];
let address = base.wrapping_add(thunk.offset);
if self
.arch
.write_thunk(&mut bytes, 0, address, thunk.target)
.is_ok()
{
out.push((thunk.offset, bytes));
}
}
out
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Placed {
pub output: u32,
pub target: u64,
pub address: u64,
pub patch: Option<(SectionId, u64)>,
}
pub fn patches_in(
placed: &[Placed],
output: u32,
start: u64,
end: u64,
) -> impl Iterator<Item = &Placed> {
let from = placed.partition_point(|p| (p.output, p.target) < (output, start));
placed
.get(from..)
.unwrap_or_default()
.iter()
.take_while(move |p| p.output == output && p.target < end)
.filter(|p| p.patch.is_some())
}
fn plt_address<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
owner: Owner,
) -> Option<u64> {
crate::elf::values::plt_address(input.synth, layout, owner)
}
fn slot_of<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
owner: Owner,
) -> u64 {
crate::elf::values::plt_slot_address(input.synth, layout, owner).unwrap_or(0)
}
fn branch_target<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
file: usize,
symbol: u32,
addend: i64,
) -> Option<(u64, Option<u64>, u8)> {
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,
},
};
if target.is_ifunc()
&& let Some(stub) = crate::elf::values::iplt_address(input.synth, layout, owner)
{
return Some((stub, Some(slot_of(input, layout, owner)), 0));
}
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(plt) = plt_address(input, layout, owner)
{
return Some((plt, Some(slot_of(input, layout, owner)), 0));
}
let st_other = target.raw.map_or(0, |raw| raw.st_other);
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,
Def::Undefined { .. } if symbol == 0 => 0,
_ => return None,
};
Some((address.wrapping_add_signed(addend), None, st_other))
}
#[must_use]
pub fn plan<F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, '_, F>,
layout: &Layout<'_>,
previous: &Thunks,
) -> Thunks {
let arch = input.synth.arch;
if !arch.needs_thunks() {
return Thunks::default();
}
if arch == Arch::Arm {
return super::arm::thunks::plan(input, layout, previous);
}
let refs = &input.refs;
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 Relocations::Rela(relas) = relocations.relocations else {
continue;
};
for rel in relas.iter() {
if !arch.is_thunk_branch(rel.r_type) {
continue;
}
let place = base.wrapping_add(rel.offset);
let Some((target, stub_slot, st_other)) =
branch_target(input, layout, file_index, rel.symbol, rel.addend)
else {
continue;
};
let branch = Branch {
r_type: rel.r_type,
place,
target,
st_other,
via_stub: stub_slot.is_some(),
slot: stub_slot,
};
if let Some(key) = arch.branch_thunk(branch) {
needed.push((output, key));
}
}
}
}
let sites = if arch == Arch::AArch64 {
aarch64_errata::scan(refs, layout, input.options)
} else {
Vec::new()
};
if needed.is_empty() && sites.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(arch, needed, &pool_start).with_patches(sites, &pool_start)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thunks_are_shared_and_ordered() {
let plan = Thunks::build(
vec![(1, 0x9000_0000), (1, 0x8000_0000), (1, 0x9000_0000)],
&|_| 0x100,
);
assert_eq!(plan.entries.len(), 2);
assert_eq!(plan.size_of(1), 2 * aarch64::THUNK_SIZE);
assert_eq!(plan.offset_of(1, 0x8000_0000), Some(0x100));
assert_eq!(
plan.offset_of(1, 0x9000_0000),
Some(0x100 + aarch64::THUNK_SIZE)
);
assert_eq!(plan.offset_of(2, 0x8000_0000), None);
}
#[test]
fn patches_follow_the_thunks_of_their_pool() {
let site = |output, address| Site {
output,
address,
section: SectionId::new(0),
offset: address,
};
let plan = Thunks::build(vec![(1, 0x8000_0000)], &|_| 0x100)
.with_patches(vec![site(1, 0x10), site(1, 0x20), site(2, 0x30)], &|_| {
0x100
});
assert_eq!(plan.patches[0].offset, 0x100 + aarch64::THUNK_SIZE);
assert_eq!(
plan.patches[1].offset,
0x100 + aarch64::THUNK_SIZE + aarch64::ERRATUM_PATCH_SIZE
);
assert_eq!(plan.patches[2].offset, 0x100);
assert_eq!(
plan.size_of(1),
aarch64::THUNK_SIZE + 2 * aarch64::ERRATUM_PATCH_SIZE
);
assert_eq!(plan.patch_bytes(2), aarch64::ERRATUM_PATCH_SIZE);
assert!(!plan.is_empty());
}
#[test]
fn patches_are_not_thunks() {
let placed = [
Placed {
output: 1,
target: 0x40,
address: 0x200,
patch: None,
},
Placed {
output: 1,
target: 0x40,
address: 0x210,
patch: Some((SectionId::new(3), 0x40)),
},
];
let found: Vec<u64> = patches_in(&placed, 1, 0, 0x100)
.map(|p| p.address)
.collect();
assert_eq!(found, [0x210]);
assert_eq!(patches_in(&placed, 1, 0x41, 0x100).count(), 0);
assert_eq!(patches_in(&placed, 2, 0, u64::MAX).count(), 0);
}
#[test]
fn rendered_thunks_branch_to_their_target() {
let plan = Thunks::build(vec![(0, 0x8000_1000)], &|_| 0);
let rendered = plan.render(0, 0x1000);
assert_eq!(rendered.len(), 1);
let (offset, bytes) = &rendered[0];
assert_eq!(*offset, 0);
let words: Vec<u32> = bytes
.as_chunks::<4>()
.0
.iter()
.map(|w| u32::from_le_bytes(*w))
.collect();
assert_eq!(words[2], aarch64::BR_X16);
assert_eq!(words[0] & 0x1f, 16);
assert_eq!(words[1], 0x9100_0210);
}
}