qld 0.1.1

A fast, parallel linker compatible with GNU ld, gold, lld and mold
Documentation
//! Relocation targets: from a symbol index in a file to the definition it
//! refers to.
//!
//! A relocation names a symbol of its own file. A local symbol is defined
//! right there; a global one goes through the file's symbol IDs to the
//! definition resolution chose, which may be in another file, a common
//! symbol, a linker-defined symbol, or nothing.

#![deny(clippy::arithmetic_side_effects)]

use crate::elf::read::consts::{STB_WEAK, STT_GNU_IFUNC, STT_TLS};
use crate::elf::read::{RawSymbol, SectionIndex};
use crate::ids::{FileId, SectionId, SymbolId};
use crate::symbols::{DefinitionKind, Resolution, SymbolTable};

use super::inputs::ElfInput;
use super::sections::Sections;

/// The file ID used in [`crate::symbols::Definition`]s of linker-defined
/// symbols.
pub const LINKER_FILE: FileId = FileId::from_u32(u32::MAX - 1);

/// Where a relocation's symbol is defined.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Def {
    /// In section `section` of file `file`, at `value` (raw `st_value`).
    Section {
        /// Defining file.
        file: usize,
        /// Section index in that file.
        section: u32,
        /// The symbol's `st_value`.
        value: u64,
    },
    /// An absolute value.
    Absolute(u64),
    /// A common symbol (global), allocated by the linker.
    Common(SymbolId),
    /// A linker-defined symbol (global).
    Linker(SymbolId),
    /// Defined by a shared object (global): resolved at run time.
    Shared(SymbolId),
    /// Undefined; `weak` tells whether the reference is weak.
    Undefined {
        /// The reference (in the referring file) is weak.
        weak: bool,
    },
}

/// A resolved relocation target.
#[derive(Clone, Copy, Debug)]
pub struct Target {
    /// The global symbol, for global references.
    pub global: Option<SymbolId>,
    /// The definition.
    pub def: Def,
    /// The defining symbol's raw entry, when there is one in an object;
    /// for an undefined thread-local variable, the reference's.
    pub raw: Option<RawSymbol>,
}

impl Target {
    /// Whether the symbol is an IFUNC.
    #[must_use]
    pub fn is_ifunc(&self) -> bool {
        self.raw.is_some_and(|raw| raw.kind() == STT_GNU_IFUNC)
            && matches!(self.def, Def::Section { .. })
    }

    /// Whether the symbol is thread-local.
    #[must_use]
    pub fn is_tls(&self) -> bool {
        self.raw.is_some_and(|raw| raw.kind() == STT_TLS)
    }

    /// Whether it is a section symbol.
    #[must_use]
    pub fn is_section_symbol(&self) -> bool {
        self.raw
            .is_some_and(|raw| raw.kind() == crate::elf::read::consts::STT_SECTION)
    }
}

/// Read-only access to everything needed to resolve relocation targets.
#[derive(Clone, Copy)]
pub struct Refs<'r, 'a, F: crate::elf::read::ElfFormat = crate::elf::read::Elf64Le> {
    /// All inputs.
    pub files: &'r [ElfInput<'a, F>],
    /// The global symbol table.
    pub symbols: &'r SymbolTable<'a>,
    /// The resolution result.
    pub resolution: &'r Resolution<'a>,
    /// Section numbering and liveness.
    pub sections: &'r Sections,
}

impl<'r, 'a, F: crate::elf::read::ElfFormat> Refs<'r, 'a, F> {
    /// The global symbol ID of symbol `index` of `file`, if it is global.
    #[inline]
    #[must_use]
    pub fn global_id(&self, file: usize, index: usize) -> Option<SymbolId> {
        let object = self.files.get(file)?.object.as_ref()?;
        let local = index.checked_sub(object.first_global)?;
        self.resolution
            .symbol_ids(FileId::new(file))
            .get(local)
            .copied()
    }

    /// Resolves symbol `index` of `file`.
    ///
    /// A loop over one file's relocations should take
    /// [`for_file`](Self::for_file) once instead: this looks the file, its
    /// symbol table and its symbol IDs up again for every call.
    #[inline(always)]
    #[must_use]
    pub fn target(&self, file: usize, index: usize) -> Option<Target> {
        self.for_file(file)?.target(index)
    }

    /// A view of one file's relocation targets, with the lookups that do
    /// not depend on the symbol done once. `None` if `file` is not an
    /// object.
    #[inline]
    #[must_use]
    pub fn for_file(&self, file: usize) -> Option<FileTargets<'r, 'a, F>> {
        let object = self.files.get(file)?.object.as_ref()?;
        Some(FileTargets {
            refs: *self,
            file,
            symbols: object.elf.symbols(),
            first_global: object.first_global,
            ids: self.resolution.symbol_ids(FileId::new(file)),
        })
    }

    /// Resolves global symbol `id`; `weak` is the binding of the reference.
    #[inline(always)]
    #[must_use]
    pub fn global_target(&self, id: SymbolId, weak: bool) -> Target {
        let def = self.symbols.definition(id);
        let undefined = Target {
            global: Some(id),
            def: Def::Undefined { weak },
            raw: None,
        };
        match def.kind {
            DefinitionKind::Undefined | DefinitionKind::Lazy => undefined,
            DefinitionKind::Shared => {
                let raw = self
                    .files
                    .get(def.file.index())
                    .and_then(|f| f.shared.as_ref())
                    .and_then(|s| {
                        let index = *s.symbols.get(def.index as usize)?;
                        s.elf.symbols().get_raw(index as usize)
                    });
                Target {
                    global: Some(id),
                    def: Def::Shared(id),
                    raw,
                }
            }
            DefinitionKind::Common => Target {
                global: Some(id),
                def: Def::Common(id),
                raw: None,
            },
            DefinitionKind::Regular | DefinitionKind::Weak => {
                if def.file == LINKER_FILE {
                    return Target {
                        global: Some(id),
                        def: Def::Linker(id),
                        raw: None,
                    };
                }
                let file = def.file.index();
                let Some(object) = self.files.get(file).and_then(|f| f.object.as_ref()) else {
                    // The internal file: `--defsym`.
                    return Target {
                        global: Some(id),
                        def: Def::Linker(id),
                        raw: None,
                    };
                };
                let symbols = object.elf.symbols();
                let Some(index) = (def.index as usize).checked_add(object.first_global) else {
                    return undefined;
                };
                let Some(raw) = symbols.get_raw(index) else {
                    return undefined;
                };
                match local_def(file, index, &raw, symbols) {
                    Some(d) => Target {
                        global: Some(id),
                        def: d,
                        raw: Some(raw),
                    },
                    None => undefined,
                }
            }
        }
    }

    /// The name of symbol `symbol` of `file`, for diagnostics.
    #[must_use]
    pub fn symbol_name(&self, file: usize, symbol: u32) -> Option<String> {
        let object = self.files.get(file)?.object.as_ref()?;
        let symbol = object.elf.symbols().get(symbol as usize).ok()?;
        Some(String::from_utf8_lossy(symbol.name).into_owned())
    }

    /// The section a target is defined in.
    #[must_use]
    pub fn target_section(&self, target: &Target) -> Option<SectionId> {
        match target.def {
            Def::Section { file, section, .. } => self.sections.id(file, section),
            _ => None,
        }
    }
}

/// One file's relocation targets: [`Refs::for_file`].
///
/// Resolving a relocation's symbol starts with three lookups that are the
/// same for every relocation of the file — the input, its symbol table and
/// the symbol IDs resolution gave its globals. The scan and the writer walk
/// millions of relocations file by file, so they take this view once and
/// ask it for each symbol.
#[derive(Clone, Copy)]
pub struct FileTargets<'r, 'a, F: crate::elf::read::ElfFormat = crate::elf::read::Elf64Le> {
    refs: Refs<'r, 'a, F>,
    file: usize,
    symbols: &'r crate::elf::read::SymbolTable<'a, F>,
    first_global: usize,
    ids: &'r [SymbolId],
}

impl<'r, 'a, F: crate::elf::read::ElfFormat> FileTargets<'r, 'a, F> {
    /// The whole-link view this one came from.
    #[inline]
    #[must_use]
    pub fn refs(&self) -> Refs<'r, 'a, F> {
        self.refs
    }

    /// The global symbol ID of symbol `index`, if it is global.
    #[inline]
    #[must_use]
    pub fn global_id(&self, index: usize) -> Option<SymbolId> {
        let local = index.checked_sub(self.first_global)?;
        self.ids.get(local).copied()
    }

    /// Resolves symbol `index` of the file.
    #[inline(always)]
    #[must_use]
    pub fn target(&self, index: usize) -> Option<Target> {
        let raw = self.symbols.get_raw(index)?;
        if index < self.first_global {
            let def = local_def(self.file, index, &raw, self.symbols)?;
            return Some(Target {
                global: None,
                def,
                raw: Some(raw),
            });
        }
        let id = self.global_id(index)?;
        let mut target = self.refs.global_target(id, raw.binding() == STB_WEAK);
        // An undefined thread-local variable has no defining entry, and
        // some relocations mean something else against an ordinary symbol
        // (LoongArch's extreme code model): keep the reference's entry, so
        // that `STT_TLS` is still visible.
        if target.raw.is_none() && raw.kind() == STT_TLS {
            target.raw = Some(raw);
        }
        Some(target)
    }
}

#[inline(always)]
fn local_def<F: crate::elf::read::ElfFormat>(
    file: usize,
    index: usize,
    raw: &RawSymbol,
    symbols: &crate::elf::read::SymbolTable<'_, F>,
) -> Option<Def> {
    Some(match symbols.section_of(index, raw)? {
        SectionIndex::Section(section) => Def::Section {
            file,
            section,
            value: raw.st_value,
        },
        SectionIndex::Absolute => Def::Absolute(raw.st_value),
        SectionIndex::Undefined => Def::Undefined {
            weak: raw.binding() == STB_WEAK,
        },
        SectionIndex::Common | SectionIndex::Reserved(_) => Def::Absolute(0),
    })
}