#![deny(clippy::arithmetic_side_effects)]
use crate::elf::read::Relocation;
use crate::elf::read::consts::{SHF_ALLOC, SHF_WRITE, STT_FUNC, STT_GNU_IFUNC, STT_TLS};
use crate::symbols::SymbolFlags;
use super::arch::{
Arch, Class, ClassifyContext, ClassifyError, DynKind, GotKind, Kind, TlsMode, Width,
};
use super::export::{Mode, PREEMPTIBLE};
use super::refs::{Def, Target};
use super::scan::NEEDS_IPLT;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Dynamic {
None,
Relative,
Symbolic(DynKind),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Problem {
NeedsPic,
LocalExecTls,
NoCopyReloc,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LocalNeed {
None,
Got,
TlsGd,
GotTpOff,
TlsDesc,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Decision {
pub class: Class,
pub dynamic: Dynamic,
pub text: bool,
pub flags: SymbolFlags,
pub local: LocalNeed,
pub tls_ld: bool,
pub problem: Option<Problem>,
}
#[derive(Clone, Copy, Debug)]
pub struct Context {
pub mode: Mode,
pub relax: bool,
pub copy_relocs: bool,
pub arch: Arch,
pub weak_zero: bool,
}
impl Context {
#[must_use]
pub fn weak_zero(arch: Arch, mode: Mode) -> bool {
mode.executable() && matches!(arch, Arch::I386 | Arch::X32 | Arch::X86_64)
}
}
#[must_use]
pub fn packable(align: u64, offset: u64) -> bool {
align >= 2 && offset & 1 == 0
}
#[derive(Clone, Copy, Debug)]
struct Props {
global: bool,
preemptible: bool,
shared: bool,
defined: bool,
absolute: bool,
function: bool,
local_ifunc: bool,
undefined_weak: bool,
tls: bool,
}
fn props(target: &Target, flags: SymbolFlags) -> Props {
let global = target.global.is_some();
let preemptible = global && flags.contains(PREEMPTIBLE);
let kind = target.raw.map_or(0, |r| r.kind());
let section = matches!(target.def, Def::Section { .. });
Props {
global,
preemptible,
shared: matches!(target.def, Def::Shared(_)),
defined: section
|| matches!(
target.def,
Def::Absolute(_) | Def::Common(_) | Def::Linker(_)
),
absolute: matches!(target.def, Def::Absolute(_))
|| flags.contains(super::defined::ABSOLUTE),
function: kind == STT_FUNC || kind == STT_GNU_IFUNC,
local_ifunc: kind == STT_GNU_IFUNC && section && !preemptible,
undefined_weak: matches!(target.def, Def::Undefined { weak: true }),
tls: kind == STT_TLS,
}
}
#[must_use]
pub fn classify_context(context: &Context, target: &Target, flags: SymbolFlags) -> ClassifyContext {
classify_context_of(context, &props(target, flags))
}
fn classify_context_of(context: &Context, p: &Props) -> ClassifyContext {
let mode = context.mode;
let tls = if !mode.dynamic {
TlsMode::LocalExec
} else if mode.shared {
TlsMode::Dynamic
} else if p.preemptible {
TlsMode::InitialExec
} else {
TlsMode::LocalExec
};
ClassifyContext {
relax_got: context.relax
&& p.defined
&& !p.preemptible
&& !p.local_ifunc
&& !(p.absolute && mode.pic),
pic: mode.pic,
tls,
tls_ld: if mode.shared {
TlsMode::Dynamic
} else {
TlsMode::LocalExec
},
code: false,
tls_symbol: p.tls,
}
}
#[cold]
#[inline(never)]
fn relax_weak_zero(context: &Context, target: &Target, classify: &mut ClassifyContext) {
if context.relax
&& !context.mode.dynamic
&& !context.mode.pic
&& matches!(target.def, Def::Undefined { weak: true })
{
classify.relax_got = true;
}
}
#[inline(always)]
pub fn decide<F: crate::elf::read::ElfFormat>(
context: &Context,
rel: &Relocation,
data: &[u8],
target: &Target,
flags: SymbolFlags,
section_flags: u64,
) -> Result<Decision, ClassifyError> {
let p = props(target, flags);
let mut classify = classify_context_of(context, &p);
classify.code = section_flags & crate::elf::read::consts::SHF_EXECINSTR != 0;
if F::WORD_SIZE == 4 && context.weak_zero {
relax_weak_zero(context, target, &mut classify);
}
let class = context
.arch
.classify(rel.r_type, rel.addend, data, rel.offset, classify)?;
let mode = context.mode;
let mut decision = Decision {
class,
dynamic: Dynamic::None,
text: false,
flags: SymbolFlags::EMPTY,
local: LocalNeed::None,
tls_ld: false,
problem: None,
};
if section_flags & SHF_ALLOC == 0 {
return Ok(decision);
}
let need = |decision: &mut Decision, global: SymbolFlags, local: LocalNeed| {
if p.global {
decision.flags |= global;
} else {
decision.local = local;
}
};
if p.local_ifunc && p.global {
decision.flags |= NEEDS_IPLT;
}
if class.needs_got() {
match class.slot {
GotKind::Address => need(&mut decision, SymbolFlags::NEEDS_GOT, LocalNeed::Got),
GotKind::TpOff => need(
&mut decision,
SymbolFlags::NEEDS_GOTTPOFF,
LocalNeed::GotTpOff,
),
GotKind::TlsGd => need(&mut decision, SymbolFlags::NEEDS_TLSGD, LocalNeed::TlsGd),
GotKind::TlsDesc => need(
&mut decision,
SymbolFlags::NEEDS_TLSDESC,
LocalNeed::TlsDesc,
),
GotKind::TlsLd => decision.tls_ld = true,
}
}
match class.kind {
Kind::GdToIe | Kind::DescToIe => {
need(
&mut decision,
SymbolFlags::NEEDS_GOTTPOFF,
LocalNeed::GotTpOff,
);
}
Kind::TpOff => {
if mode.dynamic && (mode.shared || p.preemptible) {
decision.problem = Some(Problem::LocalExecTls);
}
}
Kind::Pc | Kind::Page => {
if p.preemptible {
if context.arch.is_branch(rel.r_type) {
decision.flags |= SymbolFlags::NEEDS_PLT;
} else {
direct_reference(&mut decision, context, p);
}
} else if p.undefined_weak && mode.dynamic && context.arch.is_branch(rel.r_type) {
decision.flags |= SymbolFlags::NEEDS_PLT;
}
}
Kind::Abs => {
match class.width {
Width::Field(field) if !field.is_data() => return Ok(decision),
Width::RiscV(field) if field.is_label_math() => return Ok(decision),
_ => {}
}
if !mode.dynamic {
return Ok(decision);
}
let writable = section_flags & SHF_WRITE != 0;
let word = if F::WORD_SIZE == 8 {
class.width == Width::W64
} else {
context.arch.is_word(class.width)
};
if word {
if !p.preemptible {
if mode.pic && (p.defined || !p.global) && !p.absolute {
decision.dynamic = Dynamic::Relative;
}
} else if context.weak_zero && p.undefined_weak {
} else if mode.shared || writable || !p.shared {
decision.dynamic = Dynamic::Symbolic(DynKind::Abs64);
decision.flags |= SymbolFlags::NEEDS_DYNSYM;
} else {
direct_reference(&mut decision, context, p);
}
} else if p.preemptible {
if mode.shared {
decision.problem = Some(Problem::NeedsPic);
} else {
direct_reference(&mut decision, context, p);
}
} else if mode.pic && !p.absolute && (p.defined || !p.global) {
decision.problem = Some(Problem::NeedsPic);
}
decision.text = decision.dynamic != Dynamic::None && !writable;
}
_ => {}
}
Ok(decision)
}
fn direct_reference(decision: &mut Decision, context: &Context, p: Props) {
if context.mode.shared {
decision.problem = Some(Problem::NeedsPic);
} else if p.shared {
if p.function {
decision.flags |= SymbolFlags::NEEDS_PLT | SymbolFlags::NEEDS_CANONICAL_PLT;
} else if context.copy_relocs {
decision.flags |= SymbolFlags::NEEDS_COPY_RELOC;
} else {
decision.problem = Some(Problem::NoCopyReloc);
}
} else if !p.undefined_weak {
decision.problem = Some(Problem::NeedsPic);
}
}