#![deny(clippy::arithmetic_side_effects)]
use std::cell::OnceCell;
use rayon::prelude::*;
use crate::elf::common::Commons;
use crate::elf::export::PREEMPTIBLE;
use crate::elf::inputs::ElfInput;
use crate::elf::layout::{Layout, LayoutInput};
use crate::elf::object::{InputSection, ObjectInput, SectionKind};
use crate::elf::read::consts::{SHF_ALLOC, SHF_EXECINSTR};
use crate::elf::read::{Relocation, Relocations};
use crate::elf::refs::Def;
use crate::elf::reloc::{self, Context};
use crate::elf::synth::Owner;
use crate::elf::values::Addresses;
use crate::error::{Error, Result};
use crate::ids::SectionId;
use crate::symbols::SymbolFlags;
use super::{Arch, TlsMode};
pub const MAX_PASSES: u32 = 30;
const NONE: u32 = u32::MAX;
#[derive(Clone, Copy, Debug)]
enum Place {
Unknown,
Absolute(u64),
Section {
id: SectionId,
value: u64,
addend: i64,
},
Offset {
file: usize,
section: u32,
value: u64,
addend: i64,
},
Plt { owner: Owner, addend: i64 },
Iplt { owner: Owner, addend: i64 },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Rewrite {
Align {
addend: u32,
},
Delete,
Replace {
word: u32,
len: u8,
r_type: u32,
},
Retype(u32),
}
impl Rewrite {
#[must_use]
pub fn kept(self) -> u64 {
match self {
Self::Replace { len, .. } => u64::from(len),
Self::Align { .. } | Self::Delete | Self::Retype(_) => 0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Edit {
pub seq: u32,
pub index: u32,
pub offset: u64,
pub remove: u32,
pub delta: u64,
pub rewrite: Rewrite,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SectionRelax {
pub id: SectionId,
pub edits: Vec<Edit>,
pub sorted: bool,
}
impl SectionRelax {
#[must_use]
pub fn removed(&self) -> u64 {
self.edits.last().map_or(0, |e| e.delta)
}
#[must_use]
pub fn map(&self, offset: u64) -> u64 {
let before = self.edits.partition_point(|e| e.offset < offset);
let deleted = before
.checked_sub(1)
.and_then(|i| self.edits.get(i))
.map_or(0, |e| e.delta);
offset.saturating_sub(deleted)
}
#[must_use]
pub fn edit(&self, seq: u32) -> Option<&Edit> {
let at = self.edits.binary_search_by_key(&seq, |e| e.seq).ok()?;
self.edits.get(at)
}
#[must_use]
pub fn edit_of_index(&self, index: u32) -> Option<&Edit> {
if self.sorted {
self.edit(index)
} else {
self.edits.iter().find(|e| e.index == index)
}
}
fn shape(&self) -> impl Iterator<Item = (u32, u64)> + '_ {
self.edits
.iter()
.filter(|e| e.remove != 0)
.map(|e| (e.seq, e.delta))
}
}
#[derive(Clone, Debug, Default)]
pub struct Relaxation {
index: Vec<u32>,
sections: Vec<SectionRelax>,
deleted_type: u32,
}
impl Relaxation {
fn new(total: usize, sections: Vec<SectionRelax>, deleted_type: u32) -> Self {
if sections.is_empty() {
return Self::default();
}
let mut index = vec![NONE; total];
for (position, section) in sections.iter().enumerate() {
if let Some(slot) = index.get_mut(section.id.index()) {
*slot = u32::try_from(position).unwrap_or(NONE);
}
}
Self {
index,
sections,
deleted_type,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
#[must_use]
pub fn section(&self, id: SectionId) -> Option<&SectionRelax> {
if self.index.is_empty() {
return None;
}
let position = *self.index.get(id.index())?;
self.sections.get(position as usize)
}
#[inline]
#[must_use]
pub fn map(&self, id: SectionId, offset: u64) -> u64 {
match self.section(id) {
Some(section) => section.map(offset),
None => offset,
}
}
#[must_use]
pub fn emitted_type(&self, id: SectionId, index: usize, r_type: u32) -> u32 {
let edit = u32::try_from(index)
.ok()
.and_then(|index| self.section(id)?.edit_of_index(index));
match edit.map(|e| e.rewrite) {
Some(Rewrite::Replace { r_type: new, .. }) if new != 0 => new,
Some(Rewrite::Delete) => self.deleted_type,
_ => r_type,
}
}
#[inline]
#[must_use]
pub fn removed(&self, id: SectionId) -> u64 {
self.section(id).map_or(0, SectionRelax::removed)
}
#[must_use]
pub fn symbol_size(&self, id: SectionId, value: u64, size: u64) -> u64 {
match self.section(id) {
Some(section) => section
.map(value.saturating_add(size))
.saturating_sub(section.map(value)),
None => size,
}
}
fn same_shape(&self, other: &Self) -> bool {
self.sections.len() == other.sections.len()
&& self
.sections
.iter()
.zip(&other.sections)
.all(|(a, b)| a.id == b.id && a.shape().eq(b.shape()))
}
}
#[must_use]
pub fn sorted_order(offsets: &[u64]) -> Option<Vec<u32>> {
if offsets.is_sorted() {
return None;
}
let mut order: Vec<u32> = (0..u32::try_from(offsets.len()).unwrap_or(u32::MAX)).collect();
order.sort_by_key(|&i| offsets.get(i as usize).copied().unwrap_or(u64::MAX));
Some(order)
}
#[must_use]
pub fn ordered(relocs: Vec<Relocation>) -> (Vec<Relocation>, bool) {
let offsets: Vec<u64> = relocs.iter().map(|r| r.offset).collect();
match sorted_order(&offsets) {
None => (relocs, true),
Some(order) => (
order
.iter()
.filter_map(|&i| relocs.get(i as usize).copied())
.collect(),
false,
),
}
}
pub fn copy(data: &[u8], relax: &SectionRelax, out: &mut [u8], fill: fn(&mut [u8])) {
let mut from = 0usize;
let mut to = 0usize;
let copy = |out: &mut [u8], from: usize, until: usize, to: &mut usize| {
let len = until.saturating_sub(from);
if let (Some(src), Some(dest)) = (
data.get(from..until),
out.get_mut(*to..to.saturating_add(len)),
) {
dest.copy_from_slice(src);
}
*to = to.saturating_add(len);
};
for edit in &relax.edits {
let Ok(at) = usize::try_from(edit.offset) else {
break;
};
if at < from {
continue;
}
copy(out, from, at, &mut to);
let remove = edit.remove as usize;
let mut kept = 0usize;
match edit.rewrite {
Rewrite::Align { addend } => {
let addend = addend as usize;
if !remove.is_multiple_of(4) || !addend.is_multiple_of(4) {
kept = addend.saturating_sub(remove);
if let Some(padding) = out.get_mut(to..to.saturating_add(kept)) {
fill(padding);
}
}
}
Rewrite::Replace { word, len, .. } => {
kept = usize::from(len);
let bytes = word.to_le_bytes();
if let (Some(dest), Some(src)) =
(out.get_mut(to..to.saturating_add(kept)), bytes.get(..kept))
{
dest.copy_from_slice(src);
}
}
Rewrite::Delete | Rewrite::Retype(_) => {}
}
to = to.saturating_add(kept);
from = at.saturating_add(kept).saturating_add(remove);
}
copy(out, from, data.len(), &mut to);
}
#[derive(Clone, Copy, Debug)]
struct Candidate {
id: SectionId,
file: usize,
section: u32,
}
fn candidates<F: crate::elf::read::ElfFormat>(input: &LayoutInput<'_, '_, F>) -> Vec<Candidate> {
let refs = &input.refs;
let per_file: Vec<Vec<Candidate>> = refs
.files
.par_iter()
.enumerate()
.map(|(file, input_file)| {
let Some(object) = &input_file.object else {
return Vec::new();
};
let mut found = Vec::new();
for (index, section) in object.sections.iter().enumerate() {
let Ok(index) = u32::try_from(index) else {
break;
};
if section.relocs == 0
|| section.header.sh_flags & (SHF_ALLOC | SHF_EXECINSTR)
!= (SHF_ALLOC | SHF_EXECINSTR)
|| !matches!(section.kind, SectionKind::Regular)
|| section.is_nobits()
|| !refs.sections.is_live_in(file, index)
{
continue;
}
if let Some(id) = refs.sections.id(file, index) {
found.push(Candidate {
id,
file,
section: index,
});
}
}
found
})
.collect();
per_file.into_iter().flatten().collect()
}
pub fn layout<'a, F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, 'a, F>,
inner: &dyn Fn(&LayoutInput<'_, 'a, F>) -> Result<Layout<'a>>,
) -> Result<Layout<'a>> {
let candidates = candidates(input);
let mut state = Relaxation::default();
if candidates.is_empty() {
let round = LayoutInput {
relax: Some(&state),
..*input
};
return inner(&round);
}
let prepared: Vec<Result<Option<SectionInput<'_, 'a, F>>>> = candidates
.par_iter()
.map(|candidate| prepare(input, *candidate))
.collect();
let mut sections = Vec::with_capacity(prepared.len());
for section in prepared {
sections.extend(section?);
}
for pass in 0..MAX_PASSES {
let round = LayoutInput {
relax: Some(&state),
..*input
};
let mut layout = inner(&round)?;
layout.relax = state;
let next = relax_pass(input, &layout, &mut sections, pass)?;
if next.same_shape(&layout.relax) {
layout.relax = next;
return Ok(layout);
}
state = next;
}
Err(Error::Internal("linker relaxation did not converge".into()))
}
pub struct Pass<'p, 'x, 'a, F: crate::elf::read::ElfFormat = crate::elf::read::Elf64Le> {
pub addresses: Addresses<'x, 'a, F>,
pub context: Context,
pub relax: bool,
pub tp: Option<u64>,
pub gp: Option<u64>,
pub previous: &'p Relaxation,
pub pass: u32,
}
impl<F: crate::elf::read::ElfFormat> Pass<'_, '_, '_, F> {
fn resolve(&self, file: usize, symbol: u32, addend: i64, branch: bool) -> Place {
let addresses = &self.addresses;
let refs = &addresses.refs;
let Some(target) = refs.target(file, symbol as usize) else {
return Place::Unknown;
};
let owner = Addresses::<F>::owner(&target, file, symbol);
if branch {
if target.is_ifunc() && addresses.iplt_address(owner).is_some() {
return Place::Iplt { owner, addend };
}
let flags = target
.global
.map_or(SymbolFlags::EMPTY, |id| refs.symbols.flags(id));
if flags.contains(SymbolFlags::NEEDS_PLT | PREEMPTIBLE)
&& addresses.plt_address(owner).is_some()
{
return Place::Plt { owner, addend };
}
}
match target.def {
Def::Section {
file,
section,
value,
} => {
let kind = refs.sections.kind_in(file, section);
let section_symbol = target.is_section_symbol();
if kind == Some(SectionKind::Merge) && section_symbol {
return match value.checked_add_signed(addend) {
Some(offset) => Place::Offset {
file,
section,
value: offset,
addend: 0,
},
None => Place::Unknown,
};
}
if kind == Some(SectionKind::Regular)
&& let Some(id) = refs.sections.id(file, section)
&& refs.sections.is_live(id)
{
if section_symbol && let Some(offset) = value.checked_add_signed(addend) {
return Place::Section {
id,
value: offset,
addend: 0,
};
}
return Place::Section { id, value, addend };
}
Place::Offset {
file,
section,
value,
addend,
}
}
Def::Absolute(value) => Place::Absolute(value.wrapping_add_signed(addend)),
Def::Undefined { weak: true } => Place::Absolute(addend as u64),
_ => Place::Unknown,
}
}
fn address(&self, place: Place) -> Option<u64> {
let addresses = &self.addresses;
match place {
Place::Unknown => None,
Place::Absolute(value) => Some(value),
Place::Section { id, value, addend } => {
let layout = addresses.layout;
if layout.section_shndx.get(id.index()).copied().unwrap_or(0) == 0 {
return None;
}
Some(
layout
.section_addr
.get(id.index())?
.wrapping_add(self.previous.map(id, value))
.wrapping_add_signed(addend),
)
}
Place::Offset {
file,
section,
value,
addend,
} => Some(
addresses
.section_offset_address(file, section, value)?
.wrapping_add_signed(addend),
),
Place::Plt { owner, addend } => {
Some(addresses.plt_address(owner)?.wrapping_add_signed(addend))
}
Place::Iplt { owner, addend } => {
Some(addresses.iplt_address(owner)?.wrapping_add_signed(addend))
}
}
}
#[must_use]
pub fn tls_mode(&self, file: usize, symbol: u32) -> TlsMode {
let refs = &self.addresses.refs;
let Some(target) = refs.target(file, symbol as usize) else {
return TlsMode::Dynamic;
};
let flags = target
.global
.map_or(SymbolFlags::EMPTY, |id| refs.symbols.flags(id));
reloc::classify_context(&self.context, &target, flags).tls
}
#[must_use]
pub fn previous_edit(&self, id: SectionId, seq: u32) -> Option<&Edit> {
self.previous.section(id)?.edit(seq)
}
}
pub struct SectionInput<'s, 'a, F: crate::elf::read::ElfFormat = crate::elf::read::Elf64Le> {
pub id: SectionId,
pub file: usize,
pub input: &'s ElfInput<'a, F>,
pub object: &'s ObjectInput<'a, F>,
pub section: &'s InputSection<'a>,
pub data: &'a [u8],
pub relocs: Vec<Relocation>,
order: Option<Vec<u32>>,
pub address: u64,
targets: Vec<OnceCell<Place>>,
}
impl<F: crate::elf::read::ElfFormat> SectionInput<'_, '_, F> {
#[must_use]
pub fn target(&self, pass: &Pass<'_, '_, '_, F>, seq: u32, branch: bool) -> Option<u64> {
let rel = self.relocs.get(seq as usize)?;
let place = *self
.targets
.get(seq as usize)?
.get_or_init(|| pass.resolve(self.file, rel.symbol, rel.addend, branch));
pass.address(place)
}
#[must_use]
pub fn index_of(&self, seq: u32) -> u32 {
self.order
.as_ref()
.and_then(|o| o.get(seq as usize).copied())
.unwrap_or(seq)
}
#[must_use]
pub fn malformed(&self, offset: u64, what: String) -> Error {
Error::Malformed {
file: self.input.path(),
member: self.input.member(),
offset: self.section.header.sh_offset.saturating_add(offset),
what,
}
}
}
#[derive(Debug, Default)]
pub struct Edits {
edits: Vec<Edit>,
delta: u64,
}
impl Edits {
#[must_use]
pub fn delta(&self) -> u64 {
self.delta
}
pub fn push<F: crate::elf::read::ElfFormat>(
&mut self,
section: &SectionInput<'_, '_, F>,
seq: u32,
offset: u64,
remove: u32,
rewrite: Rewrite,
) {
self.delta = self.delta.saturating_add(u64::from(remove));
self.edits.push(Edit {
seq,
index: section.index_of(seq),
offset,
remove,
delta: self.delta,
rewrite,
});
}
}
fn relax_pass<'a, F: crate::elf::read::ElfFormat>(
input: &LayoutInput<'_, 'a, F>,
layout: &Layout<'a>,
sections: &mut [SectionInput<'_, 'a, F>],
pass: u32,
) -> Result<Relaxation> {
let commons = Commons::default();
let arch = input.synth.arch;
let context = Pass {
addresses: Addresses {
refs: input.refs,
layout,
merged: input.merged,
eh_frames: input.eh_frames,
synth: input.synth,
commons: &commons,
globals: Vec::new(),
},
context: Context {
mode: input.mode,
relax: input.options.relax,
copy_relocs: input.options.copy_relocs,
arch,
weak_zero: Context::weak_zero(arch, input.mode),
},
relax: input.options.relax,
tp: layout.tls.map(|tls| tls.tp(arch)),
gp: match arch {
Arch::RiscV64 | Arch::RiscV32 if input.options.relax_gp => {
super::riscv::relax::global_pointer(input, layout)
}
_ => None,
},
previous: &layout.relax,
pass,
};
let results: Vec<Result<Option<SectionRelax>>> = sections
.par_iter_mut()
.map(|section| relax_section(&context, section))
.collect();
let mut sections = Vec::new();
for result in results {
if let Some(section) = result? {
sections.push(section);
}
}
Ok(Relaxation::new(
input.refs.sections.len(),
sections,
deleted_type(arch),
))
}
fn prepare<'s, 'a, F: crate::elf::read::ElfFormat>(
input: &'s LayoutInput<'_, 'a, F>,
candidate: Candidate,
) -> Result<Option<SectionInput<'s, 'a, F>>> {
let Some(file) = input.refs.files.get(candidate.file) else {
return Ok(None);
};
let Some(object) = &file.object else {
return Ok(None);
};
let Some(section) = object.section(candidate.section) else {
return Ok(None);
};
let Some(relocations) = object
.section(section.relocs)
.map(|r| object.elf.relocation_section(section.relocs, &r.header))
.transpose()?
.flatten()
else {
return Ok(None);
};
let Relocations::Rela(relas) = relocations.relocations else {
return Ok(None);
};
let relocs: Vec<Relocation> = relas.iter().collect();
let offsets: Vec<u64> = relocs.iter().map(|r| r.offset).collect();
let order = sorted_order(&offsets);
let relocs: Vec<Relocation> = match &order {
Some(order) => order
.iter()
.filter_map(|&i| relocs.get(i as usize).copied())
.collect(),
None => relocs,
};
let targets = std::iter::repeat_with(OnceCell::new)
.take(relocs.len())
.collect();
Ok(Some(SectionInput {
id: candidate.id,
file: candidate.file,
input: file,
object,
section,
data: object.section_data(section)?,
relocs,
order,
address: 0,
targets,
}))
}
fn relax_section<F: crate::elf::read::ElfFormat>(
pass: &Pass<'_, '_, '_, F>,
section: &mut SectionInput<'_, '_, F>,
) -> Result<Option<SectionRelax>> {
let layout = pass.addresses.layout;
let index = section.id.index();
if layout.section_shndx.get(index).copied().unwrap_or(0) == 0 {
return Ok(None);
}
section.address = layout.section_addr.get(index).copied().unwrap_or(0);
let edits = decide(pass, section)?;
if edits.edits.is_empty() {
return Ok(None);
}
Ok(Some(SectionRelax {
id: section.id,
edits: edits.edits,
sorted: section.order.is_none(),
}))
}
#[must_use]
pub fn applies(arch: Arch) -> bool {
arch.is_riscv()
}
fn decide<F: crate::elf::read::ElfFormat>(
pass: &Pass<'_, '_, '_, F>,
section: &SectionInput<'_, '_, F>,
) -> Result<Edits> {
match pass.context.arch {
Arch::RiscV64 | Arch::RiscV32 => super::riscv::relax::decide(pass, section),
_ => Ok(Edits::default()),
}
}
fn deleted_type(arch: Arch) -> u32 {
match arch {
Arch::RiscV64 | Arch::RiscV32 => crate::elf::read::consts::riscv::R_RISCV_RELAX,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn edit(seq: u32, offset: u64, remove: u32, delta: u64, rewrite: Rewrite) -> Edit {
Edit {
seq,
index: seq,
offset,
remove,
delta,
rewrite,
}
}
fn jump(len: u8) -> Rewrite {
Rewrite::Replace {
word: 0x6f,
len,
r_type: 17,
}
}
#[test]
fn offsets_move_by_the_bytes_deleted_before_them() {
let section = SectionRelax {
id: SectionId::new(0),
sorted: true,
edits: vec![
edit(0, 0x10, 4, 4, jump(4)),
edit(3, 0x20, 0, 4, Rewrite::Retype(0x100)),
edit(5, 0x30, 6, 10, jump(2)),
],
};
assert_eq!(section.map(0x8), 0x8);
assert_eq!(section.map(0x10), 0x10, "a relaxed call keeps its start");
assert_eq!(section.map(0x18), 0x14);
assert_eq!(section.map(0x30), 0x2c);
assert_eq!(section.map(0x38), 0x2e);
assert_eq!(section.removed(), 10);
assert_eq!(
section.edit(3).map(|e| e.rewrite),
Some(Rewrite::Retype(0x100))
);
assert_eq!(section.edit(4), None);
let state = Relaxation::new(4, vec![section], 51);
assert_eq!(state.map(SectionId::new(0), 0x38), 0x2e);
assert_eq!(state.map(SectionId::new(1), 0x38), 0x38);
assert_eq!(state.symbol_size(SectionId::new(0), 0x10, 0x28), 0x1e);
assert_eq!(state.emitted_type(SectionId::new(0), 0, 19), 17);
assert_eq!(state.emitted_type(SectionId::new(0), 1, 19), 19);
assert_eq!(Relaxation::default().removed(SectionId::new(0)), 0);
}
#[test]
fn order_is_by_offset() {
assert_eq!(sorted_order(&[0, 4, 4, 8]), None);
assert_eq!(sorted_order(&[8, 0, 4]), Some(vec![1, 2, 0]));
}
#[test]
fn copy_drops_and_rewrites_bytes() {
let mut data = Vec::new();
data.extend_from_slice(&0x0000_0097u32.to_le_bytes());
data.extend_from_slice(&0x0000_80e7u32.to_le_bytes());
data.extend_from_slice(&[0x13, 0, 0, 0, 1, 0]);
data.extend_from_slice(&0x1234_5678u32.to_le_bytes());
let relax = SectionRelax {
id: SectionId::new(0),
sorted: true,
edits: vec![
edit(0, 0, 4, 4, jump(4)),
edit(2, 8, 2, 6, Rewrite::Align { addend: 6 }),
],
};
let mut out = vec![0xaa; data.len() - 6];
copy(&data, &relax, &mut out, |p| p.fill(0xee));
let mut expect = Vec::new();
expect.extend_from_slice(&0x6fu32.to_le_bytes());
expect.extend_from_slice(&[0xee; 4]);
expect.extend_from_slice(&0x1234_5678u32.to_le_bytes());
assert_eq!(out, expect);
}
}