use std::time::Instant;
use rayon::prelude::*;
use crate::args::{LinkOptions, OutputKind, StripMode};
use crate::debug::tombstone::{Style as TombstoneStyle, Tombstones};
use crate::diag::{Diagnostic, DiagnosticSink};
use crate::elf::read::{Elf32Le, Elf64Be, Elf64Le, ElfKind};
use crate::error::{Error, Result};
use crate::input::FileTable;
use crate::passes::IcfMode;
#[cfg(doc)]
use crate::symbols::resolve_symbols_with;
use crate::symbols::{SymbolName, SymbolTable};
use super::common;
use super::defined;
use super::dso;
use super::dynsym;
use super::ehframe;
use super::export::{self, Mode};
use super::gc;
use super::icf;
use super::inputs::{self, InternalNames, parse_number};
use super::layout::{self, LayoutInput, TrailerSizes};
use super::map;
use super::merge;
use super::object::{ParseConfig, SectionKind, WrapTable};
use super::place;
use super::refs::{Def, Refs};
use super::reloc;
use super::relocatable;
use super::resolve::{self, ElfRules};
use super::rules::RuleSet;
use super::scan::{self, UndefinedRef};
use super::sections::Sections;
use super::symtab;
use super::synth::{self, Synth};
use super::values::Addresses;
use super::write::{self, WriteInput};
use super::xref;
const MAX_REFERENCES: usize = 3;
pub fn link(options: &LinkOptions, diagnostics: &dyn DiagnosticSink) -> Result<()> {
let prepared = super::script_layout::prepare(options)?;
check_supported(&prepared.options)?;
match input_kind(&prepared.options) {
ElfKind::Elf64Le => link_as::<Elf64Le>(&prepared, diagnostics),
ElfKind::Elf32Le => link_as::<Elf32Le>(&prepared, diagnostics),
ElfKind::Elf64Be => link_as::<Elf64Be>(&prepared, diagnostics),
kind @ ElfKind::Elf32Be => Err(Error::Unimplemented(format!(
"big-endian ELF output ({kind:?}) needs a big-endian architecture (M4)"
))),
}
}
pub(crate) fn input_kind(options: &LinkOptions) -> ElfKind {
if let Some(arch) = options.target.and_then(super::arch::Arch::from_target) {
return arch.kind();
}
options
.inputs
.iter()
.find_map(|input| match &input.kind {
crate::args::InputKind::File(path) => sniff_file(path),
crate::args::InputKind::Bytes { data, .. } => sniff_bytes(data),
_ => None,
})
.or_else(|| {
super::arch::Arch::from_target(super::target::default_target())
.map(super::arch::Arch::kind)
})
.unwrap_or(ElfKind::Elf64Le)
}
fn sniff_bytes(data: &[u8]) -> Option<ElfKind> {
let one = |data: &[u8]| {
let neutral = data.get(18..20) == Some(&[0, 0]);
let elf = ElfKind::identify(data).filter(|_| !neutral);
elf.or_else(|| {
super::target::of_bitcode(data)
.and_then(super::arch::Arch::from_target)
.map(super::arch::Arch::kind)
})
};
if let Some(kind) = one(data) {
return Some(kind);
}
let archive = crate::input::archive::Archive::parse(std::path::Path::new(""), data).ok()?;
archive
.members()
.filter_map(core::result::Result::ok)
.find_map(|member| member.bytes().and_then(one))
}
fn sniff_file(path: &std::path::Path) -> Option<ElfKind> {
use std::io::Read;
let mut file = std::fs::File::open(path).ok()?;
let mut head = [0u8; 8];
file.read_exact(&mut head).ok()?;
if let Some(kind) = ElfKind::identify(&head) {
return Some(kind);
}
let bitcode =
head.starts_with(b"BC\xc0\xde") || head.starts_with(&0x0b17_c0de_u32.to_le_bytes());
if &head != b"!<arch>\n" && !bitcode {
return None;
}
let data = std::fs::read(path).ok()?;
sniff_bytes(&data)
}
fn link_as<F: crate::elf::read::ElfFormat>(
prepared: &super::script_layout::Prepared,
diagnostics: &dyn DiagnosticSink,
) -> Result<()> {
let options = &prepared.options;
if <F::Endian as crate::elf::read::Endian>::ENDIANNESS == crate::target::Endianness::Big
&& (options.gdb_index || options.debug_names)
{
return Err(Error::Unimplemented(
"--gdb-index and --debug-names for big-endian output".into(),
));
}
if options.kind == crate::args::OutputKind::Relocatable && F::KIND != ElfKind::Elf64Le {
return Err(Error::Unimplemented(format!(
"relocatable output (-r) for {:?} (roadmap M4: more ELF architectures)",
F::KIND
)));
}
let timing = options.timing.as_ref();
let start = Instant::now();
let lap = |what: &str| {
if let Some(timing) = timing {
timing.write_line(&format!(
"qld: {what}: {:.1} ms",
start.elapsed().as_secs_f64() * 1000.0
));
}
};
let wrap = WrapTable::new(&options.wrap);
let mut internal = InternalNames::new(options);
prepared.add_internal_names(&mut internal.names);
let mut script = prepared.script.as_ref();
let table = FileTable::for_link(options);
let config = ParseConfig {
strip_debug: options.strip >= StripMode::Debug,
wrap: &wrap,
table: &table,
};
let caller_pool = options.threads.is_none() && rayon::current_thread_index().is_some();
let own_pools = options.threads.is_none() && !caller_pool;
let mut narrow = Narrow {
pool: (options.threads.is_some() && rayon::current_num_threads() > MAX_DEFAULT_THREADS)
.then(|| thread_pool(MAX_DEFAULT_THREADS))
.transpose()?,
widen: None,
};
let mut inputs = if own_pools {
let threads = available_threads().min(INPUT_THREADS);
thread_pool(threads)?
.install(|| inputs::collect::<F>(options, &table, &internal, config))?
} else {
narrow.run(|| inputs::collect::<F>(options, &table, &internal, config))?
};
lap("inputs");
if let Some(default) = &prepared.relocatable_default
&& super::arch::Arch::of(options, &inputs.files) == super::arch::Arch::X86_64
{
script = Some(default);
}
options.check_cancelled()?;
let threads = input_sized_threads::<F>(options, &table, own_pools);
if own_pools
&& threads == Some(MAX_DEFAULT_THREADS)
&& available_threads() > MAX_DEFAULT_THREADS
{
narrow.widen = Some(available_threads());
}
match threads {
Some(threads) => thread_pool(threads)?.install(|| {
link_inputs(
options,
diagnostics,
&mut inputs,
&internal,
script,
&lap,
&narrow,
)
}),
None => link_inputs(
options,
diagnostics,
&mut inputs,
&internal,
script,
&lap,
&narrow,
),
}
}
struct Narrow {
pool: Option<rayon::ThreadPool>,
widen: Option<usize>,
}
impl Narrow {
fn run<R: Send>(&self, op: impl FnOnce() -> R + Send) -> R {
match &self.pool {
Some(pool) => pool.install(op),
None => op(),
}
}
}
const INPUT_THREADS: usize = 16;
fn available_threads() -> usize {
std::thread::available_parallelism().map_or(1, std::num::NonZeroUsize::get)
}
fn thread_pool(threads: usize) -> Result<rayon::ThreadPool> {
rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.build()
.map_err(|e| Error::Internal(format!("cannot create thread pool: {e}")))
}
fn check_supported(options: &LinkOptions) -> Result<()> {
let unimplemented = |what: &str, milestone: &str| {
Err(Error::Unimplemented(format!(
"{what} (roadmap {milestone})"
)))
};
if let Some(format) = &options.output_format
&& !matches!(
format.name(),
"elf64-x86-64" | "elf64-x86_64" | "binary" | "ihex" | "srec"
)
{
return unimplemented(&format!("--oformat {}", format.name()), "M4");
}
if options.kind == OutputKind::Relocatable
&& options.compress_debug_sections != crate::args::DebugCompression::None
{
return unimplemented("--compress-debug-sections with -r", "M5");
}
if options.kind == OutputKind::Relocatable {
if options.gdb_index {
return Err(Error::Option(
"-r and --gdb-index may not be used together".into(),
));
}
if options.debug_names {
return Err(Error::Option(
"-r and --debug-names may not be used together".into(),
));
}
if options.symbol_ordering_file.is_some()
|| options.call_graph_ordering_file.is_some()
|| options
.call_graph_profile_sort
.is_some_and(|s| s != crate::args::options::CallGraphSort::None)
{
return unimplemented("section ordering with -r", "M5");
}
}
if options.separate_debug_file.is_some()
&& (options.kind == OutputKind::Relocatable
|| options
.output_format
.as_ref()
.is_some_and(crate::args::OutputFormat::is_raw))
{
return Err(Error::Option(
"--separate-debug-file needs an ELF executable or shared object output".into(),
));
}
for (name, expr) in &options.defsym {
defined::defsym_assignment(name, expr)?;
}
Ok(())
}
const BYTES_PER_THREAD: u64 = 4 << 20;
const MAX_DEFAULT_THREADS: usize = 16;
fn input_sized_threads<F: crate::elf::read::ElfFormat>(
options: &LinkOptions,
table: &FileTable,
own_pools: bool,
) -> Option<usize> {
if options.threads.is_some() {
return None;
}
let top_level = || table.iter().filter(|(_, file)| file.parent().is_none());
let mut bytes: u64 = top_level()
.map(|(_, file)| u64::try_from(file.data().len()).unwrap_or(u64::MAX))
.fold(0u64, u64::saturating_add);
let most = BYTES_PER_THREAD.saturating_mul(MAX_DEFAULT_THREADS as u64);
if bytes < most {
for (_, file) in top_level() {
bytes = bytes.saturating_add(compressed_growth::<F>(file));
if bytes >= most {
break;
}
}
}
let wanted = usize::try_from(bytes.div_ceil(BYTES_PER_THREAD))
.unwrap_or(usize::MAX)
.clamp(1, MAX_DEFAULT_THREADS);
own_pools.then(|| wanted.min(available_threads()))
}
fn compressed_growth<F: crate::elf::read::ElfFormat>(file: &crate::input::InputFile) -> u64 {
use crate::elf::read::{ObjectFile, Source};
use crate::input::FileFormat;
if !matches!(file.format(), FileFormat::Elf(ident) if ident.is_relocatable()) {
return 0;
}
let Ok(object) = ObjectFile::<F>::parse(file.data(), Source::new(file.path())) else {
return 0;
};
object
.elf()
.sections()
.iter()
.filter(|header| header.is_compressed())
.filter_map(|header| {
let (chdr, _) = object.compressed_data(&header).ok()??;
Some(chdr.ch_size.saturating_sub(header.sh_size))
})
.fold(0u64, u64::saturating_add)
}
#[allow(clippy::too_many_lines)]
fn link_inputs<'a, F: crate::elf::read::ElfFormat>(
options: &LinkOptions,
diagnostics: &'a dyn DiagnosticSink,
inputs: &mut inputs::Inputs<'a, F>,
internal: &InternalNames,
script: Option<&'a super::script_layout::LayoutScript>,
lap: &(dyn Fn(&str) + Sync),
narrow: &Narrow,
) -> Result<()> {
let rules = ElfRules {
allow_multiple_definition: options.allow_multiple_definition,
};
let (mut symbols, resolution, lto) =
narrow.run(|| super::lto::resolve(options, diagnostics, &rules, inputs))?;
let files = &inputs.files;
narrow.run(|| dso::bind_unextracted(files, &symbols, &resolution));
lap("resolution");
options.check_cancelled()?;
let mut sections = narrow.run(|| Sections::new(files, &resolution))?;
let relocatable = options.kind == OutputKind::Relocatable;
if relocatable {
relocatable::revive_sections(files, &mut sections, options);
} else if options.emit_relocs {
relocatable::revive_named(files, &mut sections, b".note.GNU-stack");
}
if options
.output_format
.as_ref()
.is_some_and(crate::args::OutputFormat::is_raw)
{
relocatable::revive_named(files, &mut sections, b".note.gnu.property");
}
let (mut errors, cref) = narrow.run(|| {
resolve::deduplicate_comdat(files, &mut sections);
let errors = resolve::report_duplicates(
files,
&resolution,
§ions,
options.demangle,
diagnostics,
);
report_gnu_warnings(files, &symbols, diagnostics);
xref::trace_symbols(files, &resolution, options, diagnostics);
xref::warn_common(files, &resolution, options, diagnostics);
let cref = xref::cross_reference(files, &symbols, &resolution, options);
(errors, cref)
});
if relocatable {
if errors > 0 && !options.noinhibit_exec {
return Err(Error::Reported { errors });
}
link_relocatable(
options,
diagnostics,
files,
&symbols,
&resolution,
sections,
internal,
script,
lap,
)?;
map::write_cref(options, cref.as_deref())?;
lto.finish(diagnostics)?;
options.output_complete();
return Ok(());
}
let mode = Mode::new(options, files.iter().any(|f| f.shared.is_some()));
let rule_set = RuleSet::for_link(script, diagnostics, super::arch::Arch::of(options, files));
let (needed, mut placement) = narrow.run(|| {
rayon::join(
|| dso::plan_needed(files, &symbols, &rules, &resolution),
|| place::place(&rule_set, files, §ions, options),
)
});
if mode.dynamic && !mode.shared {
narrow.run(|| dso::mark_dependency_symbols(files, &symbols, &needed, options));
}
let always = defined::always_defined(mode, script.is_some(), files);
for name in &always {
symbols.intern(SymbolName::new(name.as_bytes()));
}
let always = always.as_slice();
for id in &placement.discarded {
if let Some(slot) = sections.live.get_mut(id.index()) {
*slot = false;
}
}
let linker = narrow
.run(|| defined::register(&symbols, files, &placement, options, mode.dynamic, always));
let (mut version_script, dynamic_patterns) = export::read_scripts(options)?;
if version_script.is_none()
&& let Some(nodes) = script.map(|s| &s.version).filter(|v| !v.is_empty())
{
version_script = Some(export::VersionScript::new(nodes)?);
}
let exports = narrow.run(|| {
export::plan(
files,
&symbols,
&resolution,
&needed,
options,
mode,
version_script,
&dynamic_patterns,
&linker,
)
})?;
lap("placement");
options.check_cancelled()?;
let mut eh_frames = if options.gc_sections {
Some(narrow.run(|| ehframe::split(files, §ions))?)
} else {
None
};
if let Some(eh_frames) = eh_frames.as_mut() {
let refs = Refs {
files,
symbols: &symbols,
resolution: &resolution,
sections: §ions,
};
let why_live = !options.why_live.is_empty();
let (removed, graph) = narrow
.run(|| gc::collect(&refs, &placement, eh_frames, &linker, internal, why_live))?;
if options.print_gc_sections {
gc::print_removed(&refs, &removed, diagnostics);
}
if let Some(graph) = &graph {
gc::report_why_live(&refs, graph, &options.why_live, diagnostics);
}
for id in &removed {
if let Some(slot) = sections.live.get_mut(id.index()) {
*slot = false;
}
}
eh_frames
.sections
.retain(|s| sections.live.get(s.id.index()).copied().unwrap_or(false));
narrow.run(|| placement.compute_flags(files, §ions));
lap("gc");
options.check_cancelled()?;
}
let refs = Refs {
files,
symbols: &symbols,
resolution: &resolution,
sections: §ions,
};
let arch = super::arch::Arch::of(options, files);
if arch.kind() != F::KIND {
return Err(Error::Unimplemented(format!(
"linking {:?} objects for {} (roadmap M4: more ELF architectures)",
F::KIND,
arch.emulation()
)));
}
let context = reloc::Context {
mode,
relax: options.relax,
copy_relocs: options.copy_relocs,
arch,
weak_zero: reloc::Context::weak_zero(arch, mode),
};
let ((scan, split), (merged, debug_indexes)) = rayon::join(
|| {
rayon::join(
|| scan::scan(&refs, &context),
|| match eh_frames {
Some(eh_frames) => Ok(eh_frames),
None => ehframe::split(files, §ions),
},
)
},
|| {
rayon::join(
|| {
merge::merge(
files,
§ions,
&placement,
options.optimize >= 2,
narrow.widen,
)
},
|| {
crate::debug::gdb_index::elf::DebugIndexes::build(
files,
&resolution,
§ions,
options,
)
},
)
},
);
let mut eh_frames = split?;
for file in &scan.files {
for error in &file.errors {
diagnostics.emit(error.clone());
errors = errors.saturating_add(1);
}
}
errors = errors.saturating_add(report_undefined(
&refs,
&scan,
options,
mode,
&needed,
diagnostics,
));
errors = errors.saturating_add(dso::check_shlib_undefined(
files,
&symbols,
&resolution,
&needed,
options,
diagnostics,
));
if scan.text_relocs() && mode.dynamic {
let message = if mode.shared {
"creating DT_TEXTREL in a shared object"
} else {
"creating DT_TEXTREL in a PIE"
};
if options.error_textrel {
diagnostics.emit(Diagnostic::error(message.to_string()));
errors = errors.saturating_add(1);
} else if options.warn_textrel || mode.pic {
diagnostics.emit(Diagnostic::warning(message.to_string()));
}
}
if errors > 0 && !options.noinhibit_exec {
return Err(Error::Reported { errors });
}
lap("scan");
options.check_cancelled()?;
let commons = narrow.run(|| common::allocate(&refs));
let merged = merged?;
lap("merge");
options.check_cancelled()?;
let icf_mode = match options.icf {
crate::args::IcfMode::All => Some(IcfMode::All),
crate::args::IcfMode::Safe => Some(IcfMode::Safe),
_ => None,
};
if let Some(icf_mode) = icf_mode {
let fold_into = icf::fold(
&refs,
&placement,
&merged,
icf_mode,
context.arch,
options.print_icf_sections,
diagnostics,
)?;
sections.apply_folding(fold_into);
lap("icf");
options.check_cancelled()?;
}
let mut debug_indexes = debug_indexes?;
debug_indexes.apply(files, &resolution, &mut sections, diagnostics);
let refs = Refs {
files,
symbols: &symbols,
resolution: &resolution,
sections: §ions,
};
let order = super::ordering::for_link(&refs, &placement, options, diagnostics)?;
let mut synth = Synth {
arch: context.arch,
bind_now: options.bind_now,
..Synth::default()
};
let nonempty_outputs = narrow.run(|| {
let (nonempty, ()) = rayon::join(
|| {
synth.plan_entries(&refs, &scan, mode);
nonempty_outputs(files, §ions, &placement)
},
|| eh_frames.finalize(&refs),
);
nonempty
});
narrow.run(|| synth.plan_entries(&refs, &scan, mode));
if context.arch == super::arch::Arch::Arm {
let default_order = script.is_none() && order.as_ref().is_none_or(|o| o.is_empty());
synth.arm = Some(Box::new(super::arch::arm::prepare(
&refs,
&placement,
default_order && options.merge_exidx_entries,
)));
}
synth.relr = options.pack_relative_relocs && mode.pic;
synth.relr_size = synth
.relr_count()
.div_ceil(32)
.saturating_add(8)
.saturating_mul(context.arch.kind().word_size());
synth.ibt = synth::plan_ibt(files, options);
synth.pac_plt = synth::plan_pac_plt(files, options);
for warning in synth::force_bti_warnings(files, options) {
diagnostics.emit(warning);
}
synth.build_id = synth::plan_build_id(options);
synth.property_note = synth::plan_property_note(files, options);
synth.interp = synth::plan_interp(options, mode, context.arch);
synth.fde_count = u64::try_from(eh_frames.live_fdes()).unwrap_or(0);
synth.eh_frame_hdr = options.eh_frame_hdr && synth.fde_count > 0;
synth.eh_frame_end = eh_frames.sections.iter().any(|s| s.size > 0);
synth.common = (commons.size, commons.align);
let has_output = |name: &[u8]| {
placement
.outputs
.iter()
.zip(&nonempty_outputs)
.any(|(output, &nonempty)| nonempty && output.name == name)
};
let soname = options
.soname
.as_ref()
.map(|s| s.as_bytes().to_vec())
.or_else(|| {
options
.output_path()
.file_name()
.map(|n| n.as_encoded_bytes().to_vec())
});
let input = dynsym::PlanInput {
refs: &refs,
needed: &needed,
mode,
options,
synth: &synth,
exports: &exports,
scan: &scan,
has_output: &has_output,
soname,
};
let chosen = narrow.run(|| dynsym::choose(&input));
let (dynamic, plan) = narrow.run(|| {
rayon::join(
|| dynsym::plan_chosen(&input, chosen),
|| symtab::plan(&refs, &linker, options, context.arch.kind()),
)
});
let dynamic = dynamic?;
synth.dynamic_sizes = dynamic.sizes();
synth.verneed_count = dynamic.verneed_count;
synth.verdef_count = dynamic.verdef_count;
lap("dynamic");
options.check_cancelled()?;
let mut trailers = TrailerSizes {
symtab: plan.symtab_size(),
strtab: if plan.is_empty() {
0
} else {
u64::try_from(plan.strtab_size).unwrap_or(u64::MAX)
},
first_global: u32::try_from(plan.first_global).unwrap_or(0),
debug_names: debug_indexes.debug_names_size(),
gdb_index: debug_indexes.gdb_index_size(),
..TrailerSizes::default()
};
let exec_stack = files
.iter()
.filter_map(|f| f.object.as_ref())
.any(|o| o.exec_stack);
let mut layout = narrow.run(|| {
layout::layout(&LayoutInput {
options,
refs,
rules: &rule_set,
files,
sections: §ions,
placement: &placement,
merged: &merged,
eh_frames: &eh_frames,
synth: &synth,
trailers,
exec_stack,
mode,
compressed: &[],
order: order.as_ref(),
relax: None,
})
})?;
let mut relr = Vec::new();
if synth.relr_count() > 0 {
let mut shrunk = false;
for attempt in 0..8 {
let addresses = Addresses::new(
refs, &layout, &merged, &eh_frames, &synth, &commons, &placement, &linker, options,
);
let places = write::relr_addresses(&addresses, &context, &dynamic, &scan);
relr = write::encode_relr(&places, context.arch.kind());
let size = u64::try_from(relr.len())
.unwrap_or(u64::MAX)
.saturating_mul(context.arch.kind().word_size());
if size == synth.relr_size || (size < synth.relr_size && shrunk) {
break;
}
if attempt == 7 {
if size <= synth.relr_size {
break;
}
return Err(Error::Internal(".relr.dyn size did not converge".into()));
}
shrunk |= size < synth.relr_size;
synth.relr_size = size;
layout = layout::layout(&LayoutInput {
options,
refs,
rules: &rule_set,
files,
sections: §ions,
placement: &placement,
merged: &merged,
eh_frames: &eh_frames,
synth: &synth,
trailers,
exec_stack,
mode,
compressed: &[],
order: order.as_ref(),
relax: None,
})?;
}
}
lap("layout");
options.check_cancelled()?;
let mut plan = plan;
plan.add_section_symbols(layout.section_symbols as usize);
let tombstones = Tombstones::new(TombstoneStyle::Lld)
.with_rules(
options
.dead_reloc_in_nonalloc
.iter()
.map(|(glob, value)| (glob.as_bytes(), *value)),
)
.map_err(|e| Error::Option(e.0))?;
let compression = {
let level = if options.optimize >= 2 {
crate::debug::compress::deflate::Level::DEFAULT
} else {
crate::debug::compress::deflate::Level::FASTEST
};
crate::debug::section::OutputCompression::from_option(
options.compress_debug_sections,
level,
)
};
let mut prerendered = Vec::new();
if let Some(compression) = compression {
let addresses = Addresses::new(
refs, &layout, &merged, &eh_frames, &synth, &commons, &placement, &linker, options,
);
prerendered = write::prerender_debug_sections(
&WriteInput {
options,
addresses: &addresses,
symtab: &plan,
linker: &linker,
dynamic: &dynamic,
scan: &scan,
context,
tombstones: &tombstones,
relr: &relr,
entry: 0,
prerendered: &[],
diagnostics,
},
compression,
)?;
let sizes: Vec<layout::CompressedOutput> = prerendered
.iter()
.filter(|p| p.compressed)
.filter_map(|p| {
let section = layout.sections.get(p.position as usize)?;
Some(layout::CompressedOutput {
output: section.output,
size: u64::try_from(p.bytes.len()).ok()?,
gnu: !compression.is_gabi(),
})
})
.collect();
if let Some(gnu) = debug_indexes.compress(&addresses, compression)? {
trailers.debug_names = debug_indexes.debug_names_size();
trailers.debug_names_compressed = Some(gnu);
}
drop(addresses);
if !sizes.is_empty() || trailers.debug_names_compressed.is_some() {
layout = layout::layout(&LayoutInput {
options,
refs,
rules: &rule_set,
files,
sections: §ions,
placement: &placement,
merged: &merged,
eh_frames: &eh_frames,
synth: &synth,
trailers,
exec_stack,
mode,
compressed: &sizes,
order: order.as_ref(),
relax: None,
})?;
}
lap("compress");
options.check_cancelled()?;
}
let addresses = narrow.run(|| {
Addresses::new(
refs, &layout, &merged, &eh_frames, &synth, &commons, &placement, &linker, options,
)
});
let entry = entry_address(&addresses, options, mode, diagnostics);
narrow.run(|| debug_indexes.render(&addresses, &mut prerendered))?;
let debug_path = super::separate_debug::debug_path(options);
if let Some(debug_path) = &debug_path {
narrow.run(|| map::write(options, &addresses, &plan, cref.as_deref()))?;
drop(addresses);
let stripped = super::separate_debug::stripped_sections(§ions, &placement);
let stripped_refs = Refs {
sections: &stripped,
..refs
};
let main_layout = narrow.run(|| {
layout::layout(&LayoutInput {
options,
refs: stripped_refs,
rules: &rule_set,
files,
sections: &stripped,
placement: &placement,
merged: &merged,
eh_frames: &eh_frames,
synth: &synth,
trailers: TrailerSizes {
debuglink: super::separate_debug::debuglink_size(debug_path),
..TrailerSizes::default()
},
exec_stack,
mode,
compressed: &[],
order: order.as_ref(),
relax: None,
})
})?;
let link = main_layout
.sections
.iter()
.position(|s| s.name == b".gnu_debuglink")
.and_then(|p| u32::try_from(p).ok())
.map(|position| write::Prerendered {
position,
bytes: super::separate_debug::debuglink_contents(debug_path),
compressed: false,
});
let main_addresses = Addresses::new(
stripped_refs,
&main_layout,
&merged,
&eh_frames,
&synth,
&commons,
&placement,
&linker,
options,
);
narrow.run(|| {
write::write(&WriteInput {
options,
addresses: &main_addresses,
symtab: &plan,
linker: &linker,
dynamic: &dynamic,
scan: &scan,
context,
tombstones: &tombstones,
relr: &relr,
entry,
prerendered: link.as_slice(),
diagnostics,
})
})?;
let (crc, build_id) = super::separate_debug::finish_output::<F>(
&options.output_path(),
&main_layout,
options,
)?;
drop(main_addresses);
super::separate_debug::to_debug_file(&mut layout)?;
let mut debug_options = options.clone();
debug_options.output = Some(debug_path.clone());
debug_options.build_id = crate::args::BuildId::None;
let debug_addresses = Addresses::new(
refs,
&layout,
&merged,
&eh_frames,
&synth,
&commons,
&placement,
&linker,
&debug_options,
);
narrow.run(|| {
write::write(&WriteInput {
options: &debug_options,
addresses: &debug_addresses,
symtab: &plan,
linker: &linker,
dynamic: &dynamic,
scan: &scan,
context,
tombstones: &tombstones,
relr: &relr,
entry,
prerendered: &prerendered,
diagnostics,
})
})?;
super::separate_debug::finish_debug_file(debug_path, &layout, build_id.as_deref(), crc)?;
} else {
narrow.run(|| {
write::write(&WriteInput {
options,
addresses: &addresses,
symtab: &plan,
linker: &linker,
dynamic: &dynamic,
scan: &scan,
context,
tombstones: &tombstones,
relr: &relr,
entry,
prerendered: &prerendered,
diagnostics,
})
})?;
narrow.run(|| map::write(options, &addresses, &plan, cref.as_deref()))?;
}
lap("write");
lto.finish(diagnostics)?;
options.output_complete();
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn link_relocatable<'a, F: crate::elf::read::ElfFormat>(
options: &LinkOptions,
diagnostics: &dyn DiagnosticSink,
files: &[inputs::ElfInput<'a, F>],
symbols: &SymbolTable<'a>,
resolution: &crate::symbols::Resolution<'a>,
mut sections: Sections,
internal: &InternalNames,
script: Option<&'a super::script_layout::LayoutScript>,
lap: &(dyn Fn(&str) + Sync),
) -> Result<()> {
let script_placement =
script.map(|s| super::script_layout::relocatable::place(s, files, &mut sections, options));
if script.is_some() {
lap("script placement");
}
if options.gc_sections {
if options.entry.is_none() && options.undefined.is_empty() {
return Err(Error::Option(
"--gc-sections requires a defined symbol root specified by -e or -u".into(),
));
}
let rule_set = RuleSet::default_rules_for(super::arch::Arch::of(options, files));
let default_placement;
let placement = match &script_placement {
Some(placement) => placement,
None => {
default_placement = place::place(&rule_set, files, §ions, options);
&default_placement
}
};
let eh_frames = ehframe::split(files, §ions)?;
let refs = Refs {
files,
symbols,
resolution,
sections: §ions,
};
let linker = defined::LinkerSymbols::default();
let why_live = !options.why_live.is_empty();
let (removed, graph) =
gc::collect(&refs, placement, &eh_frames, &linker, internal, why_live)?;
if options.print_gc_sections {
gc::print_removed(&refs, &removed, diagnostics);
}
if let Some(graph) = &graph {
gc::report_why_live(&refs, graph, &options.why_live, diagnostics);
}
for &id in &removed {
let consumed = sections
.locate(id)
.and_then(|(file, index)| files.get(file)?.object.as_ref()?.section(index))
.is_some_and(|s| s.kind == SectionKind::Ignored && !s.is_alloc());
if !consumed && let Some(slot) = sections.live.get_mut(id.index()) {
*slot = false;
}
}
lap("gc");
options.check_cancelled()?;
}
let refs = Refs {
files,
symbols,
resolution,
sections: §ions,
};
let commons = options.define_common.then(|| common::allocate(&refs));
let script_layout = match (script, &script_placement) {
(Some(script), Some(placement)) => Some(super::script_layout::relocatable::layout(
script, placement, files, §ions, symbols, options,
)?),
_ => None,
};
if script_layout.is_some() {
lap("script layout");
}
relocatable::write(&relocatable::RelocatableInput {
options,
refs,
commons: commons.as_ref(),
script: script_layout.as_ref(),
})?;
lap("write");
Ok(())
}
fn nonempty_outputs<F: crate::elf::read::ElfFormat>(
files: &[super::inputs::ElfInput<'_, F>],
sections: &Sections,
placement: &place::Placement<'_>,
) -> Vec<bool> {
let outputs = placement.outputs.len();
let filled: Vec<Vec<u32>> = files
.par_iter()
.enumerate()
.map(|(file_index, file)| {
let mut filled = Vec::new();
let Some(object) = &file.object else {
return filled;
};
for (index, section) in object.sections.iter().enumerate() {
let Some(id) = sections.id(file_index, u32::try_from(index).unwrap_or(u32::MAX))
else {
continue;
};
if section.header.sh_size == 0 || !sections.is_live(id) {
continue;
}
if let Some(output) = placement.output_of(id)
&& filled.last() != Some(&output)
{
filled.push(output);
}
}
filled
})
.collect();
let mut nonempty = vec![false; outputs];
for output in filled.into_iter().flatten() {
if let Some(slot) = nonempty.get_mut(output as usize) {
*slot = true;
}
}
nonempty
}
fn symbol_display(name: SymbolName<'_>, demangle: bool) -> String {
let base = crate::hints::display_symbol(name.bytes(), demangle);
match name.version() {
Some(version) => format!("{base}@{}", String::from_utf8_lossy(version)),
None => base.into_owned(),
}
}
fn undefined_hints<F: crate::elf::read::ElfFormat>(
refs: &Refs<'_, '_, F>,
groups: &[&[UndefinedRef]],
options: &LinkOptions,
needed: &dso::Needed,
) -> Vec<Vec<crate::hints::Hint>> {
use crate::hints::{Hinter, LinkedLibrary, SearchScope, Undefined};
let mut linked: Vec<LinkedLibrary> = Vec::new();
for (index, file) in refs.files.iter().enumerate() {
let library = match file.role {
inputs::InputRole::Shared => LinkedLibrary {
path: file.path(),
dropped_as_needed: !needed.is_needed(index),
static_only: false,
},
inputs::InputRole::Member => LinkedLibrary::new(file.path()),
_ => continue,
};
if !linked.iter().any(|l| l.path == library.path) {
linked.push(library);
}
}
let undefined: Vec<Undefined<'_>> = groups
.iter()
.filter_map(|g| g.first())
.map(|r| {
let name = refs.symbols.name(r.symbol);
match name.version() {
Some(version) => Undefined::versioned(name.bytes(), version),
None => Undefined::new(name.bytes()),
}
})
.collect();
let defined: Vec<&[u8]> = refs
.symbols
.ids()
.filter(|&id| {
matches!(
refs.symbols.definition_kind(id),
crate::symbols::DefinitionKind::Regular
| crate::symbols::DefinitionKind::Weak
| crate::symbols::DefinitionKind::Common
| crate::symbols::DefinitionKind::Shared
) && refs.symbols.name(id).version().is_none()
})
.map(|id| refs.symbols.name(id).bytes())
.collect();
Hinter::new(SearchScope::from_options(options), linked).hints(&undefined, &defined)
}
fn report_undefined<F: crate::elf::read::ElfFormat>(
refs: &Refs<'_, '_, F>,
scan: &scan::ScanResult,
options: &LinkOptions,
mode: Mode,
needed: &dso::Needed,
diagnostics: &dyn DiagnosticSink,
) -> usize {
if mode.shared && options.no_undefined != Some(true) {
return 0;
}
let mut all: Vec<UndefinedRef> = scan
.files
.iter()
.flat_map(|f| f.undefined.iter().copied())
.collect();
all.sort_unstable_by_key(|r| (r.symbol, r.file, r.section, r.offset));
let mut errors = 0usize;
let mut line_tables: std::collections::BTreeMap<
usize,
Option<crate::debug::dwarf::LineLookup>,
> = std::collections::BTreeMap::new();
let mut groups: Vec<&[UndefinedRef]> = all.chunk_by(|a, b| a.symbol == b.symbol).collect();
groups.sort_by_key(|group| group.first().map(|r| (r.file, r.section, r.offset)));
let ignore = matches!(
options.unresolved_symbols,
Some(
crate::args::UnresolvedSymbols::IgnoreAll
| crate::args::UnresolvedSymbols::IgnoreInObjectFiles
)
);
let names: Vec<&[u8]> = groups
.iter()
.filter_map(|g| g.first())
.map(|r| refs.symbols.name(r.symbol).bytes())
.collect();
let in_dependencies = if names.is_empty() || ignore {
Vec::new()
} else {
dso::defined_in_dependencies(refs.files, needed, options, &names)
};
let hints = if names.is_empty() || ignore {
Vec::new()
} else {
undefined_hints(refs, &groups, options, needed)
};
for (group_index, group) in groups.into_iter().enumerate() {
let Some(first) = group.first() else {
continue;
};
let name = refs.symbols.name(first.symbol);
if ignore
|| options
.ignore_unresolved_symbols
.iter()
.any(|s| s.as_bytes() == name.bytes())
{
continue;
}
let order = refs.files.get(first.file).map_or(0, |f| f.position.raw());
let shown = symbol_display(name, options.demangle);
let mut diagnostic = if options.warn_unresolved_symbols {
Diagnostic::warning(format!("undefined symbol: {shown}"))
} else {
Diagnostic::error(format!("undefined symbol: {shown}"))
};
diagnostic = diagnostic.order(order);
for reference in group.iter().take(MAX_REFERENCES) {
let mut location =
scan::location(refs, reference.file, reference.section, reference.offset);
let lookup = line_tables.entry(reference.file).or_insert_with(|| {
refs.files
.get(reference.file)
.and_then(|f| f.object.as_ref())
.and_then(|o| crate::debug::dwarf::LineLookup::parse(&o.elf).ok())
});
if let Some(lookup) = lookup {
location.source = lookup.find(reference.section, reference.offset);
}
diagnostic = diagnostic.at(location);
}
if group.len() > MAX_REFERENCES {
diagnostic = diagnostic.note(format!(
"referenced {} more times",
group.len().saturating_sub(MAX_REFERENCES)
));
}
if let Some(Some((library, needed_by))) = in_dependencies.get(group_index) {
diagnostic = diagnostic.note(format!(
"'{shown}' is defined in {}, which {} needs but which is not in the link \
(DSO missing from command line); add it to the command line",
library.display(),
needed_by.display()
));
}
if let Some(hints) = hints.get(group_index) {
diagnostic = crate::hints::attach(diagnostic, hints, options.demangle);
}
diagnostics.emit(diagnostic);
if !options.warn_unresolved_symbols {
errors = errors.saturating_add(1);
}
}
for name in &options.require_defined {
let defined = refs
.symbols
.lookup(&SymbolName::new(name.as_bytes()))
.is_some_and(|id| !matches!(refs.global_target(id, false).def, Def::Undefined { .. }));
if !defined {
diagnostics.emit(Diagnostic::error(format!(
"required symbol '{name}' is not defined"
)));
errors = errors.saturating_add(1);
}
}
errors
}
fn report_gnu_warnings<F: crate::elf::read::ElfFormat>(
files: &[super::inputs::ElfInput<'_, F>],
symbols: &SymbolTable<'_>,
diagnostics: &dyn DiagnosticSink,
) {
for file in files {
let Some(object) = &file.object else {
continue;
};
for &index in &object.warnings {
let Some(section) = object.section(index) else {
continue;
};
let symbol = section.name.strip_prefix(b".gnu.warning.");
let used = match symbol {
Some(name) => symbols.lookup(&SymbolName::new(name)).is_some_and(|id| {
symbols
.flags(id)
.contains(crate::symbols::SymbolFlags::REFERENCED)
}),
None => section.name == b".gnu.warning",
};
if !used {
continue;
}
let text = object.elf.section_data(§ion.header).unwrap_or_default();
let text = text.split(|&b| b == 0).next().unwrap_or_default();
diagnostics.emit(
Diagnostic::warning(String::from_utf8_lossy(text).into_owned())
.order(file.position.raw()),
);
}
}
}
fn entry_address<F: crate::elf::read::ElfFormat>(
addresses: &Addresses<'_, '_, F>,
options: &LinkOptions,
mode: Mode,
diagnostics: &dyn DiagnosticSink,
) -> u64 {
if mode.shared && options.entry.is_none() {
return 0;
}
let name = options.entry.as_deref().unwrap_or("_start");
if let Some(value) = parse_number(name) {
return value;
}
let found = addresses
.refs
.symbols
.lookup(&SymbolName::new(name.as_bytes()))
.filter(|&id| {
!matches!(
addresses.refs.global_target(id, false).def,
Def::Undefined { .. }
)
})
.and_then(|id| addresses.globals.get(id.index()).copied());
match found {
Some(value) => value,
None => {
let text = addresses.layout.by_name(b".text").map_or(0, |s| s.addr);
diagnostics.emit(Diagnostic::warning(format!(
"cannot find entry symbol {name}; defaulting to {text:#x}"
)));
text
}
}
}