Flatten output chunk modules

Give each section a sibling module containing its type and functions,
and update callers to use the direct paths. Remove the misc and version
grouping modules and redundant nested wrappers.

Validation:
- cargo check --locked --workspace
- cargo test --locked --workspace: 5,488 integration tests passed,
  636 skipped, no failures; all library tests passed. Four cross targets
  were unavailable because their compiler or QEMU was missing.
This commit is contained in:
Rui Ueyama committed 2026-09-05 16:17:50 +09:00
1 parent 6fcdeb9088
commit 2eef5735f0
46 files changed
+3932 -3855

No files matched your search

+1 -1
View File
@@ -63,7 +63,7 @@ use crate::context::Context;
use crate::elf::*;
use crate::input_sections::{check_tlsle, scan_absrel, scan_pcrel, InputSection};
use crate::output_chunks::eh_frame;
use crate::output_chunks::got::plt::SPARC_NUM_SMALL_PLT;
use crate::output_chunks::plt::SPARC_NUM_SMALL_PLT;
use crate::symbol::{Symbol, NEEDS_GOT, NEEDS_GOTTP, NEEDS_PLT, NEEDS_TLSGD};
use crate::util::{bit, bits};
use crate::{error, fatal};
+37 -17
View File
@@ -15,24 +15,43 @@ use crate::input_sections::{
FragmentRef, InputSection, InputSectionId, SectionFragment, SectionRef,
};
use crate::linker_script::{DynamicPattern, VersionPattern};
use crate::output_chunks::dynamic::{DynamicSection, RelDynSection, RelrDynSection};
use crate::output_chunks::eh_frame::{EhFrameHdrSection, EhFrameRelocSection, EhFrameSection};
use crate::output_chunks::got::{
GotPltSection, GotSection, PltGotSection, PltSection, RelPltSection,
};
use crate::output_chunks::build_id::BuildIdSection;
use crate::output_chunks::comdat_group::ComdatGroupSection;
use crate::output_chunks::compressed::CompressedSection;
use crate::output_chunks::copyrel::CopyrelSection;
use crate::output_chunks::dynamic::DynamicSection;
use crate::output_chunks::dynstr::DynstrSection;
use crate::output_chunks::dynsym::DynsymSection;
use crate::output_chunks::eh_frame::EhFrameSection;
use crate::output_chunks::eh_frame_hdr::EhFrameHdrSection;
use crate::output_chunks::eh_frame_reloc::EhFrameRelocSection;
use crate::output_chunks::gnu_debuglink::GnuDebuglinkSection;
use crate::output_chunks::gnu_hash::GnuHashSection;
use crate::output_chunks::got::GotSection;
use crate::output_chunks::gotplt::GotPltSection;
use crate::output_chunks::hash::HashSection;
use crate::output_chunks::interp::InterpSection;
use crate::output_chunks::merged::{MergedSection, MergedSectionId};
use crate::output_chunks::misc::{
BuildIdSection, ComdatGroupSection, CompressedSection, CopyrelSection, GnuDebuglinkSection,
InterpSection, NotePackageSection, NotePropertySection, RelocSection, RelroPaddingSection,
RiscvAttributesSection,
};
use crate::output_chunks::note_package::NotePackageSection;
use crate::output_chunks::note_property::NotePropertySection;
use crate::output_chunks::output_section::OutputSection;
use crate::output_chunks::sframe::{SFrameRelocSection, SFrameSection};
use crate::output_chunks::symtab::{
DynstrSection, DynsymSection, GnuHashSection, HashSection, ShstrtabSection, StrtabSection,
SymtabSection, SymtabShndxSection,
};
use crate::output_chunks::version::{VerdefSection, VerneedSection, VersymSection};
use crate::output_chunks::plt::PltSection;
use crate::output_chunks::pltgot::PltGotSection;
use crate::output_chunks::reldyn::RelDynSection;
use crate::output_chunks::reloc::RelocSection;
use crate::output_chunks::relplt::RelPltSection;
use crate::output_chunks::relrdyn::RelrDynSection;
use crate::output_chunks::relro_padding::RelroPaddingSection;
use crate::output_chunks::riscv_attributes::RiscvAttributesSection;
use crate::output_chunks::sframe::SFrameSection;
use crate::output_chunks::sframe_reloc::SFrameRelocSection;
use crate::output_chunks::shstrtab::ShstrtabSection;
use crate::output_chunks::strtab::StrtabSection;
use crate::output_chunks::symtab::SymtabSection;
use crate::output_chunks::symtab_shndx::SymtabShndxSection;
use crate::output_chunks::verdef::VerdefSection;
use crate::output_chunks::verneed::VerneedSection;
use crate::output_chunks::versym::VersymSection;
use crate::output_chunks::{
ChunkHeader, ChunkId, GdbIndexSection, OutputEhdr, OutputPhdr, OutputSectionId, OutputShdr,
};
@@ -174,7 +193,8 @@ pub struct Context<E: Arch> {
pub note_property: Option<NotePropertySection<E>>,
pub riscv_attributes: Option<RiscvAttributesSection<E>>,
pub arm_exidx: Option<crate::output_chunks::arm_exidx::ArmExidxSection<E>>,
pub ppc64_save_restore: Option<crate::output_chunks::misc::Ppc64SaveRestoreSection<E>>,
pub ppc64_save_restore:
Option<crate::output_chunks::ppc64_save_restore::Ppc64SaveRestoreSection<E>>,
pub ppc64_opd: Option<crate::output_chunks::opd::Ppc64OpdSection<E>>,
/// Whether any input uses Power10 PC-relative calls, which decides
/// how thunks address their targets.
+9 -11
View File
@@ -428,7 +428,7 @@ pub fn link<E: Arch>(cmdline: &[String]) -> Result<i32, String> {
// RELR is encoded independently for each output chunk using offsets
// relative to that chunk.
if ctx.args.pack_dyn_relocs_relr {
output_chunks::dynamic::reldyn::construct_relr(&mut ctx);
output_chunks::reldyn::construct_relr(&mut ctx);
}
// Reserve a space for dynamic symbol strings in .dynstr and sort
// .dynsym contents if necessary. Beyond this point, no symbol will
@@ -475,10 +475,10 @@ pub fn link<E: Arch>(cmdline: &[String]) -> Result<i32, String> {
// Fill .gnu.version_d section contents.
if ctx.verdef.is_some() {
output_chunks::version::verdef::construct(&mut ctx);
output_chunks::verdef::construct(&mut ctx);
}
// Fill .gnu.version_r section contents.
output_chunks::version::verneed::construct(&mut ctx);
output_chunks::verneed::construct(&mut ctx);
// .eh_frame is a special section from the linker's point of view,
// as its contents are parsed and reconstructed by the linker,
@@ -562,7 +562,7 @@ pub fn link<E: Arch>(cmdline: &[String]) -> Result<i32, String> {
// At this point, both memory and file layouts are fixed.
let t = ctx.timer("update_reldyn");
output_chunks::dynamic::reldyn::update_shdr(&mut ctx);
output_chunks::reldyn::update_shdr(&mut ctx);
drop(t);
ctx.filesize = filesize;
t_before_copy.stop();
@@ -596,7 +596,7 @@ pub fn link<E: Arch>(cmdline: &[String]) -> Result<i32, String> {
if ctx.chunks.contains(&ChunkId::RelDyn) && reldyn.sh_size.get() != 0 {
let start = reldyn.sh_offset.get() as usize;
let end = (reldyn.sh_offset.get() + reldyn.sh_size.get()) as usize;
output_chunks::dynamic::reldyn::sort(&ctx, &mut buf[start..end]);
output_chunks::reldyn::sort(&ctx, &mut buf[start..end]);
}
// The final stage reads address ranges, which requires relocated debug
@@ -825,12 +825,10 @@ fn run_tasks<E: Arch>(
ChunkId::EhFrame => output_chunks::eh_frame::copy_buf(ctx, own, bufs.next()),
ChunkId::Symtab => {
let strtab = bufs.next().unwrap();
output_chunks::symtab::symtab::copy_buf(ctx, own, strtab, bufs.next());
}
ChunkId::Reloc(i) => output_chunks::misc::reloc::copy_buf(ctx, i, own, bufs.next()),
ChunkId::EhFrameReloc => {
output_chunks::eh_frame::eh_frame_reloc::copy_buf(ctx, own, bufs.next())
output_chunks::symtab::copy_buf(ctx, own, strtab, bufs.next());
}
ChunkId::Reloc(i) => output_chunks::reloc::copy_buf(ctx, i, own, bufs.next()),
ChunkId::EhFrameReloc => output_chunks::eh_frame_reloc::copy_buf(ctx, own, bufs.next()),
id => output_chunks::copy_buf(ctx, id, own),
}
});
@@ -838,7 +836,7 @@ fn run_tasks<E: Arch>(
// .eh_frame_hdr's header, whose table .eh_frame wrote.
if tasks.iter().any(|t| t.chunk == ChunkId::EhFrame) && ctx.eh_frame_hdr.is_some() {
let r = file_range(ctx, ChunkId::EhFrameHdr);
output_chunks::eh_frame::eh_frame_hdr::write_header(
output_chunks::eh_frame_hdr::write_header(
ctx,
&mut buf[r.offset as usize..(r.offset + r.size) as usize],
);
+48
View File
@@ -0,0 +1,48 @@
//! `.note.gnu.build-id`, the output file's build identifier.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .note.gnu.build-id contains an identifier for an output ELF file. The
// contents of the section is usually a cryptogrpahic hash of the output
// file itself to guarantee uniqueness of build-id.
#[derive(Debug)]
pub struct BuildIdSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> BuildIdSection<E> {
pub fn new() -> BuildIdSection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.gnu.build-id", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set(1);
BuildIdSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for BuildIdSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let size = ctx.args.build_id.size() as u64 + 16; // +16 for the header
ctx.buildid.as_mut().unwrap().hdr.shdr.sh_size.set(size);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.buildid.as_ref().unwrap();
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(&mut buf[4..], ctx.args.build_id.size() as u32); // Hash size
E::Endian::write_u32(&mut buf[8..], NT_GNU_BUILD_ID);
buf[12..16].copy_from_slice(b"GNU\0"); // Name string
buf[16..16 + sec.contents.len()].copy_from_slice(&sec.contents); // Build ID
}
+50
View File
@@ -0,0 +1,50 @@
//! COMDAT groups in relocatable outputs.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::{ChunkHeader, ChunkId};
use crate::symbol::SymbolId;
// ComdatGroupSection represents a comdat group for an output file.
// This is used only for the relocatable output (i.e. the `-r` output).
#[derive(Debug)]
pub struct ComdatGroupSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub sym: SymbolId,
pub members: Vec<ChunkId>,
}
impl<E: Layout> ComdatGroupSection<E> {
pub fn new(sym: SymbolId, members: Vec<ChunkId>) -> ComdatGroupSection<E> {
let mut hdr = ChunkHeader::<E>::new(".group", SHT_GROUP, 0);
hdr.shdr.sh_entsize.set(4);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set((members.len() * 4 + 4) as u64);
ComdatGroupSection { hdr, sym, members }
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>, i: u32) {
debug_assert!(ctx.args.relocatable);
let sec = &ctx.comdat_group_sections[i as usize];
let sym = &ctx.symbols[sec.sym];
let sh_info = if sym.st_type() == STT_SECTION {
let isec = sym.input_section_ref(ctx).unwrap();
ctx.output_section(isec.output_section.unwrap()).hdr.shndx
} else {
sym.output_sym_idx(ctx)
};
let symtab_shndx = ctx.symtab.hdr.shndx;
let sec = &mut ctx.comdat_group_sections[i as usize];
sec.hdr.shdr.sh_link.set(symtab_shndx);
sec.hdr.shdr.sh_info.set(sh_info);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, i: u32, buf: &mut [u8]) {
let sec = &ctx.comdat_group_sections[i as usize];
E::Endian::write_u32(buf, GRP_COMDAT);
for (j, &member) in sec.members.iter().enumerate() {
E::Endian::write_u32(&mut buf[4 + j * 4..], ctx.chunk_header(member).shndx);
}
}
+84
View File
@@ -0,0 +1,84 @@
//! Compressed output sections.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::{self, ChunkHeader, ChunkId};
use crate::util::compress::Compressor;
// Debug sections can be compressed with zlib or zstd to reduce the
// overall size of an ELF file. CompressedSection represents a compressed
// section.
#[derive(Debug)]
pub struct CompressedSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub chdr: ElfChdr<E>,
pub compressor: Compressor,
/// Kept for --gdb-index, which reads the uncompressed contents.
pub uncompressed_data: Option<Vec<u8>>,
pub original: ChunkId,
}
impl std::fmt::Debug for Compressor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compressor({} bytes)", self.compressed_size())
}
}
pub fn new<E: Arch>(ctx: &Context<E>, original: ChunkId) -> CompressedSection<E> {
let hdr = ctx.chunk_header(original);
// C++ mold uses uninitialized storage here to avoid zero-filling a
// potentially large scratch buffer. Rust currently uses a
// zero-initialized Vec because write_to accepts &mut [u8].
let mut buf = vec![0u8; hdr.shdr.sh_size.get() as usize];
// Write uncompressed contents and then compress them
output_chunks::write_to(ctx, original, &mut buf);
let level = ctx.args.compress_debug_sections_level;
let compressor = if ctx.args.compress_debug_sections == ELFCOMPRESS_ZLIB {
Compressor::zlib(&buf, level as u32)
} else {
Compressor::zstd(&buf, level as i32)
};
// Compute header field values
let mut chdr = ElfChdr::<E>::default();
chdr.ch_type_mut().set(ctx.args.compress_debug_sections);
chdr.ch_size_mut().set(hdr.shdr.sh_size.get());
chdr.ch_addralign_mut().set(hdr.shdr.sh_addralign.get());
let mut new_hdr = ChunkHeader::<E>::with_name(
hdr.name,
hdr.shdr.sh_type.get(),
hdr.shdr.sh_flags.get() | SHF_COMPRESSED as u64,
);
new_hdr.shndx = hdr.shndx;
new_hdr.is_compressed = true;
new_hdr.shdr = hdr.shdr;
new_hdr
.shdr
.sh_flags
.set(new_hdr.shdr.sh_flags.get() | SHF_COMPRESSED as u64);
new_hdr.shdr.sh_addralign.set(1);
new_hdr
.shdr
.sh_size
.set((std::mem::size_of::<ElfChdr<E>>() + compressor.compressed_size()) as u64);
// We can discard the uncompressed contents unless --gdb-index is given
CompressedSection {
hdr: new_hdr,
chdr,
compressor,
uncompressed_data: ctx.args.gdb_index.then_some(buf),
original,
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, i: u32, buf: &mut [u8]) {
let sec = &ctx.compressed_sections[i as usize];
sec.chdr.write(buf);
sec.compressor
.write_to(&mut buf[std::mem::size_of::<ElfChdr<E>>()..]);
}
+106
View File
@@ -0,0 +1,106 @@
//! `.copyrel` and `.copyrel.rel.ro`, storage for copy relocations.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::error;
use crate::input_files::FileId;
use crate::output_chunks::{self, ChunkHeader};
use crate::symbol::SymbolId;
use crate::util::align_to;
// .copyrel and .copyrel.rel.ro represent memory regions to which the
// runtime copies symbols from other ELF files for copy relocations.
#[derive(Debug)]
pub struct CopyrelSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub symbols: Vec<SymbolId>,
}
impl<E: Layout> CopyrelSection<E> {
pub fn new(is_relro: bool) -> CopyrelSection<E> {
let name = if is_relro {
".copyrel.rel.ro"
} else {
".copyrel"
};
let mut hdr = ChunkHeader::<E>::new(name, SHT_NOBITS, (SHF_ALLOC | SHF_WRITE) as u64);
hdr.is_relro = is_relro;
CopyrelSection {
hdr,
symbols: Vec::new(),
}
}
}
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, relro: bool, id: SymbolId) {
debug_assert!(!ctx.args.shared);
let sym = &ctx.symbols[id];
if sym.has_copyrel() {
return;
}
let Some(FileId::Dso(dso_id)) = sym.file() else {
debug_assert!(sym.is_undef_weak());
let file = ctx.file_display(sym.file().unwrap());
error!("{file}: cannot create a copy relocation for {sym}; recompile with -fPIE or -fPIC");
return;
};
let dso = &ctx.dsos[dso_id.index()];
if sym.esym(ctx).st_visibility() == STV_PROTECTED {
error!("{dso}: cannot create a copy relocation for protected symbol '{sym}'; recompile with -fPIC"
);
return;
}
if !ctx.args.z_copyreloc {
error!("-z nocopyreloc: {dso}: cannot create a copy relocation for symbol '{sym}'; recompile with -fPIC"
);
return;
}
let alignment = dso.alignment(sym);
let size = sym.esym(ctx).st_size().get();
// We need to create dynamic symbols not only for this particular symbol
// but also for its aliases (i.e. other symbols at the same address)
// becasue otherwise the aliases are broken apart at runtime.
// For example, `environ`, `_environ` and `__environ` in libc.so are
// aliases. If one of the symbols is copied by a copy relocation, other
// symbols have to refer to the copied place as well.
let aliases: Vec<SymbolId> = dso.symbols_at(ctx, sym, dso_id).to_vec();
let sec = if relro {
&mut ctx.copyrel_relro
} else {
&mut ctx.copyrel
};
sec.symbols.push(id);
let offset = align_to(sec.hdr.shdr.sh_size.get(), alignment);
sec.hdr.shdr.sh_size.set(offset + size);
sec.hdr
.shdr
.sh_addralign
.set(sec.hdr.shdr.sh_addralign.get().max(alignment));
for alias in aliases {
ctx.symbols.aux_mut(alias);
let s = &mut ctx.symbols[alias];
s.set_imported(true);
s.set_exported(true);
s.set_copyrel(true);
s.set_copyrel_readonly(relro);
s.value = offset;
output_chunks::dynsym::add_symbol(ctx, alias);
}
}
pub fn write_dynrels<E: Arch>(ctx: &Context<E>, sec: &CopyrelSection<E>, out: &mut [E::Rel]) {
for (i, &id) in sec.symbols.iter().enumerate() {
let sym = &ctx.symbols[id];
out[i] = ElfRel::<E>::new(
sym.addr(ctx),
E::R_COPY,
sym.dynsym_idx(&ctx.symbols).unwrap_or(0),
0,
);
}
debug_assert_eq!(sec.symbols.len(), out.len());
}
File diff suppressed because it is too large. Load diff
+62
View File
@@ -0,0 +1,62 @@
//! `.dynstr`, strings used by the dynamic linker.
use std::collections::HashMap;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::util::write_cstr;
// .dynstr contains strings that the runtime uses.
#[derive(Debug)]
pub struct DynstrSection<E: Layout> {
pub hdr: ChunkHeader<E>,
strings: HashMap<Vec<u8>, u64>,
}
impl<E: Layout> DynstrSection<E> {
pub fn new() -> DynstrSection<E> {
DynstrSection {
hdr: ChunkHeader::<E>::new(".dynstr", SHT_STRTAB, SHF_ALLOC as u64),
strings: HashMap::new(),
}
}
pub fn add_string(&mut self, s: &[u8]) -> u64 {
if self.hdr.shdr.sh_size.get() == 0 {
self.strings.insert(Vec::new(), 0);
self.hdr.shdr.sh_size.set(1);
}
if let Some(&off) = self.strings.get(s) {
return off;
}
let off = self.hdr.shdr.sh_size.get();
self.strings.insert(s.to_vec(), off);
self.hdr
.shdr
.sh_size
.set(self.hdr.shdr.sh_size.get() + s.len() as u64 + 1);
off
}
pub fn find_string(&self, s: &[u8]) -> u64 {
*self.strings.get(s).expect("string not in .dynstr")
}
}
impl<E: Layout> Default for DynstrSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
for (s, &off) in &ctx.dynstr.strings {
write_cstr(&mut buf[off as usize..], s);
}
let mut off = ctx.dynsym.dynstr_offset as usize;
for &id in ctx.dynsym.symbols.iter().flatten() {
off += write_cstr(&mut buf[off..], ctx.symbols[id].name());
}
}
+81
View File
@@ -0,0 +1,81 @@
//! `.dynsym`, the dynamic symbol table.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::error;
use crate::output_chunks::symtab::to_output_esym;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
// .dynsym contains symbols for dynamic linking. This is similar to
// .symtab, but .dynsym contains data that the runtime uses.
#[derive(Debug)]
pub struct DynsymSection<E: Layout> {
pub hdr: ChunkHeader<E>,
/// Index 0 is the null symbol.
pub symbols: Vec<Option<SymbolId>>,
pub dynstr_offset: u64,
}
impl<E: Arch> DynsymSection<E> {
pub fn new() -> DynsymSection<E> {
let mut hdr = ChunkHeader::<E>::new(".dynsym", SHT_DYNSYM, SHF_ALLOC as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfSym<E>>() as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
DynsymSection {
hdr,
symbols: Vec::new(),
dynstr_offset: 0,
}
}
}
impl<E: Arch> Default for DynsymSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, sym: SymbolId) {
if ctx.dynsym.symbols.is_empty() {
ctx.dynsym.symbols.push(None);
}
if ctx.symbols[sym].dynsym_idx(&ctx.symbols).is_none() {
// A placeholder; the real index is assigned by sort_dynsyms.
ctx.symbols.aux_mut(sym).dynsym_idx = Some(u32::MAX);
ctx.dynsym.symbols.push(Some(sym));
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.dynsym.hdr.shdr.sh_link.set(ctx.dynstr.hdr.shndx);
ctx.dynsym
.hdr
.shdr
.sh_size
.set(std::mem::size_of::<ElfSym<E>>() as u64 * ctx.dynsym.symbols.len() as u64);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let size = std::mem::size_of::<ElfSym<E>>();
buf[..size].fill(0);
let mut offset = ctx.dynsym.dynstr_offset as u32;
for &id in ctx.dynsym.symbols.iter().skip(1).flatten() {
let sym = &ctx.symbols[id];
let (esym, xindex) = to_output_esym(ctx, sym, offset);
if xindex != 0 {
let nshdrs = ctx.shdr.as_ref().map_or(0, |s| {
s.hdr.shdr.sh_size.get() / ElfShdr::<E>::size() as u64
});
error!("{}: .dynsym: too many output sections: {nshdrs} requested, but ELF allows at most 65279",
ctx.args.output
);
return;
}
esym.write(&mut buf[sym.dynsym_idx(&ctx.symbols).unwrap() as usize * size..]);
offset += sym.name().len() as u32 + 1;
}
}
+4 -188
View File
@@ -1,18 +1,20 @@
//! `.eh_frame`, `.eh_frame_hdr` and `.rela.eh_frame`.
//! `.eh_frame`, reconstructed exception-handling records.
//!
//! `.eh_frame` is reconstructed rather than copied: FDEs of dead functions
//! are dropped, identical CIEs are merged, and a sorted lookup table is
//! emitted as `.eh_frame_hdr` so that the unwinder can find the FDE for a
//! PC by binary search.
use rayon::prelude::*;
use std::ptr::NonNull;
use rayon::prelude::*;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::ObjectFile;
use crate::input_sections::CieRecord;
use crate::output_chunks::eh_frame_hdr::EhFrameHdrSection;
use crate::output_chunks::ChunkHeader;
use crate::output_file::split_at_offsets;
use crate::symbol::Symbol;
@@ -404,192 +406,6 @@ pub fn check_range<E: Arch>(
}
}
// .eh_frame_hdr is a lookup table for .eh_frame. Entries in .eh_frame_hdr
// are sorted by their dcorresponding function addresses, so tha the
// runtime can quickly find an exception-handling record for the current
// function by binary search. Without .eh_frame_hdr, the runtime would
// have had to do linear search in .eh_frame.
#[derive(Debug)]
pub struct EhFrameHdrSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub num_fdes: u64,
}
impl<E: Layout> EhFrameHdrSection<E> {
pub const HEADER_SIZE: u64 = 12;
pub fn new() -> EhFrameHdrSection<E> {
let mut hdr = ChunkHeader::<E>::new(".eh_frame_hdr", SHT_PROGBITS, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set(Self::HEADER_SIZE);
EhFrameHdrSection { hdr, num_fdes: 0 }
}
}
impl<E: Layout> Default for EhFrameHdrSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod eh_frame_hdr {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let num_fdes: u64 = ctx.objs.iter().map(|f| f.fdes.len() as u64).sum();
let sec = ctx.eh_frame_hdr.as_mut().unwrap();
sec.num_fdes = num_fdes;
sec.hdr
.shdr
.sh_size
.set(EhFrameHdrSection::<E>::HEADER_SIZE + num_fdes * 8);
}
pub fn write_header<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.eh_frame_hdr.as_ref().unwrap();
// Write a header. The actual table is written by EhFrameSection::copy_buf.
buf[0] = 1;
buf[1] = (DW_EH_PE_pcrel | DW_EH_PE_sdata4) as u8;
buf[2] = DW_EH_PE_udata4 as u8;
buf[3] = (DW_EH_PE_datarel | DW_EH_PE_sdata4) as u8;
E::Endian::write_u32(
&mut buf[4..],
ctx.eh_frame
.hdr
.shdr
.sh_addr
.get()
.wrapping_sub(sec.hdr.shdr.sh_addr.get())
.wrapping_sub(4) as u32,
);
E::Endian::write_u32(&mut buf[8..], sec.num_fdes as u32);
}
}
// EhFrameRelocSection contains relocation records for .eh_frame. It is used
// only for relocatable outputs (an .o file rather than an executable or .so).
#[derive(Debug)]
pub struct EhFrameRelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> EhFrameRelocSection<E> {
pub fn new() -> EhFrameRelocSection<E> {
let (name, ty) = if E::IS_RELA {
(".rela.eh_frame", SHT_RELA)
} else {
(".rel.eh_frame", SHT_REL)
};
let mut hdr = ChunkHeader::<E>::new(name, ty, SHF_INFO_LINK as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
EhFrameRelocSection { hdr }
}
}
impl<E: Arch> Default for EhFrameRelocSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod eh_frame_reloc {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let count: usize = ctx
.objs
.par_iter()
.map(|file| {
let cies: usize = file
.cies
.iter()
.filter(|c| c.is_leader)
.map(|c| c.rels(file).len())
.sum();
let fdes: usize = file.fdes.iter().map(|f| f.rels(file).len()).sum();
cies + fdes
})
.sum();
let sec = ctx.eh_frame_reloc.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set((count * std::mem::size_of::<ElfRel<E>>()) as u64);
sec.hdr.shdr.sh_link.set(ctx.symtab.hdr.shndx);
sec.hdr.shdr.sh_info.set(ctx.eh_frame.hdr.shndx);
}
/// Writes the relocations; with REL and `-r`, addends are written into
/// `.eh_frame` itself, which is passed as `eh_frame_buf`.
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8], eh_frame_buf: Option<&mut [u8]>) {
let out = rels_from_bytes_mut::<E>(buf);
let mut eh_frame_buf = eh_frame_buf;
let mut n = 0;
let mut copy = |file: &ObjectFile<E>,
shndx: u32,
r: &ElfRel<E>,
offset: u64,
eh_frame_buf: &mut Option<&mut [u8]>| {
let isec = file.section_at(shndx);
let sym = &ctx.symbols[file.base.symbols[r.r_sym() as usize]];
let mut rel = ElfRel::<E>::new(
ctx.eh_frame.hdr.shdr.sh_addr.get() + offset,
r.r_type(),
0,
0,
);
if sym.st_type() == STT_SECTION {
// We discard section symbols in input files and re-create new
// ones for each output section. So we need to adjust relocations'
// addends if they refer a section symbol.
let target = sym.input_section_ref(ctx).unwrap();
rel.set_r_sym(ctx.output_section(target.output_section.unwrap()).hdr.shndx);
let addend = isec.rel_addend(r) + target.offset() as i64;
if E::IS_RELA {
rel.set_r_addend(addend);
} else if ctx.args.relocatable {
if let Some(eh) = eh_frame_buf {
E::write_addend(&mut eh[offset as usize..], addend, r);
}
}
} else {
rel.set_r_sym(sym.output_sym_idx(ctx));
if E::IS_RELA {
rel.set_r_addend(isec.rel_addend(r));
}
}
out[n] = rel;
n += 1;
};
for file in &ctx.objs {
for cie in &file.cies {
if cie.is_leader {
for rel in cie.rels(file) {
let offset =
cie.output_offset as u64 + rel.r_offset() - cie.input_offset as u64;
copy(file, cie.section, rel, offset, &mut eh_frame_buf);
}
}
}
for fde in &file.fdes {
let cie = &file.cies[fde.cie_idx as usize];
let base = file.fde_offset + fde.output_offset as u64;
for rel in fde.rels(file) {
let offset = base + rel.r_offset() - fde.input_offset as u64;
copy(file, cie.section, rel, offset, &mut eh_frame_buf);
}
}
}
}
}
/// Fatal error for `.eh_frame` contents that can't be handled.
pub fn unsupported<E: Arch>(rel: &ElfRel<E>) -> ! {
fatal!(
+65
View File
@@ -0,0 +1,65 @@
//! `.eh_frame_hdr`, the lookup table for exception-handling records.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .eh_frame_hdr is a lookup table for .eh_frame. Entries in .eh_frame_hdr
// are sorted by their dcorresponding function addresses, so tha the
// runtime can quickly find an exception-handling record for the current
// function by binary search. Without .eh_frame_hdr, the runtime would
// have had to do linear search in .eh_frame.
#[derive(Debug)]
pub struct EhFrameHdrSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub num_fdes: u64,
}
impl<E: Layout> EhFrameHdrSection<E> {
pub const HEADER_SIZE: u64 = 12;
pub fn new() -> EhFrameHdrSection<E> {
let mut hdr = ChunkHeader::<E>::new(".eh_frame_hdr", SHT_PROGBITS, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set(Self::HEADER_SIZE);
EhFrameHdrSection { hdr, num_fdes: 0 }
}
}
impl<E: Layout> Default for EhFrameHdrSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let num_fdes: u64 = ctx.objs.iter().map(|f| f.fdes.len() as u64).sum();
let sec = ctx.eh_frame_hdr.as_mut().unwrap();
sec.num_fdes = num_fdes;
sec.hdr
.shdr
.sh_size
.set(EhFrameHdrSection::<E>::HEADER_SIZE + num_fdes * 8);
}
pub fn write_header<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.eh_frame_hdr.as_ref().unwrap();
// Write a header. The actual table is written by EhFrameSection::copy_buf.
buf[0] = 1;
buf[1] = (DW_EH_PE_pcrel | DW_EH_PE_sdata4) as u8;
buf[2] = DW_EH_PE_udata4 as u8;
buf[3] = (DW_EH_PE_datarel | DW_EH_PE_sdata4) as u8;
E::Endian::write_u32(
&mut buf[4..],
ctx.eh_frame
.hdr
.shdr
.sh_addr
.get()
.wrapping_sub(sec.hdr.shdr.sh_addr.get())
.wrapping_sub(4) as u32,
);
E::Endian::write_u32(&mut buf[8..], sec.num_fdes as u32);
}
+128
View File
@@ -0,0 +1,128 @@
//! `.rel.eh_frame` and `.rela.eh_frame` for relocatable outputs.
use rayon::prelude::*;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::ObjectFile;
use crate::output_chunks::ChunkHeader;
// EhFrameRelocSection contains relocation records for .eh_frame. It is used
// only for relocatable outputs (an .o file rather than an executable or .so).
#[derive(Debug)]
pub struct EhFrameRelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> EhFrameRelocSection<E> {
pub fn new() -> EhFrameRelocSection<E> {
let (name, ty) = if E::IS_RELA {
(".rela.eh_frame", SHT_RELA)
} else {
(".rel.eh_frame", SHT_REL)
};
let mut hdr = ChunkHeader::<E>::new(name, ty, SHF_INFO_LINK as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
EhFrameRelocSection { hdr }
}
}
impl<E: Arch> Default for EhFrameRelocSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let count: usize = ctx
.objs
.par_iter()
.map(|file| {
let cies: usize = file
.cies
.iter()
.filter(|c| c.is_leader)
.map(|c| c.rels(file).len())
.sum();
let fdes: usize = file.fdes.iter().map(|f| f.rels(file).len()).sum();
cies + fdes
})
.sum();
let sec = ctx.eh_frame_reloc.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set((count * std::mem::size_of::<ElfRel<E>>()) as u64);
sec.hdr.shdr.sh_link.set(ctx.symtab.hdr.shndx);
sec.hdr.shdr.sh_info.set(ctx.eh_frame.hdr.shndx);
}
/// Writes the relocations; with REL and `-r`, addends are written into
/// `.eh_frame` itself, which is passed as `eh_frame_buf`.
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8], eh_frame_buf: Option<&mut [u8]>) {
let out = rels_from_bytes_mut::<E>(buf);
let mut eh_frame_buf = eh_frame_buf;
let mut n = 0;
let mut copy = |file: &ObjectFile<E>,
shndx: u32,
r: &ElfRel<E>,
offset: u64,
eh_frame_buf: &mut Option<&mut [u8]>| {
let isec = file.section_at(shndx);
let sym = &ctx.symbols[file.base.symbols[r.r_sym() as usize]];
let mut rel = ElfRel::<E>::new(
ctx.eh_frame.hdr.shdr.sh_addr.get() + offset,
r.r_type(),
0,
0,
);
if sym.st_type() == STT_SECTION {
// We discard section symbols in input files and re-create new
// ones for each output section. So we need to adjust relocations'
// addends if they refer a section symbol.
let target = sym.input_section_ref(ctx).unwrap();
rel.set_r_sym(ctx.output_section(target.output_section.unwrap()).hdr.shndx);
let addend = isec.rel_addend(r) + target.offset() as i64;
if E::IS_RELA {
rel.set_r_addend(addend);
} else if ctx.args.relocatable {
if let Some(eh) = eh_frame_buf {
E::write_addend(&mut eh[offset as usize..], addend, r);
}
}
} else {
rel.set_r_sym(sym.output_sym_idx(ctx));
if E::IS_RELA {
rel.set_r_addend(isec.rel_addend(r));
}
}
out[n] = rel;
n += 1;
};
for file in &ctx.objs {
for cie in &file.cies {
if cie.is_leader {
for rel in cie.rels(file) {
let offset =
cie.output_offset as u64 + rel.r_offset() - cie.input_offset as u64;
copy(file, cie.section, rel, offset, &mut eh_frame_buf);
}
}
}
for fde in &file.fdes {
let cie = &file.cies[fde.cie_idx as usize];
let base = file.fde_offset + fde.output_offset as u64;
for rel in fde.rels(file) {
let offset = base + rel.r_offset() - fde.input_offset as u64;
copy(file, cie.section, rel, offset, &mut eh_frame_buf);
}
}
}
}
+54
View File
@@ -0,0 +1,54 @@
//! `.gnu_debuglink`, the pathname and checksum of separate debug information.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::util::align_to;
use crate::util::{path_filename, write_cstr};
// .gnu_debuglink section contains a pathname and its CRC32 checksum for a
// separate debug info file. gdb can read the section to read debug info
// from an external file.
#[derive(Debug)]
pub struct GnuDebuglinkSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub filename: String,
pub crc32: u32,
}
impl<E: Layout> GnuDebuglinkSection<E> {
pub fn new() -> GnuDebuglinkSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu_debuglink", SHT_PROGBITS, 0);
hdr.shdr.sh_addralign.set(4);
GnuDebuglinkSection {
hdr,
filename: String::new(),
crc32: 0,
}
}
}
impl<E: Layout> Default for GnuDebuglinkSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let filename = path_filename(&ctx.args.separate_debug_file);
let sec = ctx.gnu_debuglink.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set(align_to(filename.len() as u64 + 1, 4) + 4);
sec.filename = filename;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.gnu_debuglink.as_ref().unwrap();
buf.fill(0);
write_cstr(buf, sec.filename.as_bytes());
let n = buf.len();
E::Endian::write_u32(&mut buf[n - 4..], sec.crc32);
}
+131
View File
@@ -0,0 +1,131 @@
//! `.gnu.hash`, the GNU hash table for dynamic symbol lookup.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
/// The hash function for `.gnu.hash`.
// The hash function for .gnu.hash.
pub fn djb_hash(name: &[u8]) -> u32 {
let mut h: u32 = 5381;
for &c in name {
h = (h << 5).wrapping_add(h).wrapping_add(c as u32);
}
h
}
// .gnu.hash is an alternative format for .hash. It contains not only an
// on-disk hash table but also contains a bloom filter to quickly identify
// whether or not a given symbol name exists in .dynsym.
#[derive(Debug)]
pub struct GnuHashSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub num_buckets: u32,
pub num_bloom: u32,
pub num_exported: u32,
}
impl<E: Arch> GnuHashSection<E> {
pub const LOAD_FACTOR: u32 = 8;
pub const HEADER_SIZE: u64 = 16;
pub const BLOOM_SHIFT: u32 = 26;
pub fn new() -> GnuHashSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.hash", SHT_GNU_HASH, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
GnuHashSection {
hdr,
num_buckets: 0,
num_bloom: 1,
num_exported: 0,
}
}
}
impl<E: Arch> Default for GnuHashSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if ctx.dynsym.symbols.is_empty() {
return;
}
let word = E::WORD_SIZE as u64;
let gh = ctx.gnu_hash.as_mut().unwrap();
// We allocate 12 bits for each symbol in the bloom filter.
gh.num_bloom = ((gh.num_exported as u64 * 12) / (word * 8))
.max(1)
.next_power_of_two() as u32;
gh.hdr.shdr.sh_size.set(
GnuHashSection::<E>::HEADER_SIZE
+ gh.num_bloom as u64 * word // Bloom filter
+ gh.num_buckets as u64 * 4 // Hash buckets
+ gh.num_exported as u64 * 4, // Hash values
);
gh.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
let gh = ctx.gnu_hash.as_ref().unwrap();
let word = E::WORD_SIZE;
let first_exported = ctx.dynsym.symbols.len() - gh.num_exported as usize;
E::Endian::write_u32(buf, gh.num_buckets);
E::Endian::write_u32(&mut buf[4..], first_exported as u32);
E::Endian::write_u32(&mut buf[8..], gh.num_bloom);
E::Endian::write_u32(&mut buf[12..], GnuHashSection::<E>::BLOOM_SHIFT);
let syms: Vec<SymbolId> = ctx.dynsym.symbols[first_exported..]
.iter()
.flatten()
.copied()
.collect();
if syms.is_empty() {
return;
}
// Write a bloom filter
let bloom_off = GnuHashSection::<E>::HEADER_SIZE as usize;
let word_bits = word * 8;
let mut indices = Vec::with_capacity(syms.len());
for &id in &syms {
let h = ctx.symbols[id].aux(&ctx.symbols).unwrap().djb_hash;
indices.push(h % gh.num_buckets);
let idx = (h as usize / word_bits) % gh.num_bloom as usize;
let bits = (1u64 << (h as usize % word_bits))
| (1u64 << ((h >> GnuHashSection::<E>::BLOOM_SHIFT) as usize % word_bits));
let slot = &mut buf[bloom_off + idx * word..];
if E::IS_64 {
E::Endian::write_u64(slot, E::Endian::read_u64(slot) | bits);
} else {
E::Endian::write_u32(slot, E::Endian::read_u32(slot) | bits as u32);
}
}
// Write hash bucket indices
let buckets_off = bloom_off + gh.num_bloom as usize * word;
for (i, &bucket) in indices.iter().enumerate().rev() {
E::Endian::write_u32(
&mut buf[buckets_off + bucket as usize * 4..],
(first_exported + i) as u32,
);
}
// Write a hash table
let table_off = buckets_off + gh.num_buckets as usize * 4;
for (i, &id) in syms.iter().enumerate() {
// The last entry in a chain must be terminated with an entry with
// least-significant bit 1.
let h = ctx.symbols[id].aux(&ctx.symbols).unwrap().djb_hash;
let last = i + 1 == syms.len() || indices[i] != indices[i + 1];
E::Endian::write_u32(
&mut buf[table_off + i * 4..],
if last { h | 1 } else { h & !1 },
);
}
}
+377 -783
View File
File diff suppressed because it is too large. Load diff
+75
View File
@@ -0,0 +1,75 @@
//! `.got.plt`, function pointers used by the PLT.
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .got.plt is similar to .got in the sense that it is a table containing
// pointers. The contents in .got.plt are function pointers used by .plt.
#[derive(Debug)]
pub struct GotPltSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> GotPltSection<E> {
pub fn new(args: &crate::cmdline::Args) -> GotPltSection<E> {
let sh_type = if E::IS_PPC64 {
SHT_NOBITS
} else {
SHT_PROGBITS
};
let mut hdr = ChunkHeader::<E>::new(".got.plt", sh_type, (SHF_ALLOC | SHF_WRITE) as u64);
hdr.is_relro = args.z_now;
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr.sh_size.set(header_size::<E>());
GotPltSection { hdr }
}
}
pub fn header_size<E: Arch>() -> u64 {
let words = if E::FAMILY == Family::Ppc64V2 { 2 } else { 3 };
words * E::WORD_SIZE as u64
}
pub fn entry_size<E: Arch>() -> u64 {
let words = if E::FAMILY == Family::Ppc64V1 { 3 } else { 1 };
words * E::WORD_SIZE as u64
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.gotplt
.hdr
.shdr
.sh_size
.set(header_size::<E>() + ctx.plt.symbols.len() as u64 * entry_size::<E>());
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
// On PPC64, it's dynamic loader responsibility to fill the .got.plt
// section. Dynamic loader finds the address of the first PLT entry by
// DT_PPC64_GLINK and assumes that each PLT entry is 4 bytes long.
if E::IS_PPC64 {
return;
}
let w = E::WORD_SIZE;
let write = |buf: &mut [u8], idx: usize, val: u64| {
if E::IS_64 {
E::Endian::write_u64(&mut buf[idx * w..], val);
} else {
E::Endian::write_u32(&mut buf[idx * w..], val as u32);
}
};
// The first slot of .got.plt points to _DYNAMIC, as requested by
// the psABI. The second and the third slots are reserved by the psABI.
write(
buf,
0,
ctx.dynamic.as_ref().map_or(0, |d| d.hdr.shdr.sh_addr.get()),
);
write(buf, 1, 0);
write(buf, 2, 0);
for i in 0..ctx.plt.symbols.len() {
write(buf, i + 3, ctx.plt.hdr.shdr.sh_addr.get());
}
}
+106
View File
@@ -0,0 +1,106 @@
//! `.hash`, the ELF hash table for dynamic symbol lookup.
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// The hash function for .hash.
pub fn elf_hash(name: &[u8]) -> u32 {
let mut h: u32 = 0;
for &c in name {
h = (h << 4).wrapping_add(c as u32);
let g = h & 0xf000_0000;
if g != 0 {
h ^= g >> 24;
}
h &= !g;
}
h
}
// .hash contains an on-disk hash table for .dynsym so that the runtime
// can look up a symbol name quickly without scannin all entries in
// .dynsym.
//
// Quickly identifying whether or not a .dynsym contains a given symbol is
// especially important for ELF because of the dynamic symbol lookup rule
// for ELF. In ELF, each dynamic symbol is not searched from a specific
// library but from all the ELF files loaded to memory. Therefore,
// minimizing the cost of each dynamic symbol lookup is important.
#[derive(Debug)]
pub struct HashSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> HashSection<E> {
pub fn new() -> HashSection<E> {
let mut hdr = ChunkHeader::<E>::new(".hash", SHT_HASH, SHF_ALLOC as u64);
// Even though u32 should suffice as an etnry size for all targets,
// s390x uses u64. It looks like a spec bug, but we need to follow
// suit for the sake of binary compatibility.
let entry = entry_size::<E>() as u64;
hdr.shdr.sh_entsize.set(entry);
hdr.shdr.sh_addralign.set(entry);
HashSection { hdr }
}
}
impl<E: Arch> Default for HashSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn entry_size<E: Arch>() -> usize {
if E::FAMILY == Family::S390x {
8
} else {
4
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if ctx.dynsym.symbols.is_empty() {
return;
}
let entry = entry_size::<E>() as u64;
let num_slots = ctx.dynsym.symbols.len() as u64;
let hash = ctx.hash.as_mut().unwrap();
hash.hdr.shdr.sh_size.set(entry * 2 + num_slots * entry * 2);
hash.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
let entry = entry_size::<E>();
let write = |buf: &mut [u8], i: usize, v: u32| {
if entry == 8 {
E::Endian::write_u64(&mut buf[i * 8..], v as u64);
} else {
E::Endian::write_u32(&mut buf[i * 4..], v);
}
};
let read = |buf: &[u8], i: usize| -> u32 {
if entry == 8 {
E::Endian::read_u64(&buf[i * 8..]) as u32
} else {
E::Endian::read_u32(&buf[i * 4..])
}
};
let n = ctx.dynsym.symbols.len();
write(buf, 0, n as u32);
write(buf, 1, n as u32);
let buckets = 2;
let chains = 2 + n;
for &id in ctx.dynsym.symbols.iter().skip(1).flatten() {
let sym = &ctx.symbols[id];
let i = sym.dynsym_idx(&ctx.symbols).unwrap() as usize;
let h = elf_hash(sym.name()) as usize % n;
let head = read(buf, buckets + h);
write(buf, chains + i, head);
write(buf, buckets + h, i as u32);
}
}
+39
View File
@@ -0,0 +1,39 @@
//! `.interp`, the dynamic linker's pathname.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::util::write_cstr;
// .interp contains the pathname of a dynamic linker. Dynamically-linked
// executables have the section. If exists, the kernel runs the program at
// the specified path with the executable pathname as an argument,
// allowing the dynamic linker to run the program.
#[derive(Debug)]
pub struct InterpSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> InterpSection<E> {
pub fn new() -> InterpSection<E> {
InterpSection {
hdr: ChunkHeader::<E>::new(".interp", SHT_PROGBITS, SHF_ALLOC as u64),
}
}
}
impl<E: Layout> Default for InterpSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let size = ctx.args.dynamic_linker.len() as u64 + 1;
ctx.interp.as_mut().unwrap().hdr.shdr.sh_size.set(size);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
write_cstr(buf, ctx.args.dynamic_linker.as_bytes());
}
-805
View File
@@ -1,805 +0,0 @@
//! Small synthesized sections.
use std::collections::{BTreeMap, BTreeSet};
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::error;
use crate::input_files::FileId;
use crate::input_sections::{r_delta, InputSection};
use crate::output_chunks::{self, ChunkHeader, ChunkId, OutputSectionId};
use crate::symbol::{OriginValue, SymbolId};
use crate::util::align_to;
use crate::util::compress::Compressor;
use crate::util::{path_filename, write_cstr};
use bstr::BStr;
// .interp contains the pathname of a dynamic linker. Dynamically-linked
// executables have the section. If exists, the kernel runs the program at
// the specified path with the executable pathname as an argument,
// allowing the dynamic linker to run the program.
#[derive(Debug)]
pub struct InterpSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> InterpSection<E> {
pub fn new() -> InterpSection<E> {
InterpSection {
hdr: ChunkHeader::<E>::new(".interp", SHT_PROGBITS, SHF_ALLOC as u64),
}
}
}
impl<E: Layout> Default for InterpSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod interp {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let size = ctx.args.dynamic_linker.len() as u64 + 1;
ctx.interp.as_mut().unwrap().hdr.shdr.sh_size.set(size);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
write_cstr(buf, ctx.args.dynamic_linker.as_bytes());
}
}
// .copyrel and .copyrel.rel.ro represent memory regions to which the
// runtime copies symbols from other ELF files for copy relocations.
#[derive(Debug)]
pub struct CopyrelSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub symbols: Vec<SymbolId>,
}
impl<E: Layout> CopyrelSection<E> {
pub fn new(is_relro: bool) -> CopyrelSection<E> {
let name = if is_relro {
".copyrel.rel.ro"
} else {
".copyrel"
};
let mut hdr = ChunkHeader::<E>::new(name, SHT_NOBITS, (SHF_ALLOC | SHF_WRITE) as u64);
hdr.is_relro = is_relro;
CopyrelSection {
hdr,
symbols: Vec::new(),
}
}
}
pub mod copyrel {
use super::*;
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, relro: bool, id: SymbolId) {
debug_assert!(!ctx.args.shared);
let sym = &ctx.symbols[id];
if sym.has_copyrel() {
return;
}
let Some(FileId::Dso(dso_id)) = sym.file() else {
debug_assert!(sym.is_undef_weak());
let file = ctx.file_display(sym.file().unwrap());
error!(
"{file}: cannot create a copy relocation for {sym}; recompile with -fPIE or -fPIC"
);
return;
};
let dso = &ctx.dsos[dso_id.index()];
if sym.esym(ctx).st_visibility() == STV_PROTECTED {
error!("{dso}: cannot create a copy relocation for protected symbol '{sym}'; recompile with -fPIC"
);
return;
}
if !ctx.args.z_copyreloc {
error!("-z nocopyreloc: {dso}: cannot create a copy relocation for symbol '{sym}'; recompile with -fPIC"
);
return;
}
let alignment = dso.alignment(sym);
let size = sym.esym(ctx).st_size().get();
// We need to create dynamic symbols not only for this particular symbol
// but also for its aliases (i.e. other symbols at the same address)
// becasue otherwise the aliases are broken apart at runtime.
// For example, `environ`, `_environ` and `__environ` in libc.so are
// aliases. If one of the symbols is copied by a copy relocation, other
// symbols have to refer to the copied place as well.
let aliases: Vec<SymbolId> = dso.symbols_at(ctx, sym, dso_id).to_vec();
let sec = if relro {
&mut ctx.copyrel_relro
} else {
&mut ctx.copyrel
};
sec.symbols.push(id);
let offset = align_to(sec.hdr.shdr.sh_size.get(), alignment);
sec.hdr.shdr.sh_size.set(offset + size);
sec.hdr
.shdr
.sh_addralign
.set(sec.hdr.shdr.sh_addralign.get().max(alignment));
for alias in aliases {
ctx.symbols.aux_mut(alias);
let s = &mut ctx.symbols[alias];
s.set_imported(true);
s.set_exported(true);
s.set_copyrel(true);
s.set_copyrel_readonly(relro);
s.value = offset;
output_chunks::symtab::dynsym::add_symbol(ctx, alias);
}
}
pub fn write_dynrels<E: Arch>(ctx: &Context<E>, sec: &CopyrelSection<E>, out: &mut [E::Rel]) {
for (i, &id) in sec.symbols.iter().enumerate() {
let sym = &ctx.symbols[id];
out[i] = ElfRel::<E>::new(
sym.addr(ctx),
E::R_COPY,
sym.dynsym_idx(&ctx.symbols).unwrap_or(0),
0,
);
}
debug_assert_eq!(sec.symbols.len(), out.len());
}
}
// .note.gnu.build-id contains an identifier for an output ELF file. The
// contents of the section is usually a cryptogrpahic hash of the output
// file itself to guarantee uniqueness of build-id.
#[derive(Debug)]
pub struct BuildIdSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> BuildIdSection<E> {
pub fn new() -> BuildIdSection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.gnu.build-id", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set(1);
BuildIdSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for BuildIdSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod build_id {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let size = ctx.args.build_id.size() as u64 + 16; // +16 for the header
ctx.buildid.as_mut().unwrap().hdr.shdr.sh_size.set(size);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.buildid.as_ref().unwrap();
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(&mut buf[4..], ctx.args.build_id.size() as u32); // Hash size
E::Endian::write_u32(&mut buf[8..], NT_GNU_BUILD_ID);
buf[12..16].copy_from_slice(b"GNU\0"); // Name string
buf[16..16 + sec.contents.len()].copy_from_slice(&sec.contents); // Build ID
}
}
// .note.package is an optional hint section that can contain arbitrary
// string. Package managers, such as dpkg or rpm, uses the section to
// embed package metadata into each ELF file so that it is easy to find
// the origin of an ELF file without any additional information.
#[derive(Debug)]
pub struct NotePackageSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> NotePackageSection<E> {
pub fn new() -> NotePackageSection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.package", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
NotePackageSection { hdr }
}
}
impl<E: Layout> Default for NotePackageSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod note_package {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if !ctx.args.package_metadata.is_empty() {
// +17 is for the header and the NUL terminator
ctx.note_package
.hdr
.shdr
.sh_size
.set(align_to(ctx.args.package_metadata.len() as u64 + 17, 4));
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(
&mut buf[4..],
ctx.note_package.hdr.shdr.sh_size.get() as u32 - 16,
); // Content size
E::Endian::write_u32(&mut buf[8..], NT_FDO_PACKAGING_METADATA);
buf[12..16].copy_from_slice(b"FDO\0");
write_cstr(&mut buf[16..], ctx.args.package_metadata.as_bytes()); // Content
}
}
// .note.gnu.property section contains an additional runtime information
// about ISA variant.
#[derive(Debug)]
pub struct NotePropertySection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<(u32, u32)>,
}
impl<E: Arch> NotePropertySection<E> {
pub fn new() -> NotePropertySection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.gnu.property", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
NotePropertySection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Arch> Default for NotePropertySection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod note_property {
use super::*;
fn entry_size<E: Arch>() -> usize {
if E::IS_64 {
16
} else {
12
}
}
// Merges input files' .note.gnu.property values.
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
// Obtain the list of keys
let files: Vec<&crate::input_files::ObjectFile<E>> = ctx
.objs
.iter()
.filter(|file| !ctx.is_internal(file.id()))
.collect();
let keys: BTreeSet<u32> = files
.iter()
.flat_map(|f| f.gnu_properties.keys().copied())
.collect();
let value = |f: &crate::input_files::ObjectFile<E>, key: u32| {
f.gnu_properties.get(&key).copied().unwrap_or(0)
};
// Merge values for each key
let mut map: BTreeMap<u32, u32> = BTreeMap::new();
for key in keys {
if (GNU_PROPERTY_X86_UINT32_AND_LO..=GNU_PROPERTY_X86_UINT32_AND_HI).contains(&key) {
// An AND feature is set if all input objects have the property and
// the feature.
map.insert(
key,
files.iter().fold(u32::MAX, |acc, f| acc & value(f, key)),
);
} else if (GNU_PROPERTY_X86_UINT32_OR_LO..=GNU_PROPERTY_X86_UINT32_OR_HI).contains(&key)
{
// An OR feature is set if some input object has the feature.
map.insert(key, files.iter().fold(0, |acc, f| acc | value(f, key)));
} else if (GNU_PROPERTY_X86_UINT32_OR_AND_LO..=GNU_PROPERTY_X86_UINT32_OR_AND_HI)
.contains(&key)
{
// An OR-AND feature is set if all input object files have the property
// and some of them has the feature.
if files.iter().all(|f| f.gnu_properties.contains_key(&key)) {
map.insert(key, files.iter().fold(0, |acc, f| acc | value(f, key)));
}
}
}
if ctx.args.z_ibt {
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |=
GNU_PROPERTY_X86_FEATURE_1_IBT;
}
if ctx.args.z_shstk {
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |=
GNU_PROPERTY_X86_FEATURE_1_SHSTK;
}
*map.entry(GNU_PROPERTY_X86_ISA_1_NEEDED).or_insert(0) |= ctx.args.z_x86_64_isa_level;
// Serialize the map
let contents: Vec<(u32, u32)> = map.into_iter().filter(|&(_, v)| v != 0).collect();
let sec = ctx.note_property.as_mut().unwrap();
sec.hdr.shdr.sh_size.set(if contents.is_empty() {
0
} else {
(16 + contents.len() * entry_size::<E>()) as u64
});
sec.contents = contents;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.note_property.as_ref().unwrap();
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(&mut buf[4..], sec.hdr.shdr.sh_size.get() as u32 - 16); // Content size
E::Endian::write_u32(&mut buf[8..], NT_GNU_PROPERTY_TYPE_0);
buf[12..16].copy_from_slice(b"GNU\0");
for (i, &(ty, val)) in sec.contents.iter().enumerate() {
let off = 16 + i * entry_size::<E>();
E::Endian::write_u32(&mut buf[off..], ty);
E::Endian::write_u32(&mut buf[off + 4..], 4);
E::Endian::write_u32(&mut buf[off + 8..], val); // Content
}
}
}
// .gnu_debuglink section contains a pathname and its CRC32 checksum for a
// separate debug info file. gdb can read the section to read debug info
// from an external file.
#[derive(Debug)]
pub struct GnuDebuglinkSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub filename: String,
pub crc32: u32,
}
impl<E: Layout> GnuDebuglinkSection<E> {
pub fn new() -> GnuDebuglinkSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu_debuglink", SHT_PROGBITS, 0);
hdr.shdr.sh_addralign.set(4);
GnuDebuglinkSection {
hdr,
filename: String::new(),
crc32: 0,
}
}
}
impl<E: Layout> Default for GnuDebuglinkSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod gnu_debuglink {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let filename = path_filename(&ctx.args.separate_debug_file);
let sec = ctx.gnu_debuglink.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set(align_to(filename.len() as u64 + 1, 4) + 4);
sec.filename = filename;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.gnu_debuglink.as_ref().unwrap();
buf.fill(0);
write_cstr(buf, sec.filename.as_bytes());
let n = buf.len();
E::Endian::write_u32(&mut buf[n - 4..], sec.crc32);
}
}
// PT_GNU_RELRO works on page granularity. We want to align its end to
// a page boundary. We append this section at end of a segment so that
// the segment always ends at a page boundary.
#[derive(Debug)]
pub struct RelroPaddingSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> RelroPaddingSection<E> {
pub fn new() -> RelroPaddingSection<E> {
let mut hdr =
ChunkHeader::<E>::new(".relro_padding", SHT_NOBITS, (SHF_ALLOC | SHF_WRITE) as u64);
hdr.is_relro = true;
hdr.shdr.sh_size.set(1);
RelroPaddingSection { hdr }
}
}
impl<E: Layout> Default for RelroPaddingSection<E> {
fn default() -> Self {
Self::new()
}
}
// Debug sections can be compressed with zlib or zstd to reduce the
// overall size of an ELF file. CompressedSection represents a compressed
// section.
#[derive(Debug)]
pub struct CompressedSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub chdr: ElfChdr<E>,
pub compressor: Compressor,
/// Kept for --gdb-index, which reads the uncompressed contents.
pub uncompressed_data: Option<Vec<u8>>,
pub original: ChunkId,
}
impl std::fmt::Debug for Compressor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Compressor({} bytes)", self.compressed_size())
}
}
pub mod compressed {
use super::*;
pub fn new<E: Arch>(ctx: &Context<E>, original: ChunkId) -> CompressedSection<E> {
let hdr = ctx.chunk_header(original);
// C++ mold uses uninitialized storage here to avoid zero-filling a
// potentially large scratch buffer. Rust currently uses a
// zero-initialized Vec because write_to accepts &mut [u8].
let mut buf = vec![0u8; hdr.shdr.sh_size.get() as usize];
// Write uncompressed contents and then compress them
output_chunks::write_to(ctx, original, &mut buf);
let level = ctx.args.compress_debug_sections_level;
let compressor = if ctx.args.compress_debug_sections == ELFCOMPRESS_ZLIB {
Compressor::zlib(&buf, level as u32)
} else {
Compressor::zstd(&buf, level as i32)
};
// Compute header field values
let mut chdr = ElfChdr::<E>::default();
chdr.ch_type_mut().set(ctx.args.compress_debug_sections);
chdr.ch_size_mut().set(hdr.shdr.sh_size.get());
chdr.ch_addralign_mut().set(hdr.shdr.sh_addralign.get());
let mut new_hdr = ChunkHeader::<E>::with_name(
hdr.name,
hdr.shdr.sh_type.get(),
hdr.shdr.sh_flags.get() | SHF_COMPRESSED as u64,
);
new_hdr.shndx = hdr.shndx;
new_hdr.is_compressed = true;
new_hdr.shdr = hdr.shdr;
new_hdr
.shdr
.sh_flags
.set(new_hdr.shdr.sh_flags.get() | SHF_COMPRESSED as u64);
new_hdr.shdr.sh_addralign.set(1);
new_hdr
.shdr
.sh_size
.set((std::mem::size_of::<ElfChdr<E>>() + compressor.compressed_size()) as u64);
// We can discard the uncompressed contents unless --gdb-index is given
CompressedSection {
hdr: new_hdr,
chdr,
compressor,
uncompressed_data: ctx.args.gdb_index.then_some(buf),
original,
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, i: u32, buf: &mut [u8]) {
let sec = &ctx.compressed_sections[i as usize];
sec.chdr.write(buf);
sec.compressor
.write_to(&mut buf[std::mem::size_of::<ElfChdr<E>>()..]);
}
}
// RelocSection represents a relocation table for an output file.
// This is used only for the relocatable output (i.e. the `-r` output).
#[derive(Debug)]
pub struct RelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub output_section: OutputSectionId,
/// The index of the first relocation of each member.
offsets: Vec<u64>,
}
pub mod reloc {
use super::*;
pub fn new<E: Arch>(ctx: &Context<E>, osec_id: OutputSectionId) -> RelocSection<E> {
let osec = &ctx.output_sections[osec_id.index()];
let name = format!(
"{}{}",
if E::IS_RELA { ".rela" } else { ".rel" },
osec.hdr.name
);
let name = BStr::new(crate::util::leak_bytes(name.into_bytes()));
let mut hdr = ChunkHeader::<E>::with_name(
name,
if E::IS_RELA { SHT_RELA } else { SHT_REL },
SHF_INFO_LINK as u64,
);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
// Compute an offset for each input section
let mut offsets = Vec::with_capacity(osec.members.len());
let mut sum = 0u64;
for &m in &osec.members {
offsets.push(sum);
let isec = ctx.input_section(m);
let file = &ctx.objs[isec.file.index()];
sum += isec.rels(file).len() as u64;
}
hdr.shdr
.sh_size
.set(sum * std::mem::size_of::<ElfRel<E>>() as u64);
RelocSection {
hdr,
output_section: osec_id,
offsets,
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>, i: u32) {
let symtab_shndx = ctx.symtab.hdr.shndx;
let osec = ctx.reloc_sections[i as usize].output_section;
let osec_shndx = ctx.output_sections[osec.index()].hdr.shndx;
let sec = &mut ctx.reloc_sections[i as usize];
sec.hdr.shdr.sh_link.set(symtab_shndx);
sec.hdr.shdr.sh_info.set(osec_shndx);
}
// Translates an input relocation's symbol reference into the {r_sym, addend}
// pair that is valid in the output file. The returned r_sym is either an output
// section index (for section-relative relocs) or an output symbol table index.
fn symidx_addend<E: Arch>(
ctx: &Context<E>,
isec: &InputSection<E>,
rel: &ElfRel<E>,
) -> (u32, i64) {
let file = &ctx.objs[isec.file.index()];
let sym = &ctx.symbols[file.base.symbols[rel.r_sym() as usize]];
if !isec.is_alloc() {
if let Some((frag, addend)) = isec.fragment(ctx, rel) {
let msec = &ctx.merged_sections[frag.section.index()];
return (
msec.hdr.shndx,
msec.fragments.get(frag.entry).offset() as i64 + addend,
);
}
}
if sym.st_type() == STT_SECTION {
match sym.origin::<E>() {
OriginValue::Fragment(frag) => {
let msec = &ctx.merged_sections[frag.section.index()];
return (
msec.hdr.shndx,
msec.fragments.get(frag.entry).offset() as i64
+ sym.value as i64
+ isec.rel_addend(rel),
);
}
OriginValue::InputSection(section) => {
let target = ctx.input_section(section);
if let Some(osec) = target.output_section {
return (
ctx.output_section(osec).hdr.shndx,
isec.rel_addend(rel) + target.offset() as i64,
);
}
}
_ => {}
}
// This is usually a dead debug section referring to a
// COMDAT-eliminated section.
return (0, 0);
}
if sym.write_to_symtab() {
return (sym.output_sym_idx(ctx), isec.rel_addend(rel));
}
(0, 0)
}
/// Writes the relocations. With `-r` on a REL target, the addends are
/// written into the output section's bytes, passed as `osec_buf`.
pub fn copy_buf<E: Arch>(
ctx: &Context<E>,
i: u32,
buf: &mut [u8],
osec_buf: Option<&mut [u8]>,
) {
let sec = &ctx.reloc_sections[i as usize];
let osec = &ctx.output_sections[sec.output_section.index()];
let out = rels_from_bytes_mut::<E>(buf);
let mut osec_buf = osec_buf;
for (mi, &m) in osec.members.iter().enumerate() {
let isec = ctx.input_section(m);
let file = &ctx.objs[isec.file.index()];
let base = sec.offsets[mi] as usize;
for (j, rel) in isec.rels(file).iter().enumerate() {
let (symidx, addend) = symidx_addend(ctx, isec, rel);
let mut r_offset = osec.hdr.shdr.sh_addr.get() + isec.offset() + rel.r_offset();
if E::IS_RISCV || E::IS_LOONGARCH {
// On RISC-V and LoongArch, relaxation may have deleted instructions,
// shifting this relocation's offset.
r_offset -= r_delta(isec, rel.r_offset()) as u64;
}
// SH4 object files store addends in the relocated places rather
// than in r_addend, and the relocation records we emit here are
// meant to be consumed as if they were in an object file, so we
// follow that convention.
let out_addend = if E::FAMILY == crate::arch::Family::Sh4 {
0
} else {
addend
};
out[base + j] = ElfRel::<E>::new(r_offset, rel.r_type(), symidx, out_addend);
if ctx.args.relocatable {
if let Some(osec_buf) = osec_buf.as_deref_mut() {
let loc = (isec.offset() + rel.r_offset()) as usize;
E::write_addend(&mut osec_buf[loc..], addend, rel);
}
}
}
}
}
}
// ComdatGroupSection represents a comdat group for an output file.
// This is used only for the relocatable output (i.e. the `-r` output).
#[derive(Debug)]
pub struct ComdatGroupSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub sym: SymbolId,
pub members: Vec<ChunkId>,
}
impl<E: Layout> ComdatGroupSection<E> {
pub fn new(sym: SymbolId, members: Vec<ChunkId>) -> ComdatGroupSection<E> {
let mut hdr = ChunkHeader::<E>::new(".group", SHT_GROUP, 0);
hdr.shdr.sh_entsize.set(4);
hdr.shdr.sh_addralign.set(4);
hdr.shdr.sh_size.set((members.len() * 4 + 4) as u64);
ComdatGroupSection { hdr, sym, members }
}
}
pub mod comdat_group {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>, i: u32) {
debug_assert!(ctx.args.relocatable);
let sec = &ctx.comdat_group_sections[i as usize];
let sym = &ctx.symbols[sec.sym];
let sh_info = if sym.st_type() == STT_SECTION {
let isec = sym.input_section_ref(ctx).unwrap();
ctx.output_section(isec.output_section.unwrap()).hdr.shndx
} else {
sym.output_sym_idx(ctx)
};
let symtab_shndx = ctx.symtab.hdr.shndx;
let sec = &mut ctx.comdat_group_sections[i as usize];
sec.hdr.shdr.sh_link.set(symtab_shndx);
sec.hdr.shdr.sh_info.set(sh_info);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, i: u32, buf: &mut [u8]) {
let sec = &ctx.comdat_group_sections[i as usize];
E::Endian::write_u32(buf, GRP_COMDAT);
for (j, &member) in sec.members.iter().enumerate() {
E::Endian::write_u32(&mut buf[4 + j * 4..], ctx.chunk_header(member).shndx);
}
}
}
/// `.riscv.attributes` describes the ISA the output requires, merged
/// from the input files' attributes.
#[derive(Debug)]
pub struct RiscvAttributesSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> RiscvAttributesSection<E> {
pub fn new() -> RiscvAttributesSection<E> {
RiscvAttributesSection {
hdr: ChunkHeader::<E>::new(".riscv.attributes", SHT_RISCV_ATTRIBUTES, 0),
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for RiscvAttributesSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod riscv_attributes {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if !ctx.riscv_attributes.as_ref().unwrap().contents.is_empty() {
return;
}
let contents = crate::arch::riscv::attributes_contents(ctx);
let sec = ctx.riscv_attributes.as_mut().unwrap();
sec.hdr.shdr.sh_size.set(contents.len() as u64);
sec.contents = contents;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.riscv_attributes.as_ref().unwrap();
buf[..sec.contents.len()].copy_from_slice(&sec.contents);
}
}
/// `.save_restore_regs`, the register save and restore routines that GCC
/// expects the linker to provide on PowerPC64 ELFv2.
#[derive(Debug)]
pub struct Ppc64SaveRestoreSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> Ppc64SaveRestoreSection<E> {
pub fn new() -> Ppc64SaveRestoreSection<E> {
let mut hdr = ChunkHeader::<E>::new(
".save_restore_regs",
SHT_PROGBITS,
(SHF_ALLOC | SHF_EXECINSTR) as u64,
);
hdr.shdr.sh_addralign.set(16);
hdr.shdr
.sh_size
.set((crate::arch::ppc64v2::SAVE_RESTORE_INSNS.len() * 4) as u64);
Ppc64SaveRestoreSection { hdr }
}
}
impl<E: Layout> Default for Ppc64SaveRestoreSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod ppc64_save_restore {
use super::*;
pub fn copy_buf<E: Arch>(_ctx: &Context<E>, buf: &mut [u8]) {
buf.copy_from_slice(&crate::arch::ppc64v2::save_restore_contents());
}
}
+94 -65
View File
@@ -7,16 +7,45 @@
//! Operations common to all chunks dispatch on the id.
pub mod arm_exidx;
pub mod build_id;
pub mod comdat_group;
pub mod compressed;
pub mod copyrel;
pub mod dynamic;
pub mod dynstr;
pub mod dynsym;
pub mod eh_frame;
pub mod eh_frame_hdr;
pub mod eh_frame_reloc;
pub mod gnu_debuglink;
pub mod gnu_hash;
pub mod got;
pub mod gotplt;
pub mod hash;
pub mod interp;
pub mod merged;
pub mod misc;
pub mod note_package;
pub mod note_property;
pub mod opd;
pub mod output_section;
pub mod plt;
pub mod pltgot;
pub mod ppc64_save_restore;
pub mod reldyn;
pub mod reloc;
pub mod relplt;
pub mod relrdyn;
pub mod relro_padding;
pub mod riscv_attributes;
pub mod sframe;
pub mod sframe_reloc;
pub mod shstrtab;
pub mod strtab;
pub mod symtab;
pub mod version;
pub mod symtab_shndx;
pub mod verdef;
pub mod verneed;
pub mod versym;
use std::num::NonZeroU32;
@@ -693,32 +722,32 @@ pub fn update_phdr<E: Arch>(ctx: &mut Context<E>) {
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>, id: ChunkId) {
match id {
ChunkId::Phdr => update_phdr(ctx),
ChunkId::Interp => misc::interp::update_shdr(ctx),
ChunkId::GotPlt => got::gotplt::update_shdr(ctx),
ChunkId::RelPlt => got::relplt::update_shdr(ctx),
ChunkId::RelDyn => dynamic::reldyn::update_shdr(ctx),
ChunkId::Dynamic => dynamic::dynamic::update_shdr(ctx),
ChunkId::Strtab => symtab::strtab::update_shdr(ctx),
ChunkId::Shstrtab => symtab::shstrtab::update_shdr(ctx),
ChunkId::Plt => got::plt::update_shdr(ctx),
ChunkId::Symtab => symtab::symtab::update_shdr(ctx),
ChunkId::Dynsym => symtab::dynsym::update_shdr(ctx),
ChunkId::Hash => symtab::hash::update_shdr(ctx),
ChunkId::GnuHash => symtab::gnu_hash::update_shdr(ctx),
ChunkId::EhFrameHdr => eh_frame::eh_frame_hdr::update_shdr(ctx),
ChunkId::EhFrameReloc => eh_frame::eh_frame_reloc::update_shdr(ctx),
ChunkId::SFrameReloc => sframe::sframe_reloc::update_shdr(ctx),
ChunkId::Versym => version::versym::update_shdr(ctx),
ChunkId::Verneed => version::verneed::update_shdr(ctx),
ChunkId::Verdef => version::verdef::update_shdr(ctx),
ChunkId::BuildId => misc::build_id::update_shdr(ctx),
ChunkId::NotePackage => misc::note_package::update_shdr(ctx),
ChunkId::NoteProperty => misc::note_property::update_shdr(ctx),
ChunkId::RiscvAttributes => misc::riscv_attributes::update_shdr(ctx),
ChunkId::Interp => interp::update_shdr(ctx),
ChunkId::GotPlt => gotplt::update_shdr(ctx),
ChunkId::RelPlt => relplt::update_shdr(ctx),
ChunkId::RelDyn => reldyn::update_shdr(ctx),
ChunkId::Dynamic => dynamic::update_shdr(ctx),
ChunkId::Strtab => strtab::update_shdr(ctx),
ChunkId::Shstrtab => shstrtab::update_shdr(ctx),
ChunkId::Plt => plt::update_shdr(ctx),
ChunkId::Symtab => symtab::update_shdr(ctx),
ChunkId::Dynsym => dynsym::update_shdr(ctx),
ChunkId::Hash => hash::update_shdr(ctx),
ChunkId::GnuHash => gnu_hash::update_shdr(ctx),
ChunkId::EhFrameHdr => eh_frame_hdr::update_shdr(ctx),
ChunkId::EhFrameReloc => eh_frame_reloc::update_shdr(ctx),
ChunkId::SFrameReloc => sframe_reloc::update_shdr(ctx),
ChunkId::Versym => versym::update_shdr(ctx),
ChunkId::Verneed => verneed::update_shdr(ctx),
ChunkId::Verdef => verdef::update_shdr(ctx),
ChunkId::BuildId => build_id::update_shdr(ctx),
ChunkId::NotePackage => note_package::update_shdr(ctx),
ChunkId::NoteProperty => note_property::update_shdr(ctx),
ChunkId::RiscvAttributes => riscv_attributes::update_shdr(ctx),
ChunkId::ArmExidx => arm_exidx::update_shdr(ctx),
ChunkId::GnuDebuglink => misc::gnu_debuglink::update_shdr(ctx),
ChunkId::Reloc(i) => misc::reloc::update_shdr(ctx, i),
ChunkId::ComdatGroup(i) => misc::comdat_group::update_shdr(ctx, i),
ChunkId::GnuDebuglink => gnu_debuglink::update_shdr(ctx),
ChunkId::Reloc(i) => reloc::update_shdr(ctx, i),
ChunkId::ComdatGroup(i) => comdat_group::update_shdr(ctx, i),
_ => {}
}
}
@@ -738,7 +767,7 @@ pub fn compute_section_size<E: Arch>(ctx: &mut Context<E>, id: ChunkId) {
pub fn num_dynrels<E: Arch>(ctx: &Context<E>, id: ChunkId) -> u64 {
match id {
ChunkId::Output(id) => output_section::num_dynrels(ctx, id),
ChunkId::Got => got::got::num_dynrels(ctx),
ChunkId::Got => got::num_dynrels(ctx),
ChunkId::Copyrel => ctx.copyrel.symbols.len() as u64,
ChunkId::CopyrelRelro => ctx.copyrel_relro.symbols.len() as u64,
ChunkId::Ppc64Opd => opd::num_dynrels(ctx),
@@ -751,7 +780,7 @@ pub fn num_dynrels<E: Arch>(ctx: &Context<E>, id: ChunkId) -> u64 {
pub fn relr_offsets<E: Arch>(ctx: &mut Context<E>, id: ChunkId) -> Vec<u64> {
match id {
ChunkId::Output(id) => output_section::relr_offsets(ctx, id),
ChunkId::Got => got::got::relr_offsets(ctx),
ChunkId::Got => got::relr_offsets(ctx),
ChunkId::Ppc64Opd => opd::relr_offsets(ctx),
_ => Vec::new(),
}
@@ -761,9 +790,9 @@ pub fn relr_offsets<E: Arch>(ctx: &mut Context<E>, id: ChunkId) -> Vec<u64> {
pub fn write_dynrels<E: Arch>(ctx: &Context<E>, id: ChunkId, out: &mut [E::Rel]) {
match id {
ChunkId::Output(id) => output_section::write_dynrels(ctx, id, out),
ChunkId::Got => got::got::write_dynrels(ctx, out),
ChunkId::Copyrel => misc::copyrel::write_dynrels(ctx, &ctx.copyrel, out),
ChunkId::CopyrelRelro => misc::copyrel::write_dynrels(ctx, &ctx.copyrel_relro, out),
ChunkId::Got => got::write_dynrels(ctx, out),
ChunkId::Copyrel => copyrel::write_dynrels(ctx, &ctx.copyrel, out),
ChunkId::CopyrelRelro => copyrel::write_dynrels(ctx, &ctx.copyrel_relro, out),
ChunkId::Ppc64Opd => opd::write_dynrels(ctx, out),
_ => {}
}
@@ -773,9 +802,9 @@ pub fn write_dynrels<E: Arch>(ctx: &Context<E>, id: ChunkId, out: &mut [E::Rel])
pub fn compute_symtab_size<E: Arch>(ctx: &mut Context<E>, id: ChunkId) {
match id {
ChunkId::Output(id) => output_section::compute_symtab_size(ctx, id),
ChunkId::Got => got::got::compute_symtab_size(ctx),
ChunkId::Plt => got::plt::compute_symtab_size(ctx),
ChunkId::PltGot => got::pltgot::compute_symtab_size(ctx),
ChunkId::Got => got::compute_symtab_size(ctx),
ChunkId::Plt => plt::compute_symtab_size(ctx),
ChunkId::PltGot => pltgot::compute_symtab_size(ctx),
_ => {}
}
}
@@ -784,9 +813,9 @@ pub fn compute_symtab_size<E: Arch>(ctx: &mut Context<E>, id: ChunkId) {
pub fn populate_symtab<E: Arch>(ctx: &Context<E>, id: ChunkId, block: &mut SymtabBlock<'_>) {
match id {
ChunkId::Output(id) => output_section::populate_symtab(ctx, id, block),
ChunkId::Got => got::got::populate_symtab(ctx, block),
ChunkId::Plt => got::plt::populate_symtab(ctx, block),
ChunkId::PltGot => got::pltgot::populate_symtab(ctx, block),
ChunkId::Got => got::populate_symtab(ctx, block),
ChunkId::Plt => plt::populate_symtab(ctx, block),
ChunkId::PltGot => pltgot::populate_symtab(ctx, block),
_ => {}
}
}
@@ -804,44 +833,44 @@ pub fn copy_buf<E: Arch>(ctx: &Context<E>, id: ChunkId, buf: &mut [u8]) {
let phdrs = &ctx.phdr.as_ref().unwrap().phdrs;
ElfPhdr::<E>::write_all(phdrs, buf);
}
ChunkId::Interp => misc::interp::copy_buf(ctx, buf),
ChunkId::Got => got::got::copy_buf(ctx, buf),
ChunkId::GotPlt => got::gotplt::copy_buf(ctx, buf),
ChunkId::RelPlt => got::relplt::copy_buf(ctx, buf),
ChunkId::RelDyn => dynamic::reldyn::copy_buf(ctx, buf),
ChunkId::RelrDyn => dynamic::relrdyn::copy_buf(ctx, buf),
ChunkId::Dynamic => dynamic::dynamic::copy_buf(ctx, buf),
ChunkId::Strtab => symtab::strtab::copy_buf(ctx, buf),
ChunkId::Dynstr => symtab::dynstr::copy_buf(ctx, buf),
ChunkId::Hash => symtab::hash::copy_buf(ctx, buf),
ChunkId::GnuHash => symtab::gnu_hash::copy_buf(ctx, buf),
ChunkId::GnuDebuglink => misc::gnu_debuglink::copy_buf(ctx, buf),
ChunkId::Shstrtab => symtab::shstrtab::copy_buf(ctx, buf),
ChunkId::Plt => got::plt::copy_buf(ctx, buf),
ChunkId::PltGot => got::pltgot::copy_buf(ctx, buf),
ChunkId::Interp => interp::copy_buf(ctx, buf),
ChunkId::Got => got::copy_buf(ctx, buf),
ChunkId::GotPlt => gotplt::copy_buf(ctx, buf),
ChunkId::RelPlt => relplt::copy_buf(ctx, buf),
ChunkId::RelDyn => reldyn::copy_buf(ctx, buf),
ChunkId::RelrDyn => relrdyn::copy_buf(ctx, buf),
ChunkId::Dynamic => dynamic::copy_buf(ctx, buf),
ChunkId::Strtab => strtab::copy_buf(ctx, buf),
ChunkId::Dynstr => dynstr::copy_buf(ctx, buf),
ChunkId::Hash => hash::copy_buf(ctx, buf),
ChunkId::GnuHash => gnu_hash::copy_buf(ctx, buf),
ChunkId::GnuDebuglink => gnu_debuglink::copy_buf(ctx, buf),
ChunkId::Shstrtab => shstrtab::copy_buf(ctx, buf),
ChunkId::Plt => plt::copy_buf(ctx, buf),
ChunkId::PltGot => pltgot::copy_buf(ctx, buf),
ChunkId::Symtab | ChunkId::SymtabShndx => {}
ChunkId::Dynsym => symtab::dynsym::copy_buf(ctx, buf),
ChunkId::Dynsym => dynsym::copy_buf(ctx, buf),
ChunkId::EhFrame | ChunkId::EhFrameHdr => {}
ChunkId::EhFrameReloc => {}
ChunkId::SFrame => sframe::copy_buf(ctx, buf),
ChunkId::SFrameReloc => sframe::sframe_reloc::copy_buf(ctx, buf),
ChunkId::SFrameReloc => sframe_reloc::copy_buf(ctx, buf),
ChunkId::Copyrel | ChunkId::CopyrelRelro => {}
ChunkId::Versym => version::versym::copy_buf(ctx, buf),
ChunkId::Verneed => version::verneed::copy_buf(ctx, buf),
ChunkId::Verdef => version::verdef::copy_buf(ctx, buf),
ChunkId::BuildId => misc::build_id::copy_buf(ctx, buf),
ChunkId::NotePackage => misc::note_package::copy_buf(ctx, buf),
ChunkId::NoteProperty => misc::note_property::copy_buf(ctx, buf),
ChunkId::RiscvAttributes => misc::riscv_attributes::copy_buf(ctx, buf),
ChunkId::Versym => versym::copy_buf(ctx, buf),
ChunkId::Verneed => verneed::copy_buf(ctx, buf),
ChunkId::Verdef => verdef::copy_buf(ctx, buf),
ChunkId::BuildId => build_id::copy_buf(ctx, buf),
ChunkId::NotePackage => note_package::copy_buf(ctx, buf),
ChunkId::NoteProperty => note_property::copy_buf(ctx, buf),
ChunkId::RiscvAttributes => riscv_attributes::copy_buf(ctx, buf),
ChunkId::ArmExidx => arm_exidx::copy_buf(ctx, buf),
ChunkId::Ppc64SaveRestore => misc::ppc64_save_restore::copy_buf(ctx, buf),
ChunkId::Ppc64SaveRestore => ppc64_save_restore::copy_buf(ctx, buf),
ChunkId::Ppc64Opd => opd::copy_buf(ctx, buf),
ChunkId::GdbIndex | ChunkId::RelroPadding | ChunkId::Placeholder(_) => {}
ChunkId::Output(id) => output_section::copy_buf(ctx, id, buf),
ChunkId::Merged(id) => merged::copy_buf(ctx, id, buf),
ChunkId::Reloc(_) => {}
ChunkId::ComdatGroup(i) => misc::comdat_group::copy_buf(ctx, i, buf),
ChunkId::Compressed(i) => misc::compressed::copy_buf(ctx, i, buf),
ChunkId::ComdatGroup(i) => comdat_group::copy_buf(ctx, i, buf),
ChunkId::Compressed(i) => compressed::copy_buf(ctx, i, buf),
}
}
+54
View File
@@ -0,0 +1,54 @@
//! `.note.package`, package metadata.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::util::align_to;
use crate::util::write_cstr;
// .note.package is an optional hint section that can contain arbitrary
// string. Package managers, such as dpkg or rpm, uses the section to
// embed package metadata into each ELF file so that it is easy to find
// the origin of an ELF file without any additional information.
#[derive(Debug)]
pub struct NotePackageSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> NotePackageSection<E> {
pub fn new() -> NotePackageSection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.package", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
NotePackageSection { hdr }
}
}
impl<E: Layout> Default for NotePackageSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if !ctx.args.package_metadata.is_empty() {
// +17 is for the header and the NUL terminator
ctx.note_package
.hdr
.shdr
.sh_size
.set(align_to(ctx.args.package_metadata.len() as u64 + 17, 4));
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(
&mut buf[4..],
ctx.note_package.hdr.shdr.sh_size.get() as u32 - 16,
); // Content size
E::Endian::write_u32(&mut buf[8..], NT_FDO_PACKAGING_METADATA);
buf[12..16].copy_from_slice(b"FDO\0");
write_cstr(&mut buf[16..], ctx.args.package_metadata.as_bytes()); // Content
}
+115
View File
@@ -0,0 +1,115 @@
//! `.note.gnu.property`, merged ISA properties.
use std::collections::{BTreeMap, BTreeSet};
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .note.gnu.property section contains an additional runtime information
// about ISA variant.
#[derive(Debug)]
pub struct NotePropertySection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<(u32, u32)>,
}
impl<E: Arch> NotePropertySection<E> {
pub fn new() -> NotePropertySection<E> {
let mut hdr = ChunkHeader::<E>::new(".note.gnu.property", SHT_NOTE, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
NotePropertySection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Arch> Default for NotePropertySection<E> {
fn default() -> Self {
Self::new()
}
}
fn entry_size<E: Arch>() -> usize {
if E::IS_64 {
16
} else {
12
}
}
// Merges input files' .note.gnu.property values.
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
// Obtain the list of keys
let files: Vec<&crate::input_files::ObjectFile<E>> = ctx
.objs
.iter()
.filter(|file| !ctx.is_internal(file.id()))
.collect();
let keys: BTreeSet<u32> = files
.iter()
.flat_map(|f| f.gnu_properties.keys().copied())
.collect();
let value = |f: &crate::input_files::ObjectFile<E>, key: u32| {
f.gnu_properties.get(&key).copied().unwrap_or(0)
};
// Merge values for each key
let mut map: BTreeMap<u32, u32> = BTreeMap::new();
for key in keys {
if (GNU_PROPERTY_X86_UINT32_AND_LO..=GNU_PROPERTY_X86_UINT32_AND_HI).contains(&key) {
// An AND feature is set if all input objects have the property and
// the feature.
map.insert(
key,
files.iter().fold(u32::MAX, |acc, f| acc & value(f, key)),
);
} else if (GNU_PROPERTY_X86_UINT32_OR_LO..=GNU_PROPERTY_X86_UINT32_OR_HI).contains(&key) {
// An OR feature is set if some input object has the feature.
map.insert(key, files.iter().fold(0, |acc, f| acc | value(f, key)));
} else if (GNU_PROPERTY_X86_UINT32_OR_AND_LO..=GNU_PROPERTY_X86_UINT32_OR_AND_HI)
.contains(&key)
{
// An OR-AND feature is set if all input object files have the property
// and some of them has the feature.
if files.iter().all(|f| f.gnu_properties.contains_key(&key)) {
map.insert(key, files.iter().fold(0, |acc, f| acc | value(f, key)));
}
}
}
if ctx.args.z_ibt {
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |= GNU_PROPERTY_X86_FEATURE_1_IBT;
}
if ctx.args.z_shstk {
*map.entry(GNU_PROPERTY_X86_FEATURE_1_AND).or_insert(0) |= GNU_PROPERTY_X86_FEATURE_1_SHSTK;
}
*map.entry(GNU_PROPERTY_X86_ISA_1_NEEDED).or_insert(0) |= ctx.args.z_x86_64_isa_level;
// Serialize the map
let contents: Vec<(u32, u32)> = map.into_iter().filter(|&(_, v)| v != 0).collect();
let sec = ctx.note_property.as_mut().unwrap();
sec.hdr.shdr.sh_size.set(if contents.is_empty() {
0
} else {
(16 + contents.len() * entry_size::<E>()) as u64
});
sec.contents = contents;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.note_property.as_ref().unwrap();
buf.fill(0);
E::Endian::write_u32(buf, 4); // Name size
E::Endian::write_u32(&mut buf[4..], sec.hdr.shdr.sh_size.get() as u32 - 16); // Content size
E::Endian::write_u32(&mut buf[8..], NT_GNU_PROPERTY_TYPE_0);
buf[12..16].copy_from_slice(b"GNU\0");
for (i, &(ty, val)) in sec.contents.iter().enumerate() {
let off = 16 + i * entry_size::<E>();
E::Endian::write_u32(&mut buf[off..], ty);
E::Endian::write_u32(&mut buf[off + 4..], 4);
E::Endian::write_u32(&mut buf[off + 8..], val); // Content
}
}
+3 -12
View File
@@ -601,18 +601,9 @@ pub fn populate_symtab<E: Arch>(
block.push_synthetic::<E>(name, suffix.as_bytes(), func(addr));
if E::FAMILY == Family::Arm32 {
// Emit "$t", "$a" and "$d" if ARM32.
block.push_mapping_symbol::<E>(
crate::output_chunks::symtab::strtab::THUMB,
func(addr),
);
block.push_mapping_symbol::<E>(
crate::output_chunks::symtab::strtab::ARM,
func(addr + 4),
);
block.push_mapping_symbol::<E>(
crate::output_chunks::symtab::strtab::DATA,
func(addr + 12),
);
block.push_mapping_symbol::<E>(crate::output_chunks::strtab::THUMB, func(addr));
block.push_mapping_symbol::<E>(crate::output_chunks::strtab::ARM, func(addr + 4));
block.push_mapping_symbol::<E>(crate::output_chunks::strtab::DATA, func(addr + 12));
}
}
}
+149
View File
@@ -0,0 +1,149 @@
//! `.plt`, stubs for runtime lazy symbol resolution.
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::input_files::SymtabBlock;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
// .plt contains linker-synthesized stub code that acts as if they are
// functions. They are in fact immediately branches to real function entry
// points. .plt is used as a stub for runtime lazy symbol resolution.
#[derive(Debug)]
pub struct PltSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub symbols: Vec<SymbolId>,
}
impl<E: Arch> PltSection<E> {
pub fn new() -> PltSection<E> {
let mut hdr =
ChunkHeader::<E>::new(".plt", SHT_PROGBITS, (SHF_ALLOC | SHF_EXECINSTR) as u64);
if E::IS_SPARC {
hdr.shdr
.sh_flags
.set(hdr.shdr.sh_flags.get() | SHF_WRITE as u64);
hdr.shdr.sh_addralign.set(256);
} else {
hdr.shdr.sh_addralign.set(16);
}
PltSection {
hdr,
symbols: Vec::new(),
}
}
}
impl<E: Arch> Default for PltSection<E> {
fn default() -> Self {
Self::new()
}
}
// On SPARC, .plt uses 32-byte "small" entries until it grows past 0x100000
// bytes (the reach of a small entry's branch to the resolver), after which
// it switches to a "large" entry format. This is how many small entries fit.
pub const SPARC_NUM_SMALL_PLT: u64 = (0x100000 - 128) / 32;
/// The offset of a PLT entry within `.plt`.
pub fn entry_offset<E: Arch>(idx: u32) -> u64 {
let idx = idx as u64;
match E::FAMILY {
Family::Ppc64V1 => {
// The PPC64 ELFv1 ABI requires PLT entries to vary in size
// depending on their indices. For entries whose PLT index is
// less than 32768, the entry size is 8 bytes. Other entries are
// 12 bytes long.
if idx < 0x8000 {
E::PLT_HDR_SIZE + idx * 8
} else {
E::PLT_HDR_SIZE + 0x8000 * 8 + (idx - 0x8000) * 12
}
}
Family::Sparc64 => {
// SPARC large PLT entries are grouped into blocks of 160, each holding
// 160 24-byte code stubs followed by 160 8-byte data pointers (so a
// stub's `ldx` reaches its pointer within a signed 13-bit offset). This
// returns the offset of pltidx's code stub.
if idx < SPARC_NUM_SMALL_PLT {
E::PLT_HDR_SIZE + idx * E::PLT_SIZE
} else {
let i = idx - SPARC_NUM_SMALL_PLT;
0x100000 + (i / 160) * 5120 + (i % 160) * 24
}
}
_ => E::PLT_HDR_SIZE + idx * E::PLT_SIZE,
}
}
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, sym: SymbolId) {
debug_assert!(!ctx.symbols[sym].has_plt(&ctx.symbols));
let idx = ctx.plt.symbols.len() as u32;
ctx.symbols.aux_mut(sym).plt_idx = Some(idx);
ctx.plt.symbols.push(sym);
crate::output_chunks::dynsym::add_symbol(ctx, sym);
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let n = ctx.plt.symbols.len() as u64;
ctx.plt.hdr.shdr.sh_size.set(if n == 0 {
0
} else if E::IS_SPARC {
E::PLT_HDR_SIZE + n * E::PLT_SIZE
} else {
entry_offset::<E>(n as u32)
});
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_plt_header(ctx, buf);
for (i, &id) in ctx.plt.symbols.iter().enumerate() {
let off = entry_offset::<E>(i as u32) as usize;
E::write_plt_entry(ctx, &mut buf[off..], &ctx.symbols[id]);
}
}
pub fn compute_symtab_size<E: Arch>(ctx: &mut Context<E>) {
let n = ctx.plt.symbols.len() as u32;
let strtab_size: u64 = ctx
.plt
.symbols
.iter()
.map(|&id| ctx.symbols[id].name().len() as u64 + "$plt".len() as u64 + 1)
.sum();
ctx.plt.hdr.num_local_symtab = if E::FAMILY == Family::Arm32 {
n * 3 + 2
} else {
n
};
ctx.plt.hdr.strtab_size = strtab_size;
}
pub fn populate_symtab<E: Arch>(ctx: &Context<E>, block: &mut SymtabBlock<'_>) {
let plt = &ctx.plt;
if plt.hdr.num_local_symtab == 0 {
return;
}
let func = |addr: u64| {
let mut sym = ElfSym::<E>::default();
sym.st_shndx_mut().set(plt.hdr.shndx as u16);
sym.st_value_mut().set(addr);
sym.set_type(STT_FUNC);
sym
};
use crate::output_chunks::strtab::{ARM, DATA};
if E::FAMILY == Family::Arm32 {
block.push_mapping_symbol::<E>(ARM, func(plt.hdr.shdr.sh_addr.get()));
block.push_mapping_symbol::<E>(DATA, func(plt.hdr.shdr.sh_addr.get() + 16));
}
for &id in &plt.symbols {
let sym = &ctx.symbols[id];
let addr = sym.plt_addr(ctx);
block.push_synthetic::<E>(sym.name(), b"$plt", func(addr));
if E::FAMILY == Family::Arm32 {
block.push_mapping_symbol::<E>(ARM, func(addr));
block.push_mapping_symbol::<E>(DATA, func(addr + 12));
}
}
}
+92
View File
@@ -0,0 +1,92 @@
//! `.plt.got`, PLT stubs for symbols already resolved through the GOT.
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::input_files::SymtabBlock;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
// .plt.got is similar to .plt but doesn't support lazy symbol resolution.
// If we have the same symbol already in .got, resolving the same symbol
// lazily for .plt is just waste of time. Therefore, in such case, we use
// .plt.got for that symbol instead.
#[derive(Debug)]
pub struct PltGotSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub symbols: Vec<SymbolId>,
}
impl<E: Layout> PltGotSection<E> {
pub fn new() -> PltGotSection<E> {
let mut hdr =
ChunkHeader::<E>::new(".plt.got", SHT_PROGBITS, (SHF_ALLOC | SHF_EXECINSTR) as u64);
hdr.shdr.sh_addralign.set(16);
PltGotSection {
hdr,
symbols: Vec::new(),
}
}
}
impl<E: Layout> Default for PltGotSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, sym: SymbolId) {
debug_assert!(!ctx.symbols[sym].has_plt(&ctx.symbols));
debug_assert!(ctx.symbols[sym].has_got(&ctx.symbols));
let idx = ctx.pltgot.symbols.len() as u32;
ctx.symbols.aux_mut(sym).pltgot_idx = Some(idx);
ctx.pltgot.symbols.push(sym);
ctx.pltgot
.hdr
.shdr
.sh_size
.set(ctx.pltgot.symbols.len() as u64 * E::PLTGOT_SIZE);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
for (i, &id) in ctx.pltgot.symbols.iter().enumerate() {
let off = i * E::PLTGOT_SIZE as usize;
E::write_pltgot_entry(ctx, &mut buf[off..], &ctx.symbols[id]);
}
}
pub fn compute_symtab_size<E: Arch>(ctx: &mut Context<E>) {
let n = ctx.pltgot.symbols.len() as u32;
let strtab_size: u64 = ctx
.pltgot
.symbols
.iter()
.map(|&id| ctx.symbols[id].name().len() as u64 + "$pltgot".len() as u64 + 1)
.sum();
ctx.pltgot.hdr.num_local_symtab = if E::FAMILY == Family::Arm32 { n * 3 } else { n };
ctx.pltgot.hdr.strtab_size = strtab_size;
}
pub fn populate_symtab<E: Arch>(ctx: &Context<E>, block: &mut SymtabBlock<'_>) {
let pltgot = &ctx.pltgot;
if pltgot.hdr.num_local_symtab == 0 {
return;
}
let func = |addr: u64| {
let mut sym = ElfSym::<E>::default();
sym.st_shndx_mut().set(pltgot.hdr.shndx as u16);
sym.st_value_mut().set(addr);
sym.set_type(STT_FUNC);
sym
};
use crate::output_chunks::strtab::{ARM, DATA};
for &id in &pltgot.symbols {
let sym = &ctx.symbols[id];
let addr = sym.plt_addr(ctx);
block.push_synthetic::<E>(sym.name(), b"$pltgot", func(addr));
if E::FAMILY == Family::Arm32 {
block.push_mapping_symbol::<E>(ARM, func(addr));
block.push_mapping_symbol::<E>(DATA, func(addr + 12));
}
}
}
+38
View File
@@ -0,0 +1,38 @@
//! PowerPC64 register save and restore functions.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
/// `.save_restore_regs`, the register save and restore routines that GCC
/// expects the linker to provide on PowerPC64 ELFv2.
#[derive(Debug)]
pub struct Ppc64SaveRestoreSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> Ppc64SaveRestoreSection<E> {
pub fn new() -> Ppc64SaveRestoreSection<E> {
let mut hdr = ChunkHeader::<E>::new(
".save_restore_regs",
SHT_PROGBITS,
(SHF_ALLOC | SHF_EXECINSTR) as u64,
);
hdr.shdr.sh_addralign.set(16);
hdr.shdr
.sh_size
.set((crate::arch::ppc64v2::SAVE_RESTORE_INSNS.len() * 4) as u64);
Ppc64SaveRestoreSection { hdr }
}
}
impl<E: Layout> Default for Ppc64SaveRestoreSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn copy_buf<E: Arch>(_ctx: &Context<E>, buf: &mut [u8]) {
buf.copy_from_slice(&crate::arch::ppc64v2::save_restore_contents());
}
+302
View File
@@ -0,0 +1,302 @@
//! `.rel.dyn` and `.rela.dyn`, dynamic relocations.
use rayon::prelude::*;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::relrdyn::encode_relr;
use crate::output_chunks::{self, ChunkHeader};
use crate::util::encode_sleb;
// .rel.dyn contains relocation infromation for other sections.
#[derive(Debug)]
pub struct RelDynSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub android_encoded: Vec<u8>,
pub keep_android_size: bool,
}
impl<E: Arch> RelDynSection<E> {
pub fn new(args: &crate::cmdline::Args) -> RelDynSection<E> {
let name = if E::IS_RELA { ".rela.dyn" } else { ".rel.dyn" };
let mut hdr = ChunkHeader::<E>::new(name, 0, SHF_ALLOC as u64);
if args.pack_dyn_relocs_android {
hdr.shdr.sh_type.set(if E::IS_RELA {
SHT_ANDROID_RELA
} else {
SHT_ANDROID_REL
});
hdr.shdr.sh_entsize.set(0);
hdr.shdr.sh_addralign.set(1);
} else {
hdr.shdr
.sh_type
.set(if E::IS_RELA { SHT_RELA } else { SHT_REL });
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
}
RelDynSection {
hdr,
android_encoded: Vec::new(),
keep_android_size: false,
}
}
}
/// Gathers the dynamic relocations of all chunks.
pub fn collect_relocs<E: Arch>(ctx: &Context<E>) -> Vec<ElfRel<E>> {
let count: usize = ctx
.chunks
.iter()
.map(|&id| {
let hdr = ctx.chunk_header(id);
(hdr.num_dynrels - hdr.num_relrs) as usize
})
.sum();
let mut out = vec![ElfRel::<E>::default(); count];
let mut rest = out.as_mut_slice();
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
let count = (hdr.num_dynrels - hdr.num_relrs) as usize;
if count != 0 {
let (slots, tail) = std::mem::take(&mut rest).split_at_mut(count);
output_chunks::write_dynrels(ctx, id, slots);
rest = tail;
}
}
debug_assert!(rest.is_empty());
out
}
/// Encodes base relocations of each chunk in RELR form, using offsets
/// relative to the chunk.
pub fn construct_relr<E: Arch>(ctx: &mut Context<E>) {
debug_assert!(ctx.args.pack_dyn_relocs_relr);
let word = E::WORD_SIZE as u64;
let ids = ctx.chunks.clone();
for &id in &ids {
let n = output_chunks::num_dynrels(ctx, id);
let hdr = ctx.chunk_header_mut(id);
hdr.num_dynrels = n;
hdr.num_relrs = 0;
hdr.relr.clear();
// Do not use RELR for executable chunks, as they don't usually contain
// base relocations.
if hdr.shdr.sh_flags.get() & SHF_EXECINSTR as u64 != 0 {
continue;
}
// --section-start can override a chunk's alignment. Conservatively use
// .rel[a].dyn if the explicitly assigned address is not word-aligned.
let name = String::from_utf8_lossy(hdr.name).into_owned();
if ctx
.args
.section_start
.get(&name)
.is_some_and(|&addr| addr % word != 0)
{
continue;
}
if n != 0 {
let offsets = output_chunks::relr_offsets(ctx, id);
let hdr = ctx.chunk_header_mut(id);
hdr.num_relrs = offsets.len() as u64;
debug_assert!(hdr.num_relrs <= hdr.num_dynrels);
hdr.relr = encode_relr::<E>(&offsets);
}
}
let size: u64 = ids
.iter()
.map(|&id| ctx.chunk_header(id).relr.len() as u64 * word)
.sum();
if let Some(relrdyn) = &mut ctx.relrdyn {
relrdyn.hdr.shdr.sh_size.set(size);
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let ids = ctx.chunks.clone();
if !ctx.args.pack_dyn_relocs_relr {
for &id in &ids {
let n = output_chunks::num_dynrels(ctx, id);
let hdr = ctx.chunk_header_mut(id);
hdr.num_dynrels = n;
hdr.num_relrs = 0;
}
}
let mut num_relocs = 0;
let mut num_relrs = 0;
for &id in &ids {
let hdr = ctx.chunk_header(id);
num_relocs += hdr.num_dynrels;
num_relrs += hdr.num_relrs;
}
if ctx.args.pack_dyn_relocs_android {
let relocs = collect_relocs(ctx);
// APS2 uses SLEB128-encoded deltas, so .rela.dyn size may oscillate
// as addresses move. If a shrink is followed by a growth, stop
// shrinking and pad the encoded stream to converge.
let encoded = encode_android::<E>(relocs);
let old_size = ctx.reldyn.hdr.shdr.sh_size.get() as usize;
let reldyn = &mut ctx.reldyn;
if old_size != 0 && old_size < encoded.len() {
reldyn.keep_android_size = true;
}
reldyn.android_encoded = encoded;
if reldyn.keep_android_size && reldyn.android_encoded.len() < old_size {
reldyn.android_encoded.resize(old_size, 0);
}
reldyn
.hdr
.shdr
.sh_size
.set(reldyn.android_encoded.len() as u64);
} else {
ctx.reldyn
.hdr
.shdr
.sh_size
.set((num_relocs - num_relrs) * std::mem::size_of::<ElfRel<E>>() as u64);
}
ctx.reldyn.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
if ctx.args.pack_dyn_relocs_android {
buf[..ctx.reldyn.android_encoded.len()].copy_from_slice(&ctx.reldyn.android_encoded);
} else {
let size = std::mem::size_of::<ElfRel<E>>();
let mut rest = buf;
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
let count = (hdr.num_dynrels - hdr.num_relrs) as usize;
if count != 0 {
let (slots, tail) = std::mem::take(&mut rest).split_at_mut(count * size);
output_chunks::write_dynrels(ctx, id, rels_from_bytes_mut::<E>(slots));
rest = tail;
}
}
debug_assert!(rest.is_empty());
}
}
// Sort dynamic relocations. This is the reason why we do it.
// Quote from https://www.airs.com/blog/archives/186
//
// The dynamic linker in glibc uses a one element cache when processing
// relocs: if a relocation refers to the same symbol as the previous
// relocation, then the dynamic linker reuses the value rather than
// looking up the symbol again. Thus the dynamic linker gets the best
// results if the dynamic relocations are sorted so that all dynamic
// relocations for a given dynamic symbol are adjacent.
//
// Other than that, the linker sorts together all relative relocations,
// which don't have symbols. Two relative relocations, or two relocations
// against the same symbol, are sorted by the address in the output
// file. This tends to optimize paging and caching when there are two
// references from the same page.
pub fn sort<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
// .rela.dyn contains APS2-encoded bytes, not ElfRel entries.
if ctx.args.pack_dyn_relocs_android {
return;
}
// We group IFUNC relocations at the end of .rel.dyn because we want to
// apply all the other relocations before running user-supplied IFUNC
// resolvers.
let rank = |r_type: u32| -> u32 {
if r_type == E::R_RELATIVE {
0
} else if Some(r_type) == E::R_IRELATIVE {
2
} else {
1
}
};
let relocs = rels_from_bytes_mut::<E>(buf);
relocs.par_sort_by_key(|r| (rank(r.r_type()), r.r_sym(), r.r_offset()));
}
// Encode dynamic relocations using the Android Packed Relocation format
// (APS2). The encoded stream begins with the magic bytes "APS2" followed
// by SLEB128-encoded fields. Relocations are emitted in groups; within a
// group, common offset deltas, info values, and addend deltas are
// factored out so each per-relocation entry is just the differing fields.
//
// See bionic's linker/linker_relocs.cpp for the decoder.
pub fn encode_android<E: Arch>(mut rels: Vec<ElfRel<E>>) -> Vec<u8> {
const GROUPED_BY_INFO: i64 = 1;
const GROUPED_BY_OFFSET_DELTA: i64 = 2;
const GROUP_HAS_ADDEND: i64 = 8;
let r_info = |r: &ElfRel<E>| -> i64 {
if E::IS_64 {
(((r.r_sym() as u64) << 32) | r.r_type() as u64) as i64
} else {
(((r.r_sym() as u64) << 8) | (r.r_type() & 0xff) as u64) as i64
}
};
let mut buf = b"APS2".to_vec();
encode_sleb(&mut buf, rels.len() as i64);
encode_sleb(&mut buf, 0); // initial offset state
if rels.is_empty() {
return buf;
}
// APS2 offset deltas are signed, so the format doesn't require
// relocations to be in increasing-offset order. Sort by (r_type,
// r_sym, r_offset) so all relocs of the dominant type (typically
// R_RELATIVE, ~90% of dynrels in a real Android binary) land in one
// contiguous block. That collapses dozens of type-broken groups into
// a few large info-grouped runs.
rels.sort_by_key(|r| (r.r_type(), r.r_sym(), r.r_offset()));
let mut prev_offset = 0i64;
let mut prev_addend = 0i64;
let mut i = 0;
while i < rels.len() {
let offset_delta = rels[i].r_offset() as i64 - prev_offset;
let cur_info = r_info(&rels[i]);
// Greedily extend the group while consecutive relocations share the
// same offset_delta and r_info.
let mut j = i + 1;
while j < rels.len()
&& r_info(&rels[j]) == cur_info
&& rels[j].r_offset() as i64 - rels[j - 1].r_offset() as i64 == offset_delta
{
j += 1;
}
let size = (j - i) as i64;
let mut flags = 0;
if size > 1 {
flags = GROUPED_BY_INFO | GROUPED_BY_OFFSET_DELTA;
}
if E::IS_RELA {
flags |= GROUP_HAS_ADDEND;
}
encode_sleb(&mut buf, size);
encode_sleb(&mut buf, flags);
encode_sleb(&mut buf, offset_delta);
encode_sleb(&mut buf, cur_info);
if E::IS_RELA {
for r in &rels[i..j] {
encode_sleb(&mut buf, r.r_addend() - prev_addend);
prev_addend = r.r_addend();
}
}
prev_offset = rels[j - 1].r_offset() as i64;
i = j;
}
buf
}
+158
View File
@@ -0,0 +1,158 @@
//! Relocation tables for relocatable outputs.
use bstr::BStr;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_sections::{r_delta, InputSection};
use crate::output_chunks::{ChunkHeader, OutputSectionId};
use crate::symbol::OriginValue;
// RelocSection represents a relocation table for an output file.
// This is used only for the relocatable output (i.e. the `-r` output).
#[derive(Debug)]
pub struct RelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub output_section: OutputSectionId,
/// The index of the first relocation of each member.
offsets: Vec<u64>,
}
pub fn new<E: Arch>(ctx: &Context<E>, osec_id: OutputSectionId) -> RelocSection<E> {
let osec = &ctx.output_sections[osec_id.index()];
let name = format!(
"{}{}",
if E::IS_RELA { ".rela" } else { ".rel" },
osec.hdr.name
);
let name = BStr::new(crate::util::leak_bytes(name.into_bytes()));
let mut hdr = ChunkHeader::<E>::with_name(
name,
if E::IS_RELA { SHT_RELA } else { SHT_REL },
SHF_INFO_LINK as u64,
);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
// Compute an offset for each input section
let mut offsets = Vec::with_capacity(osec.members.len());
let mut sum = 0u64;
for &m in &osec.members {
offsets.push(sum);
let isec = ctx.input_section(m);
let file = &ctx.objs[isec.file.index()];
sum += isec.rels(file).len() as u64;
}
hdr.shdr
.sh_size
.set(sum * std::mem::size_of::<ElfRel<E>>() as u64);
RelocSection {
hdr,
output_section: osec_id,
offsets,
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>, i: u32) {
let symtab_shndx = ctx.symtab.hdr.shndx;
let osec = ctx.reloc_sections[i as usize].output_section;
let osec_shndx = ctx.output_sections[osec.index()].hdr.shndx;
let sec = &mut ctx.reloc_sections[i as usize];
sec.hdr.shdr.sh_link.set(symtab_shndx);
sec.hdr.shdr.sh_info.set(osec_shndx);
}
// Translates an input relocation's symbol reference into the {r_sym, addend}
// pair that is valid in the output file. The returned r_sym is either an output
// section index (for section-relative relocs) or an output symbol table index.
fn symidx_addend<E: Arch>(ctx: &Context<E>, isec: &InputSection<E>, rel: &ElfRel<E>) -> (u32, i64) {
let file = &ctx.objs[isec.file.index()];
let sym = &ctx.symbols[file.base.symbols[rel.r_sym() as usize]];
if !isec.is_alloc() {
if let Some((frag, addend)) = isec.fragment(ctx, rel) {
let msec = &ctx.merged_sections[frag.section.index()];
return (
msec.hdr.shndx,
msec.fragments.get(frag.entry).offset() as i64 + addend,
);
}
}
if sym.st_type() == STT_SECTION {
match sym.origin::<E>() {
OriginValue::Fragment(frag) => {
let msec = &ctx.merged_sections[frag.section.index()];
return (
msec.hdr.shndx,
msec.fragments.get(frag.entry).offset() as i64
+ sym.value as i64
+ isec.rel_addend(rel),
);
}
OriginValue::InputSection(section) => {
let target = ctx.input_section(section);
if let Some(osec) = target.output_section {
return (
ctx.output_section(osec).hdr.shndx,
isec.rel_addend(rel) + target.offset() as i64,
);
}
}
_ => {}
}
// This is usually a dead debug section referring to a
// COMDAT-eliminated section.
return (0, 0);
}
if sym.write_to_symtab() {
return (sym.output_sym_idx(ctx), isec.rel_addend(rel));
}
(0, 0)
}
/// Writes the relocations. With `-r` on a REL target, the addends are
/// written into the output section's bytes, passed as `osec_buf`.
pub fn copy_buf<E: Arch>(ctx: &Context<E>, i: u32, buf: &mut [u8], osec_buf: Option<&mut [u8]>) {
let sec = &ctx.reloc_sections[i as usize];
let osec = &ctx.output_sections[sec.output_section.index()];
let out = rels_from_bytes_mut::<E>(buf);
let mut osec_buf = osec_buf;
for (mi, &m) in osec.members.iter().enumerate() {
let isec = ctx.input_section(m);
let file = &ctx.objs[isec.file.index()];
let base = sec.offsets[mi] as usize;
for (j, rel) in isec.rels(file).iter().enumerate() {
let (symidx, addend) = symidx_addend(ctx, isec, rel);
let mut r_offset = osec.hdr.shdr.sh_addr.get() + isec.offset() + rel.r_offset();
if E::IS_RISCV || E::IS_LOONGARCH {
// On RISC-V and LoongArch, relaxation may have deleted instructions,
// shifting this relocation's offset.
r_offset -= r_delta(isec, rel.r_offset()) as u64;
}
// SH4 object files store addends in the relocated places rather
// than in r_addend, and the relocation records we emit here are
// meant to be consumed as if they were in an object file, so we
// follow that convention.
let out_addend = if E::FAMILY == crate::arch::Family::Sh4 {
0
} else {
addend
};
out[base + j] = ElfRel::<E>::new(r_offset, rel.r_type(), symidx, out_addend);
if ctx.args.relocatable {
if let Some(osec_buf) = osec_buf.as_deref_mut() {
let loc = (isec.offset() + rel.r_offset()) as usize;
E::write_addend(&mut osec_buf[loc..], addend, rel);
}
}
}
}
}
+93
View File
@@ -0,0 +1,93 @@
//! `.rel.plt` and `.rela.plt`, relocations for PLT entries.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::{plt, ChunkHeader};
// .rel.plt contains relocation information for .plt.
#[derive(Debug)]
pub struct RelPltSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> RelPltSection<E> {
pub fn new() -> RelPltSection<E> {
let (name, ty) = if E::IS_RELA {
(".rela.plt", SHT_RELA)
} else {
(".rel.plt", SHT_REL)
};
let mut hdr = ChunkHeader::<E>::new(name, ty, SHF_ALLOC as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
RelPltSection { hdr }
}
}
impl<E: Arch> Default for RelPltSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.relplt
.hdr
.shdr
.sh_size
.set(ctx.plt.symbols.len() as u64 * std::mem::size_of::<ElfRel<E>>() as u64);
ctx.relplt.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
if !E::IS_SPARC {
ctx.relplt.hdr.shdr.sh_info.set(ctx.gotplt.hdr.shndx);
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let out = rels_from_bytes_mut::<E>(buf);
debug_assert_eq!(out.len(), ctx.plt.symbols.len());
for (i, &id) in ctx.plt.symbols.iter().enumerate() {
let sym = &ctx.symbols[id];
let rel = if E::IS_SPARC {
// SPARC doesn't have a .got.plt because its role is merged to .plt.
// On SPARC, .plt is writable (!) and the dynamic linker directly
// modifies .plt's machine instructions as it resolves dynamic symbols.
// Therefore, it doesn't need a separate section to store the symbol
// resolution results. That is of course horrible from the security
// point of view, though.
let idx = sym.plt_idx(&ctx.symbols).unwrap() as u64;
if idx < plt::SPARC_NUM_SMALL_PLT {
ElfRel::<E>::new(
sym.plt_addr(ctx),
E::R_JUMP_SLOT,
sym.dynsym_idx(&ctx.symbols).unwrap_or(0),
0,
)
} else {
// A large PLT entry resolves through a data pointer rather than
// self-modifying code, so its relocation targets that pointer and
// carries -(call address) as the addend, making the loader store
// (target - call) there (see arch/sparc64.rs).
let call = sym.plt_addr(ctx) + 4;
let ptr = ctx.plt.hdr.shdr.sh_addr.get()
+ crate::arch::sparc64::plt_ptr_offset(ctx.plt.symbols.len(), idx);
ElfRel::<E>::new(
ptr,
E::R_JUMP_SLOT,
sym.dynsym_idx(&ctx.symbols).unwrap_or(0),
-(call as i64),
)
}
} else {
ElfRel::<E>::new(
sym.gotplt_addr(ctx),
E::R_JUMP_SLOT,
sym.dynsym_idx(&ctx.symbols).unwrap_or(0),
0,
)
};
out[i] = rel;
}
}
+124
View File
@@ -0,0 +1,124 @@
//! `.relr.dyn`, packed base relocations.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .relr.dyn is a relatively new section to contain base relocation
// information.
//
// A relocatable executable/DSO contains a lot of certain type of
// relocation entries, called "base relocations", to specify the locations
// of pointers in the FILE that need to be adjusted according to the
// desired load address and the actual load address. As an example,
// consider the following C code.
//
// extern int foo;
// int *bar = &foo;
//
// If an executable containing the above code is built as relocatable
// executable, meaning that the executable can be loaded not to a specific
// address in memory but anywhere in the virtual address space, then the
// pointer `bar`'s address is not known at link-time.
//
// The linker temporarily links the executable to a base address, record
// that information to the ELF header, and emits dynamic relocations to
// refer to the location of `bar`. At runtime, the loader adds the
// difference of the expected load address and the actual one to the
// pointer value to fix the pointer value.
//
// Relocatable executables/DSOs usually contain a fairly large number of
// base relocations. In particular, C++ virtual function table is an array
// of statically-initialized pointers which need base relocations.
//
// Notice that base relocations don't contain symbol information. They
// need only pointer locations in the ELF file that need fixing at
// load-time. Therefore, storing that information to the usual ELF
// relocation table is waste of space.
//
// .relr.dyn is designed to store base relocations in a space-efficient way.
#[derive(Debug)]
pub struct RelrDynSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> RelrDynSection<E> {
pub fn new(args: &crate::cmdline::Args) -> RelrDynSection<E> {
let ty = if args.use_android_relr_tags {
SHT_ANDROID_RELR
} else {
SHT_RELR
};
let mut hdr = ChunkHeader::<E>::new(".relr.dyn", ty, SHF_ALLOC as u64);
hdr.shdr.sh_entsize.set(E::WORD_SIZE as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
RelrDynSection { hdr }
}
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let w = E::WORD_SIZE;
let mut i = 0;
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
for &val in &hdr.relr {
let v = if val & 1 != 0 {
val
} else {
hdr.shdr.sh_addr.get() + val
};
if E::IS_64 {
E::Endian::write_u64(&mut buf[i * w..], v);
} else {
E::Endian::write_u32(&mut buf[i * w..], v as u32);
}
i += 1;
}
}
}
// .relr.dyn contains base relocations encoded in a space-efficient form.
// The contents of the section is essentially just a list of addresses
// that have to be fixed up at runtime.
//
// Here is the encoding scheme (we assume 64-bit ELF in this description
// for the sake of simplicity): .relr.dyn contains zero or more address
// groups. Each address group consists of a 64-bit start address followed
// by zero or more 63-bit bitmaps. Let A be the address of a start
// address. Then, the loader fixes address A. If Nth bit in the following
// bitmap is on, the loader also fixes address A + N * 8. In this scheme,
// one address and one bitmap can represent up to 64 base relocations in a
// 512 bytes range.
//
// A start address and a bitmap is distinguished by the lowest significant
// bit. An address must be even and thus its LSB is 0 (odd address is not
// representable in this encoding and such relocation must be stored to
// the .rel.dyn section). A bitmap has LSB 1.
pub fn encode_relr<E: Arch>(offsets: &[u64]) -> Vec<u64> {
let word = E::WORD_SIZE as u64;
let num_bits = if E::IS_64 { 63 } else { 31 };
let max_delta = word * num_bits;
let mut vec = Vec::new();
let mut i = 0;
while i < offsets.len() {
let first = offsets[i];
vec.push(first);
let mut base = first + word;
i += 1;
loop {
let mut bits = 0u64;
while i < offsets.len() && offsets[i] - base < max_delta {
bits |= 1 << ((offsets[i] - base) / word);
i += 1;
}
if bits == 0 {
break;
}
vec.push((bits << 1) | 1);
base += max_delta;
}
}
vec
}
+28
View File
@@ -0,0 +1,28 @@
//! Padding at the end of a RELRO segment.
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// PT_GNU_RELRO works on page granularity. We want to align its end to
// a page boundary. We append this section at end of a segment so that
// the segment always ends at a page boundary.
#[derive(Debug)]
pub struct RelroPaddingSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> RelroPaddingSection<E> {
pub fn new() -> RelroPaddingSection<E> {
let mut hdr =
ChunkHeader::<E>::new(".relro_padding", SHT_NOBITS, (SHF_ALLOC | SHF_WRITE) as u64);
hdr.is_relro = true;
hdr.shdr.sh_size.set(1);
RelroPaddingSection { hdr }
}
}
impl<E: Layout> Default for RelroPaddingSection<E> {
fn default() -> Self {
Self::new()
}
}
+44
View File
@@ -0,0 +1,44 @@
//! `.riscv.attributes`, merged RISC-V target attributes.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
/// `.riscv.attributes` describes the ISA the output requires, merged
/// from the input files' attributes.
#[derive(Debug)]
pub struct RiscvAttributesSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> RiscvAttributesSection<E> {
pub fn new() -> RiscvAttributesSection<E> {
RiscvAttributesSection {
hdr: ChunkHeader::<E>::new(".riscv.attributes", SHT_RISCV_ATTRIBUTES, 0),
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for RiscvAttributesSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if !ctx.riscv_attributes.as_ref().unwrap().contents.is_empty() {
return;
}
let contents = crate::arch::riscv::attributes_contents(ctx);
let sec = ctx.riscv_attributes.as_mut().unwrap();
sec.hdr.shdr.sh_size.set(contents.len() as u64);
sec.contents = contents;
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = ctx.riscv_attributes.as_ref().unwrap();
buf[..sec.contents.len()].copy_from_slice(&sec.contents);
}
-72
View File
@@ -160,75 +160,3 @@ pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
fre_off += fde.fre.len();
}
}
// SFrameRelocSection holds the relocations for .sframe. We use it only for
// relocatable outputs, where function addresses aren't known yet and each
// FDE's func_start has to remain a relocation for the final link to
// resolve. It is the .sframe counterpart of EhFrameRelocSection.
#[derive(Debug)]
pub struct SFrameRelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> SFrameRelocSection<E> {
pub fn new() -> SFrameRelocSection<E> {
let mut hdr = ChunkHeader::<E>::new(".rela.sframe", SHT_RELA, SHF_INFO_LINK as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
SFrameRelocSection { hdr }
}
}
impl<E: Arch> Default for SFrameRelocSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod sframe_reloc {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let n = ctx.sframe.fdes.len();
let sec = ctx.sframe_reloc.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set((n * std::mem::size_of::<ElfRel<E>>()) as u64);
sec.hdr.shdr.sh_link.set(ctx.symtab.hdr.shndx);
sec.hdr.shdr.sh_info.set(ctx.sframe.hdr.shndx);
}
// Emit one relocation per FDE for its func_start field. The entries are
// written in the same order as SFrameSection::copy_buf lays out the index.
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let Some(r_type) = E::R_SFRAME else {
return;
};
let out = rels_from_bytes_mut::<E>(buf);
debug_assert_eq!(out.len(), ctx.sframe.fdes.len());
for (i, &(fi, fi_idx)) in ctx.sframe.fdes.iter().enumerate() {
let fde = &ctx.objs[fi.index()].sframe_fdes[fi_idx as usize];
let sym = &ctx.symbols[fde.sym];
let r_offset = ctx.sframe.hdr.shdr.sh_addr.get()
+ SFrameHeader::<E>::size() as u64
+ (i * SFrameFdeIdx::<E>::size()) as u64;
let (r_sym, r_addend) = if sym.st_type() == STT_SECTION {
// We discard input section symbols and create a fresh one per output
// section, so a reference to a section symbol needs its addend
// adjusted by the input section's offset in its output section.
let target = sym.input_section_ref(ctx).unwrap();
(
ctx.output_section(target.output_section.unwrap()).hdr.shndx,
fde.addend + target.offset() as i64,
)
} else {
(sym.output_sym_idx(ctx), fde.addend)
};
out[i] = ElfRel::<E>::new(r_offset, r_type, r_sym, r_addend);
}
}
}
+74
View File
@@ -0,0 +1,74 @@
//! `.rela.sframe`, SFrame relocations for relocatable outputs.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// SFrameRelocSection holds the relocations for .sframe. We use it only for
// relocatable outputs, where function addresses aren't known yet and each
// FDE's func_start has to remain a relocation for the final link to
// resolve. It is the .sframe counterpart of EhFrameRelocSection.
#[derive(Debug)]
pub struct SFrameRelocSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> SFrameRelocSection<E> {
pub fn new() -> SFrameRelocSection<E> {
let mut hdr = ChunkHeader::<E>::new(".rela.sframe", SHT_RELA, SHF_INFO_LINK as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfRel<E>>() as u64);
SFrameRelocSection { hdr }
}
}
impl<E: Arch> Default for SFrameRelocSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let n = ctx.sframe.fdes.len();
let sec = ctx.sframe_reloc.as_mut().unwrap();
sec.hdr
.shdr
.sh_size
.set((n * std::mem::size_of::<ElfRel<E>>()) as u64);
sec.hdr.shdr.sh_link.set(ctx.symtab.hdr.shndx);
sec.hdr.shdr.sh_info.set(ctx.sframe.hdr.shndx);
}
// Emit one relocation per FDE for its func_start field. The entries are
// written in the same order as SFrameSection::copy_buf lays out the index.
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let Some(r_type) = E::R_SFRAME else {
return;
};
let out = rels_from_bytes_mut::<E>(buf);
debug_assert_eq!(out.len(), ctx.sframe.fdes.len());
for (i, &(fi, fi_idx)) in ctx.sframe.fdes.iter().enumerate() {
let fde = &ctx.objs[fi.index()].sframe_fdes[fi_idx as usize];
let sym = &ctx.symbols[fde.sym];
let r_offset = ctx.sframe.hdr.shdr.sh_addr.get()
+ SFrameHeader::<E>::size() as u64
+ (i * SFrameFdeIdx::<E>::size()) as u64;
let (r_sym, r_addend) = if sym.st_type() == STT_SECTION {
// We discard input section symbols and create a fresh one per output
// section, so a reference to a section symbol needs its addend
// adjusted by the input section's offset in its output section.
let target = sym.input_section_ref(ctx).unwrap();
(
ctx.output_section(target.output_section.unwrap()).hdr.shndx,
fde.addend + target.offset() as i64,
)
} else {
(sym.output_sym_idx(ctx), fde.addend)
};
out[i] = ElfRel::<E>::new(r_offset, r_type, r_sym, r_addend);
}
}
+64
View File
@@ -0,0 +1,64 @@
//! `.shstrtab`, output section names.
use std::collections::HashMap;
use bstr::BStr;
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
use crate::util::write_cstr;
// .shstrtab contains section names, such as ".text" or ".data". Just like
// .strtab, .shstrtab is not needed at runtime. One can remove .shstrtab
// and section table from an executable without breaking it.
#[derive(Debug)]
pub struct ShstrtabSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> ShstrtabSection<E> {
pub fn new() -> ShstrtabSection<E> {
ShstrtabSection {
hdr: ChunkHeader::<E>::new(".shstrtab", SHT_STRTAB, 0),
}
}
}
impl<E: Layout> Default for ShstrtabSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut map: HashMap<&'static BStr, u64> = HashMap::new();
let mut offset = 1u64;
for id in ctx.chunks.clone() {
if id.is_header() {
continue;
}
let name = ctx.chunk_header(id).name;
if name.is_empty() {
continue;
}
let off = *map.entry(name).or_insert_with(|| {
let off = offset;
offset += name.len() as u64 + 1;
off
});
ctx.chunk_header_mut(id).shdr.sh_name.set(off as u32);
}
ctx.shstrtab.as_mut().unwrap().hdr.shdr.sh_size.set(offset);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[0] = 0;
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.shdr.sh_name.get() != 0 {
write_cstr(&mut buf[hdr.shdr.sh_name.get() as usize..], hdr.name);
}
}
}
+72
View File
@@ -0,0 +1,72 @@
//! `.strtab`, names of non-dynamic symbols.
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .strtab is referenced by .strtab and contains symbol names. Note that
// .strtab is not needed at runtime; one can remove the section from an
// ELF file without breaking it. Strings that runtime accesses are stored
// in .dynstr.
#[derive(Debug)]
pub struct StrtabSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> StrtabSection<E> {
pub fn new() -> StrtabSection<E> {
StrtabSection {
hdr: ChunkHeader::<E>::new(".strtab", SHT_STRTAB, 0),
}
}
}
impl<E: Layout> Default for StrtabSection<E> {
fn default() -> Self {
Self::new()
}
}
// Offsets in .strtab for ARM32 mapping symbols
pub const ARM: u32 = 1;
pub const THUMB: u32 = 4;
pub const DATA: u32 = 7;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut offset = 1u64;
// ARM32 uses $a, $t and $t mapping symbols to mark the beginning of
// ARM, Thumb and data in text, respectively. These symbols don't
// affect correctness of the program but helps disassembler to
// disassemble machine code appropriately.
if E::FAMILY == Family::Arm32 && !ctx.args.strip_all {
offset += b"$a\0$t\0$d\0".len() as u64;
}
for id in ctx.chunks.clone() {
let hdr = ctx.chunk_header_mut(id);
hdr.strtab_offset = offset;
offset += hdr.strtab_size;
}
for file in &mut ctx.objs {
file.base.strtab_offset = offset;
offset += file.base.strtab_size;
}
for file in &mut ctx.dsos {
file.base.strtab_offset = offset;
offset += file.base.strtab_size;
}
ctx.strtab
.hdr
.shdr
.sh_size
.set(if offset == 1 { 0 } else { offset });
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[0] = 0;
if E::FAMILY == Family::Arm32 && !ctx.args.strip_all {
buf[1..10].copy_from_slice(b"$a\0$t\0$d\0");
}
}
+181 -719
View File
@@ -1,204 +1,14 @@
//! Symbol and string tables: `.symtab`, `.strtab`, `.shstrtab`, `.dynsym`,
//! `.dynstr`, `.hash` and `.gnu.hash`.
//! `.symtab`, the non-dynamic symbol table.
use std::collections::HashMap;
use bstr::BStr;
use rayon::prelude::*;
use crate::arch::{Arch, Family};
use crate::context::Context;
use crate::elf::*;
use crate::error;
use crate::input_files::{SymtabBlock, SymtabEntries};
use crate::input_sections::{r_delta, InputSection};
use crate::output_chunks::{self, ChunkHeader, ChunkId};
use crate::symbol::{AddrFlags, OriginValue, Symbol, SymbolId};
use crate::util::write_cstr;
// .strtab is referenced by .strtab and contains symbol names. Note that
// .strtab is not needed at runtime; one can remove the section from an
// ELF file without breaking it. Strings that runtime accesses are stored
// in .dynstr.
#[derive(Debug)]
pub struct StrtabSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> StrtabSection<E> {
pub fn new() -> StrtabSection<E> {
StrtabSection {
hdr: ChunkHeader::<E>::new(".strtab", SHT_STRTAB, 0),
}
}
}
impl<E: Layout> Default for StrtabSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod strtab {
use super::*;
// Offsets in .strtab for ARM32 mapping symbols
pub const ARM: u32 = 1;
pub const THUMB: u32 = 4;
pub const DATA: u32 = 7;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut offset = 1u64;
// ARM32 uses $a, $t and $t mapping symbols to mark the beginning of
// ARM, Thumb and data in text, respectively. These symbols don't
// affect correctness of the program but helps disassembler to
// disassemble machine code appropriately.
if E::FAMILY == Family::Arm32 && !ctx.args.strip_all {
offset += b"$a\0$t\0$d\0".len() as u64;
}
for id in ctx.chunks.clone() {
let hdr = ctx.chunk_header_mut(id);
hdr.strtab_offset = offset;
offset += hdr.strtab_size;
}
for file in &mut ctx.objs {
file.base.strtab_offset = offset;
offset += file.base.strtab_size;
}
for file in &mut ctx.dsos {
file.base.strtab_offset = offset;
offset += file.base.strtab_size;
}
ctx.strtab
.hdr
.shdr
.sh_size
.set(if offset == 1 { 0 } else { offset });
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[0] = 0;
if E::FAMILY == Family::Arm32 && !ctx.args.strip_all {
buf[1..10].copy_from_slice(b"$a\0$t\0$d\0");
}
}
}
// .shstrtab contains section names, such as ".text" or ".data". Just like
// .strtab, .shstrtab is not needed at runtime. One can remove .shstrtab
// and section table from an executable without breaking it.
#[derive(Debug)]
pub struct ShstrtabSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> ShstrtabSection<E> {
pub fn new() -> ShstrtabSection<E> {
ShstrtabSection {
hdr: ChunkHeader::<E>::new(".shstrtab", SHT_STRTAB, 0),
}
}
}
impl<E: Layout> Default for ShstrtabSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod shstrtab {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut map: HashMap<&'static BStr, u64> = HashMap::new();
let mut offset = 1u64;
for id in ctx.chunks.clone() {
if id.is_header() {
continue;
}
let name = ctx.chunk_header(id).name;
if name.is_empty() {
continue;
}
let off = *map.entry(name).or_insert_with(|| {
let off = offset;
offset += name.len() as u64 + 1;
off
});
ctx.chunk_header_mut(id).shdr.sh_name.set(off as u32);
}
ctx.shstrtab.as_mut().unwrap().hdr.shdr.sh_size.set(offset);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[0] = 0;
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.shdr.sh_name.get() != 0 {
write_cstr(&mut buf[hdr.shdr.sh_name.get() as usize..], hdr.name);
}
}
}
}
// .dynstr contains strings that the runtime uses.
#[derive(Debug)]
pub struct DynstrSection<E: Layout> {
pub hdr: ChunkHeader<E>,
strings: HashMap<Vec<u8>, u64>,
}
impl<E: Layout> DynstrSection<E> {
pub fn new() -> DynstrSection<E> {
DynstrSection {
hdr: ChunkHeader::<E>::new(".dynstr", SHT_STRTAB, SHF_ALLOC as u64),
strings: HashMap::new(),
}
}
pub fn add_string(&mut self, s: &[u8]) -> u64 {
if self.hdr.shdr.sh_size.get() == 0 {
self.strings.insert(Vec::new(), 0);
self.hdr.shdr.sh_size.set(1);
}
if let Some(&off) = self.strings.get(s) {
return off;
}
let off = self.hdr.shdr.sh_size.get();
self.strings.insert(s.to_vec(), off);
self.hdr
.shdr
.sh_size
.set(self.hdr.shdr.sh_size.get() + s.len() as u64 + 1);
off
}
pub fn find_string(&self, s: &[u8]) -> u64 {
*self.strings.get(s).expect("string not in .dynstr")
}
}
impl<E: Layout> Default for DynstrSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod dynstr {
use super::*;
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
for (s, &off) in &ctx.dynstr.strings {
write_cstr(&mut buf[off as usize..], s);
}
let mut off = ctx.dynsym.dynstr_offset as usize;
for &id in ctx.dynsym.symbols.iter().flatten() {
off += write_cstr(&mut buf[off..], ctx.symbols[id].name());
}
}
}
use crate::output_chunks::{self, strtab, ChunkHeader, ChunkId};
use crate::symbol::{AddrFlags, OriginValue, Symbol};
// .symtab contains non-dynamic symbols. The section is not needed at
// runtime and can be stripped from an ELF file without affecting the
@@ -225,242 +35,202 @@ impl<E: Arch> Default for SymtabSection<E> {
}
}
// .symtab_shndx is a parallel table for .symtab to contain section
// indices for symbols.
//
// Symbol table entry contains a field for section index, but that's only
// 16 bit in size, so it cannot refer to a section whose section index is
// greater than 65535. We use .symtab_shndx for ELF files containing a lot
// of sections.
//
// Use of this section is exceptional. Most ELF files don't contain one.
#[derive(Debug)]
pub struct SymtabShndxSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut nsyms = 1u32;
impl<E: Layout> SymtabShndxSection<E> {
pub fn new() -> SymtabShndxSection<E> {
let mut hdr = ChunkHeader::<E>::new(".symtab_shndx", SHT_SYMTAB_SHNDX, 0);
hdr.shdr.sh_entsize.set(4);
hdr.shdr.sh_addralign.set(4);
SymtabShndxSection { hdr }
// Section symbols
nsyms += ctx
.chunks
.iter()
.filter(|&&id| ctx.chunk_header(id).shndx != 0)
.count() as u32;
// Linker-synthesized symbols
for id in ctx.chunks.clone() {
let hdr = ctx.chunk_header_mut(id);
hdr.local_symtab_idx = nsyms;
nsyms += hdr.num_local_symtab;
}
}
impl<E: Layout> Default for SymtabShndxSection<E> {
fn default() -> Self {
Self::new()
// File local symbols
for file in &mut ctx.objs {
file.base.local_symtab_idx = nsyms;
nsyms += file.base.num_local_symtab;
}
// File global symbols
for file in &mut ctx.objs {
file.base.global_symtab_idx = nsyms;
nsyms += file.base.num_global_symtab;
}
for file in &mut ctx.dsos {
file.base.global_symtab_idx = nsyms;
nsyms += file.base.num_global_symtab;
}
ctx.symtab
.hdr
.shdr
.sh_info
.set(ctx.objs.first().map_or(nsyms, |f| f.base.global_symtab_idx));
ctx.symtab.hdr.shdr.sh_link.set(ctx.strtab.hdr.shndx);
ctx.symtab.hdr.shdr.sh_size.set(if nsyms == 1 {
0
} else {
nsyms as u64 * std::mem::size_of::<ElfSym<E>>() as u64
});
}
// Named after the chunk, like the other chunks' modules in this directory.
#[allow(clippy::module_inception)]
pub mod symtab {
use super::*;
/// Writes `.symtab`, `.strtab` and `.symtab_shndx`.
pub fn copy_buf<E: Arch>(
ctx: &Context<E>,
symtab: &mut [u8],
strtab: &mut [u8],
mut xindex: Option<&mut [u8]>,
) {
let size = std::mem::size_of::<ElfSym<E>>();
symtab[..size].fill(0);
if let Some(xindex) = xindex.as_deref_mut() {
xindex.fill(0);
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let mut nsyms = 1u32;
// Section symbols
nsyms += ctx
.chunks
.iter()
.filter(|&&id| ctx.chunk_header(id).shndx != 0)
.count() as u32;
// Linker-synthesized symbols
for id in ctx.chunks.clone() {
let hdr = ctx.chunk_header_mut(id);
hdr.local_symtab_idx = nsyms;
nsyms += hdr.num_local_symtab;
// Create section symbols
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.shndx == 0 {
continue;
}
// File local symbols
for file in &mut ctx.objs {
file.base.local_symtab_idx = nsyms;
nsyms += file.base.num_local_symtab;
let mut esym = ElfSym::<E>::default();
esym.st_value_mut().set(hdr.shdr.sh_addr.get());
esym.set_type(STT_SECTION);
match xindex.as_deref_mut() {
Some(xindex) => {
E::Endian::write_u32(&mut xindex[hdr.shndx as usize * 4..], hdr.shndx);
esym.st_shndx_mut().set(SHN_XINDEX as u16);
}
None => esym.st_shndx_mut().set(hdr.shndx as u16),
}
esym.write(&mut symtab[hdr.shndx as usize * size..]);
}
// File global symbols
for file in &mut ctx.objs {
file.base.global_symtab_idx = nsyms;
nsyms += file.base.num_global_symtab;
}
for file in &mut ctx.dsos {
file.base.global_symtab_idx = nsyms;
nsyms += file.base.num_global_symtab;
}
strtab::copy_buf(ctx, strtab);
ctx.symtab
.hdr
.shdr
.sh_info
.set(ctx.objs.first().map_or(nsyms, |f| f.base.global_symtab_idx));
ctx.symtab.hdr.shdr.sh_link.set(ctx.strtab.hdr.shndx);
ctx.symtab.hdr.shdr.sh_size.set(if nsyms == 1 {
0
} else {
nsyms as u64 * std::mem::size_of::<ElfSym<E>>() as u64
// Symbols synthesized by chunks, then symbols from input files.
// Each writer fills its own part of the tables: the local symbols
// come first, the chunks' then the files', and the global symbols
// after them, with the names laid out in the same order.
enum Writer {
Chunk(ChunkId),
Obj(crate::input_files::ObjId),
Dso(crate::input_files::DsoId),
}
struct Part {
writer: Writer,
locals: (u32, u32),
globals: (u32, u32),
strtab: (u64, u64),
}
let mut parts: Vec<Part> = Vec::new();
// Populate linker-synthesized symbols
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.num_local_symtab != 0 {
let locals = (hdr.local_symtab_idx, hdr.num_local_symtab);
parts.push(Part {
writer: Writer::Chunk(id),
locals,
globals: (0, 0),
strtab: (hdr.strtab_offset, hdr.strtab_size),
});
}
}
// Copy symbols from input files
for file in &ctx.objs {
let base = &file.base;
let locals = (base.local_symtab_idx, base.num_local_symtab);
let globals = (base.global_symtab_idx, base.num_global_symtab);
parts.push(Part {
writer: Writer::Obj(file.id()),
locals,
globals,
strtab: (base.strtab_offset, base.strtab_size),
});
}
for file in &ctx.dsos {
let base = &file.base;
let globals = (base.global_symtab_idx, base.num_global_symtab);
parts.push(Part {
writer: Writer::Dso(file.id()),
locals: (0, 0),
globals,
strtab: (base.strtab_offset, base.strtab_size),
});
}
/// Writes `.symtab`, `.strtab` and `.symtab_shndx`.
pub fn copy_buf<E: Arch>(
ctx: &Context<E>,
symtab: &mut [u8],
strtab: &mut [u8],
mut xindex: Option<&mut [u8]>,
) {
let size = std::mem::size_of::<ElfSym<E>>();
symtab[..size].fill(0);
if let Some(xindex) = xindex.as_deref_mut() {
xindex.fill(0);
}
// Carve each part's slices off the buffers, which the parts cover
// in order.
let (mut rest, mut pos) = (symtab, 0);
let (mut xrest, mut xpos) = (xindex, 0);
let mut entries = |(start, count): (u32, u32)| {
let syms = carve(
&mut rest,
&mut pos,
start as usize * size,
count as usize * size,
);
let xindex = xrest
.as_mut()
.map(|x| carve(x, &mut xpos, start as usize * 4, count as usize * 4));
(syms, xindex)
};
let locals: Vec<_> = parts.iter().map(|p| entries(p.locals)).collect();
let globals: Vec<_> = parts.iter().map(|p| entries(p.globals)).collect();
let (mut srest, mut spos) = (strtab, 0);
let strtabs: Vec<_> = parts
.iter()
.map(|p| {
carve(
&mut srest,
&mut spos,
p.strtab.0 as usize,
p.strtab.1 as usize,
)
})
.collect();
// Create section symbols
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.shndx == 0 {
continue;
}
let mut esym = ElfSym::<E>::default();
esym.st_value_mut().set(hdr.shdr.sh_addr.get());
esym.set_type(STT_SECTION);
match xindex.as_deref_mut() {
Some(xindex) => {
E::Endian::write_u32(&mut xindex[hdr.shndx as usize * 4..], hdr.shndx);
esym.st_shndx_mut().set(SHN_XINDEX as u16);
}
None => esym.st_shndx_mut().set(hdr.shndx as u16),
}
esym.write(&mut symtab[hdr.shndx as usize * size..]);
}
strtab::copy_buf(ctx, strtab);
// Symbols synthesized by chunks, then symbols from input files.
// Each writer fills its own part of the tables: the local symbols
// come first, the chunks' then the files', and the global symbols
// after them, with the names laid out in the same order.
enum Writer {
Chunk(ChunkId),
Obj(crate::input_files::ObjId),
Dso(crate::input_files::DsoId),
}
struct Part {
writer: Writer,
locals: (u32, u32),
globals: (u32, u32),
strtab: (u64, u64),
}
let mut parts: Vec<Part> = Vec::new();
// Populate linker-synthesized symbols
for &id in &ctx.chunks {
let hdr = ctx.chunk_header(id);
if hdr.num_local_symtab != 0 {
let locals = (hdr.local_symtab_idx, hdr.num_local_symtab);
parts.push(Part {
writer: Writer::Chunk(id),
locals,
globals: (0, 0),
strtab: (hdr.strtab_offset, hdr.strtab_size),
});
}
}
// Copy symbols from input files
for file in &ctx.objs {
let base = &file.base;
let locals = (base.local_symtab_idx, base.num_local_symtab);
let globals = (base.global_symtab_idx, base.num_global_symtab);
parts.push(Part {
writer: Writer::Obj(file.id()),
locals,
globals,
strtab: (base.strtab_offset, base.strtab_size),
});
}
for file in &ctx.dsos {
let base = &file.base;
let globals = (base.global_symtab_idx, base.num_global_symtab);
parts.push(Part {
writer: Writer::Dso(file.id()),
locals: (0, 0),
globals,
strtab: (base.strtab_offset, base.strtab_size),
});
}
// Carve each part's slices off the buffers, which the parts cover
// in order.
let (mut rest, mut pos) = (symtab, 0);
let (mut xrest, mut xpos) = (xindex, 0);
let mut entries = |(start, count): (u32, u32)| {
let syms = carve(
&mut rest,
&mut pos,
start as usize * size,
count as usize * size,
parts
.par_iter()
.zip(locals)
.zip(globals)
.zip(strtabs)
.for_each(|(((part, (lsyms, lx)), (gsyms, gx)), strtab)| {
let mut block = SymtabBlock::new(
SymtabEntries::new(lsyms, lx),
SymtabEntries::new(gsyms, gx),
strtab,
part.strtab.0,
);
let xindex = xrest
.as_mut()
.map(|x| carve(x, &mut xpos, start as usize * 4, count as usize * 4));
(syms, xindex)
};
let locals: Vec<_> = parts.iter().map(|p| entries(p.locals)).collect();
let globals: Vec<_> = parts.iter().map(|p| entries(p.globals)).collect();
let (mut srest, mut spos) = (strtab, 0);
let strtabs: Vec<_> = parts
.iter()
.map(|p| {
carve(
&mut srest,
&mut spos,
p.strtab.0 as usize,
p.strtab.1 as usize,
)
})
.collect();
match part.writer {
Writer::Chunk(id) => output_chunks::populate_symtab(ctx, id, &mut block),
Writer::Obj(id) => ctx.objs[id.index()].populate_symtab(ctx, id, &mut block),
Writer::Dso(id) => ctx.dsos[id.index()].populate_symtab(ctx, id, &mut block),
}
});
}
parts
.par_iter()
.zip(locals)
.zip(globals)
.zip(strtabs)
.for_each(|(((part, (lsyms, lx)), (gsyms, gx)), strtab)| {
let mut block = SymtabBlock::new(
SymtabEntries::new(lsyms, lx),
SymtabEntries::new(gsyms, gx),
strtab,
part.strtab.0,
);
match part.writer {
Writer::Chunk(id) => output_chunks::populate_symtab(ctx, id, &mut block),
Writer::Obj(id) => ctx.objs[id.index()].populate_symtab(ctx, id, &mut block),
Writer::Dso(id) => ctx.dsos[id.index()].populate_symtab(ctx, id, &mut block),
}
});
}
/// Splits `len` bytes starting at `start` off `rest`, which begins
/// at `pos` in the buffer; the pieces are taken in order.
fn carve<'a>(
rest: &mut &'a mut [u8],
pos: &mut usize,
start: usize,
len: usize,
) -> &'a mut [u8] {
if len == 0 {
return &mut [];
}
let buf = std::mem::take(rest);
let (_, buf) = buf.split_at_mut(start - *pos);
let (piece, tail) = buf.split_at_mut(len);
*rest = tail;
*pos = start + len;
piece
/// Splits `len` bytes starting at `start` off `rest`, which begins
/// at `pos` in the buffer; the pieces are taken in order.
fn carve<'a>(rest: &mut &'a mut [u8], pos: &mut usize, start: usize, len: usize) -> &'a mut [u8] {
if len == 0 {
return &mut [];
}
let buf = std::mem::take(rest);
let (_, buf) = buf.split_at_mut(start - *pos);
let (piece, tail) = buf.split_at_mut(len);
*rest = tail;
*pos = start + len;
piece
}
// RISC-V and LoongArch have code-shrinking linker relaxation. If we
@@ -622,311 +392,3 @@ pub fn to_output_esym<E: Arch>(ctx: &Context<E>, sym: &Symbol, st_name: u32) ->
}
(esym, xindex)
}
// .dynsym contains symbols for dynamic linking. This is similar to
// .symtab, but .dynsym contains data that the runtime uses.
#[derive(Debug)]
pub struct DynsymSection<E: Layout> {
pub hdr: ChunkHeader<E>,
/// Index 0 is the null symbol.
pub symbols: Vec<Option<SymbolId>>,
pub dynstr_offset: u64,
}
impl<E: Arch> DynsymSection<E> {
pub fn new() -> DynsymSection<E> {
let mut hdr = ChunkHeader::<E>::new(".dynsym", SHT_DYNSYM, SHF_ALLOC as u64);
hdr.shdr
.sh_entsize
.set(std::mem::size_of::<ElfSym<E>>() as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
DynsymSection {
hdr,
symbols: Vec::new(),
dynstr_offset: 0,
}
}
}
impl<E: Arch> Default for DynsymSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod dynsym {
use super::*;
pub fn add_symbol<E: Arch>(ctx: &mut Context<E>, sym: SymbolId) {
if ctx.dynsym.symbols.is_empty() {
ctx.dynsym.symbols.push(None);
}
if ctx.symbols[sym].dynsym_idx(&ctx.symbols).is_none() {
// A placeholder; the real index is assigned by sort_dynsyms.
ctx.symbols.aux_mut(sym).dynsym_idx = Some(u32::MAX);
ctx.dynsym.symbols.push(Some(sym));
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.dynsym.hdr.shdr.sh_link.set(ctx.dynstr.hdr.shndx);
ctx.dynsym
.hdr
.shdr
.sh_size
.set(std::mem::size_of::<ElfSym<E>>() as u64 * ctx.dynsym.symbols.len() as u64);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let size = std::mem::size_of::<ElfSym<E>>();
buf[..size].fill(0);
let mut offset = ctx.dynsym.dynstr_offset as u32;
for &id in ctx.dynsym.symbols.iter().skip(1).flatten() {
let sym = &ctx.symbols[id];
let (esym, xindex) = to_output_esym(ctx, sym, offset);
if xindex != 0 {
let nshdrs = ctx.shdr.as_ref().map_or(0, |s| {
s.hdr.shdr.sh_size.get() / ElfShdr::<E>::size() as u64
});
error!("{}: .dynsym: too many output sections: {nshdrs} requested, but ELF allows at most 65279",
ctx.args.output
);
return;
}
esym.write(&mut buf[sym.dynsym_idx(&ctx.symbols).unwrap() as usize * size..]);
offset += sym.name().len() as u32 + 1;
}
}
}
// The hash function for .hash.
pub fn elf_hash(name: &[u8]) -> u32 {
let mut h: u32 = 0;
for &c in name {
h = (h << 4).wrapping_add(c as u32);
let g = h & 0xf000_0000;
if g != 0 {
h ^= g >> 24;
}
h &= !g;
}
h
}
/// The hash function for `.gnu.hash`.
// The hash function for .gnu.hash.
pub fn djb_hash(name: &[u8]) -> u32 {
let mut h: u32 = 5381;
for &c in name {
h = (h << 5).wrapping_add(h).wrapping_add(c as u32);
}
h
}
// .hash contains an on-disk hash table for .dynsym so that the runtime
// can look up a symbol name quickly without scannin all entries in
// .dynsym.
//
// Quickly identifying whether or not a .dynsym contains a given symbol is
// especially important for ELF because of the dynamic symbol lookup rule
// for ELF. In ELF, each dynamic symbol is not searched from a specific
// library but from all the ELF files loaded to memory. Therefore,
// minimizing the cost of each dynamic symbol lookup is important.
#[derive(Debug)]
pub struct HashSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Arch> HashSection<E> {
pub fn new() -> HashSection<E> {
let mut hdr = ChunkHeader::<E>::new(".hash", SHT_HASH, SHF_ALLOC as u64);
// Even though u32 should suffice as an etnry size for all targets,
// s390x uses u64. It looks like a spec bug, but we need to follow
// suit for the sake of binary compatibility.
let entry = hash::entry_size::<E>() as u64;
hdr.shdr.sh_entsize.set(entry);
hdr.shdr.sh_addralign.set(entry);
HashSection { hdr }
}
}
impl<E: Arch> Default for HashSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod hash {
use super::*;
pub fn entry_size<E: Arch>() -> usize {
if E::FAMILY == Family::S390x {
8
} else {
4
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if ctx.dynsym.symbols.is_empty() {
return;
}
let entry = entry_size::<E>() as u64;
let num_slots = ctx.dynsym.symbols.len() as u64;
let hash = ctx.hash.as_mut().unwrap();
hash.hdr.shdr.sh_size.set(entry * 2 + num_slots * entry * 2);
hash.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
let entry = entry_size::<E>();
let write = |buf: &mut [u8], i: usize, v: u32| {
if entry == 8 {
E::Endian::write_u64(&mut buf[i * 8..], v as u64);
} else {
E::Endian::write_u32(&mut buf[i * 4..], v);
}
};
let read = |buf: &[u8], i: usize| -> u32 {
if entry == 8 {
E::Endian::read_u64(&buf[i * 8..]) as u32
} else {
E::Endian::read_u32(&buf[i * 4..])
}
};
let n = ctx.dynsym.symbols.len();
write(buf, 0, n as u32);
write(buf, 1, n as u32);
let buckets = 2;
let chains = 2 + n;
for &id in ctx.dynsym.symbols.iter().skip(1).flatten() {
let sym = &ctx.symbols[id];
let i = sym.dynsym_idx(&ctx.symbols).unwrap() as usize;
let h = elf_hash(sym.name()) as usize % n;
let head = read(buf, buckets + h);
write(buf, chains + i, head);
write(buf, buckets + h, i as u32);
}
}
}
// .gnu.hash is an alternative format for .hash. It contains not only an
// on-disk hash table but also contains a bloom filter to quickly identify
// whether or not a given symbol name exists in .dynsym.
#[derive(Debug)]
pub struct GnuHashSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub num_buckets: u32,
pub num_bloom: u32,
pub num_exported: u32,
}
impl<E: Arch> GnuHashSection<E> {
pub const LOAD_FACTOR: u32 = 8;
pub const HEADER_SIZE: u64 = 16;
pub const BLOOM_SHIFT: u32 = 26;
pub fn new() -> GnuHashSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.hash", SHT_GNU_HASH, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(E::WORD_SIZE as u64);
GnuHashSection {
hdr,
num_buckets: 0,
num_bloom: 1,
num_exported: 0,
}
}
}
impl<E: Arch> Default for GnuHashSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod gnu_hash {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
if ctx.dynsym.symbols.is_empty() {
return;
}
let word = E::WORD_SIZE as u64;
let gh = ctx.gnu_hash.as_mut().unwrap();
// We allocate 12 bits for each symbol in the bloom filter.
gh.num_bloom = ((gh.num_exported as u64 * 12) / (word * 8))
.max(1)
.next_power_of_two() as u32;
gh.hdr.shdr.sh_size.set(
GnuHashSection::<E>::HEADER_SIZE
+ gh.num_bloom as u64 * word // Bloom filter
+ gh.num_buckets as u64 * 4 // Hash buckets
+ gh.num_exported as u64 * 4, // Hash values
);
gh.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf.fill(0);
let gh = ctx.gnu_hash.as_ref().unwrap();
let word = E::WORD_SIZE;
let first_exported = ctx.dynsym.symbols.len() - gh.num_exported as usize;
E::Endian::write_u32(buf, gh.num_buckets);
E::Endian::write_u32(&mut buf[4..], first_exported as u32);
E::Endian::write_u32(&mut buf[8..], gh.num_bloom);
E::Endian::write_u32(&mut buf[12..], GnuHashSection::<E>::BLOOM_SHIFT);
let syms: Vec<SymbolId> = ctx.dynsym.symbols[first_exported..]
.iter()
.flatten()
.copied()
.collect();
if syms.is_empty() {
return;
}
// Write a bloom filter
let bloom_off = GnuHashSection::<E>::HEADER_SIZE as usize;
let word_bits = word * 8;
let mut indices = Vec::with_capacity(syms.len());
for &id in &syms {
let h = ctx.symbols[id].aux(&ctx.symbols).unwrap().djb_hash;
indices.push(h % gh.num_buckets);
let idx = (h as usize / word_bits) % gh.num_bloom as usize;
let bits = (1u64 << (h as usize % word_bits))
| (1u64 << ((h >> GnuHashSection::<E>::BLOOM_SHIFT) as usize % word_bits));
let slot = &mut buf[bloom_off + idx * word..];
if E::IS_64 {
E::Endian::write_u64(slot, E::Endian::read_u64(slot) | bits);
} else {
E::Endian::write_u32(slot, E::Endian::read_u32(slot) | bits as u32);
}
}
// Write hash bucket indices
let buckets_off = bloom_off + gh.num_bloom as usize * word;
for (i, &bucket) in indices.iter().enumerate().rev() {
E::Endian::write_u32(
&mut buf[buckets_off + bucket as usize * 4..],
(first_exported + i) as u32,
);
}
// Write a hash table
let table_off = buckets_off + gh.num_buckets as usize * 4;
for (i, &id) in syms.iter().enumerate() {
// The last entry in a chain must be terminated with an entry with
// least-significant bit 1.
let h = ctx.symbols[id].aux(&ctx.symbols).unwrap().djb_hash;
let last = i + 1 == syms.len() || indices[i] != indices[i + 1];
E::Endian::write_u32(
&mut buf[table_off + i * 4..],
if last { h | 1 } else { h & !1 },
);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
//! `.symtab_shndx`, extended section indices for the symbol table.
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .symtab_shndx is a parallel table for .symtab to contain section
// indices for symbols.
//
// Symbol table entry contains a field for section index, but that's only
// 16 bit in size, so it cannot refer to a section whose section index is
// greater than 65535. We use .symtab_shndx for ELF files containing a lot
// of sections.
//
// Use of this section is exceptional. Most ELF files don't contain one.
#[derive(Debug)]
pub struct SymtabShndxSection<E: Layout> {
pub hdr: ChunkHeader<E>,
}
impl<E: Layout> SymtabShndxSection<E> {
pub fn new() -> SymtabShndxSection<E> {
let mut hdr = ChunkHeader::<E>::new(".symtab_shndx", SHT_SYMTAB_SHNDX, 0);
hdr.shdr.sh_entsize.set(4);
hdr.shdr.sh_addralign.set(4);
SymtabShndxSection { hdr }
}
}
impl<E: Layout> Default for SymtabShndxSection<E> {
fn default() -> Self {
Self::new()
}
}
+154
View File
@@ -0,0 +1,154 @@
//! `.gnu.version_d`, defined symbol versions.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::FileId;
use crate::output_chunks::dynstr::DynstrSection;
use crate::output_chunks::hash::elf_hash;
use crate::output_chunks::ChunkHeader;
use crate::util::path_filename;
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of defined symbols. This section appears only in .so files,
// and it specifies the symbol version for each defined dynamic symbol.
#[derive(Debug)]
pub struct VerdefSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> VerdefSection<E> {
pub fn new() -> VerdefSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version_d", SHT_GNU_VERDEF, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
VerdefSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VerdefSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn construct<E: Arch>(ctx: &mut Context<E>) {
let _t = ctx.timer("fill_verdef");
if ctx.args.version_definitions.is_empty() {
return;
}
// Handle --default-symver
if ctx.args.default_symver {
for &id in ctx.dynsym.symbols.iter().flatten() {
let sym = &mut ctx.symbols[id];
if matches!(sym.file(), Some(FileId::Obj(_))) && !sym.is_undef() {
let ver = sym.ver_idx as u32;
if ver == VER_NDX_GLOBAL || ver == VER_NDX_UNSPECIFIED {
sym.ver_idx = VER_NDX_LAST_RESERVED as u16 + 1;
}
}
}
}
// Resize .gnu.version and write to it
let n = ctx.dynsym.symbols.len();
ctx.versym.contents.resize(n, VER_NDX_GLOBAL as u16);
ctx.versym.contents[0] = VER_NDX_LOCAL as u16;
for &id in ctx.dynsym.symbols.iter().flatten() {
let sym = &ctx.symbols[id];
if !matches!(sym.file(), Some(FileId::Obj(_))) {
continue;
}
let idx = sym.dynsym_idx(&ctx.symbols).unwrap() as usize;
// An unversioned undefined symbol takes version index 0.
if sym.ver_idx as u32 != VER_NDX_UNSPECIFIED {
ctx.versym.contents[idx] = sym.ver_idx;
} else if sym.is_undef() {
ctx.versym.contents[idx] = VER_NDX_LOCAL as u16;
}
}
// Allocate a buffer for .gnu.version_d and write to it
let verdef_size = ElfVerdef::<E>::size();
let verdaux_size = ElfVerdaux::<E>::size();
let mut contents: Vec<u8> =
Vec::with_capacity((verdef_size + verdaux_size) * (ctx.args.version_definitions.len() + 1));
let mut prev: Option<usize> = None;
let mut count = 0u32;
let mut write = |contents: &mut Vec<u8>,
dynstr: &mut DynstrSection<E>,
verstr: &[u8],
idx: u16,
flags: u16| {
count += 1;
if let Some(p) = prev {
let mut vd = ElfVerdef::<E>::parse(&contents[p..]);
vd.vd_next.set((contents.len() - p) as u32);
vd.write(&mut contents[p..]);
}
let pos = contents.len();
prev = Some(pos);
contents.resize(pos + verdef_size + verdaux_size, 0);
ElfVerdef::<E> {
vd_version: U16::new(1),
vd_flags: U16::new(flags),
vd_ndx: U16::new(idx),
vd_cnt: U16::new(1),
vd_hash: U32::new(elf_hash(verstr)),
vd_aux: U32::new(verdef_size as u32),
vd_next: U32::default(),
}
.write(&mut contents[pos..]);
ElfVerdaux::<E> {
vda_name: U32::new(dynstr.add_string(verstr) as u32),
vda_next: U32::default(),
}
.write(&mut contents[pos + verdef_size..]);
};
let soname = if ctx.args.soname.is_empty() {
path_filename(&ctx.args.output)
} else {
ctx.args.soname.clone()
};
write(
&mut contents,
&mut ctx.dynstr,
soname.as_bytes(),
1,
VER_FLG_BASE as u16,
);
let defs = ctx.args.version_definitions.clone();
for (i, verstr) in defs.iter().enumerate() {
write(
&mut contents,
&mut ctx.dynstr,
verstr.as_bytes(),
VER_NDX_LAST_RESERVED as u16 + 1 + i as u16,
0,
);
}
let verdef = ctx.verdef.as_mut().unwrap();
verdef.hdr.shdr.sh_info.set(count);
verdef.contents = contents;
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let shndx = ctx.dynstr.hdr.shndx;
let verdef = ctx.verdef.as_mut().unwrap();
verdef.hdr.shdr.sh_size.set(verdef.contents.len() as u64);
verdef.hdr.shdr.sh_link.set(shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let verdef = ctx.verdef.as_ref().unwrap();
buf[..verdef.contents.len()].copy_from_slice(&verdef.contents);
}
+202
View File
@@ -0,0 +1,202 @@
//! `.gnu.version_r`, required symbol versions.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::{DsoId, FileId};
use crate::output_chunks::dynstr::DynstrSection;
use crate::output_chunks::hash::elf_hash;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
// .gnu.version_r contains information to refer to shared libraries and
// their symbol versions.
#[derive(Debug)]
pub struct VerneedSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> VerneedSection<E> {
pub fn new() -> VerneedSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version_r", SHT_GNU_VERNEED, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
VerneedSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VerneedSection<E> {
fn default() -> Self {
Self::new()
}
}
// If `-z pack-relative-relocs` is specified, we'll create a .relr.dyn
// section and store base relocation records to that section instead of
// to the usual .rela.dyn section.
//
// .relr.dyn is relatively new feature and not supported by glibc until
// 2.38 which was released in 2022. If we don't do anything, executables
// built with `-z pack-relative-relocs` would just crash immediately on
// startup with an older version of glibc.
//
// As a workaround, we'll add a dependency to a dummy version name
// "GLIBC_ABI_DT_RELR" if `-z pack-relative-relocs` is given so that
// executables built with the option failed with a more friendly "version
// `GLIBC_ABI_DT_RELR' not found" error message. glibc 2.38 or later knows
// about this dummy version name and simply ignores it.
fn is_glibc2<E: Layout>(dso: &crate::input_files::SharedFile<E>) -> bool {
dso.soname.starts_with("libc.so.")
&& dso
.version_strings
.iter()
.any(|v| v.starts_with(b"GLIBC_2."))
}
pub fn construct<E: Arch>(ctx: &mut Context<E>) {
let _t = ctx.timer("fill_verneed");
// Create a list of versioned symbols and sort by file and version.
let mut syms: Vec<(DsoId, SymbolId)> = ctx
.dynsym
.symbols
.iter()
.skip(1)
.flatten()
.filter_map(|&id| {
let sym = &ctx.symbols[id];
match sym.file() {
Some(FileId::Dso(dso)) if (sym.ver_idx as u32) > VER_NDX_LAST_RESERVED => {
Some((dso, id))
}
_ => None,
}
})
.collect();
if syms.is_empty() {
return;
}
syms.sort_by_key(|&(dso, id)| {
(
ctx.dsos[dso.index()].soname.clone(),
ctx.symbols[id].ver_idx,
)
});
// Resize .gnu.version
let n = ctx.dynsym.symbols.len();
ctx.versym.contents.resize(n, VER_NDX_GLOBAL as u16);
ctx.versym.contents[0] = VER_NDX_LOCAL as u16;
// Allocate a large enough buffer for .gnu.version_r.
let capacity = (ElfVerneed::<E>::size() + ElfVernaux::<E>::size()) * (syms.len() + 1);
let mut builder = VerneedBuilder::<E> {
contents: Vec::with_capacity(capacity),
veridx: VER_NDX_LAST_RESERVED as u16 + ctx.args.version_definitions.len() as u16,
num_groups: 0,
group_pos: None,
aux_pos: None,
_arch: std::marker::PhantomData,
};
// Fill .gnu.version_r.
// Create version entries.
for i in 0..syms.len() {
let (dso, id) = syms[i];
let start_group = i == 0 || syms[i - 1].0 != dso;
if start_group {
let soname = ctx.dsos[dso.index()].soname.clone();
let vn_file = ctx.dynstr.find_string(soname.as_bytes()) as u32;
builder.start_group(vn_file);
if ctx.args.pack_dyn_relocs_relr && is_glibc2(&ctx.dsos[dso.index()]) {
builder.add_entry(&mut ctx.dynstr, b"GLIBC_ABI_DT_RELR");
}
let version = ctx.symbols[id].version(ctx);
builder.add_entry(&mut ctx.dynstr, version);
} else if ctx.symbols[syms[i - 1].1].ver_idx != ctx.symbols[id].ver_idx {
let version = ctx.symbols[id].version(ctx);
builder.add_entry(&mut ctx.dynstr, version);
}
let dynsym_idx = ctx.symbols[id].dynsym_idx(&ctx.symbols).unwrap() as usize;
ctx.versym.contents[dynsym_idx] = builder.veridx;
}
// Resize .gnu.version_r to fit to its contents.
ctx.verneed.hdr.shdr.sh_info.set(builder.num_groups);
ctx.verneed.contents = builder.contents;
}
/// Builds `.gnu.version_r` incrementally: a verneed entry per shared
/// library, each followed by its vernaux entries.
struct VerneedBuilder<E: Arch> {
contents: Vec<u8>,
veridx: u16,
num_groups: u32,
/// Positions of the current group's verneed and last vernaux.
group_pos: Option<usize>,
aux_pos: Option<usize>,
_arch: std::marker::PhantomData<E>,
}
impl<E: Arch> VerneedBuilder<E> {
fn start_group(&mut self, vn_file: u32) {
self.num_groups += 1;
if let Some(gp) = self.group_pos {
let mut vn = ElfVerneed::<E>::parse(&self.contents[gp..]);
vn.vn_next.set((self.contents.len() - gp) as u32);
vn.write(&mut self.contents[gp..]);
}
let pos = self.contents.len();
self.group_pos = Some(pos);
self.aux_pos = None;
self.contents.resize(pos + ElfVerneed::<E>::size(), 0);
ElfVerneed::<E> {
vn_version: U16::new(1),
vn_cnt: U16::default(),
vn_file: U32::new(vn_file),
vn_aux: U32::new(ElfVerneed::<E>::size() as u32),
vn_next: U32::default(),
}
.write(&mut self.contents[pos..]);
}
fn add_entry(&mut self, dynstr: &mut DynstrSection<E>, verstr: &[u8]) {
let gp = self.group_pos.unwrap();
let mut vn = ElfVerneed::<E>::parse(&self.contents[gp..]);
vn.vn_cnt.set(vn.vn_cnt.get() + 1);
vn.write(&mut self.contents[gp..]);
if let Some(ap) = self.aux_pos {
let mut aux = ElfVernaux::<E>::parse(&self.contents[ap..]);
aux.vna_next.set(ElfVernaux::<E>::size() as u32);
aux.write(&mut self.contents[ap..]);
}
self.veridx += 1;
let aux = ElfVernaux::<E> {
vna_hash: U32::new(elf_hash(verstr)),
vna_flags: U16::default(),
vna_other: U16::new(self.veridx),
vna_name: U32::new(dynstr.add_string(verstr) as u32),
vna_next: U32::default(),
};
let pos = self.contents.len();
self.aux_pos = Some(pos);
self.contents.resize(pos + ElfVernaux::<E>::size(), 0);
aux.write(&mut self.contents[pos..]);
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.verneed
.hdr
.shdr
.sh_size
.set(ctx.verneed.contents.len() as u64);
ctx.verneed.hdr.shdr.sh_link.set(ctx.dynstr.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[..ctx.verneed.contents.len()].copy_from_slice(&ctx.verneed.contents);
}
-411
View File
@@ -1,411 +0,0 @@
//! Symbol versioning sections: `.gnu.version`, `.gnu.version_r` and
//! `.gnu.version_d`.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::{DsoId, FileId};
use crate::output_chunks::symtab::elf_hash;
use crate::output_chunks::ChunkHeader;
use crate::symbol::SymbolId;
use crate::util::path_filename;
// .gnu.version section contains version indices as a parallel array for
// .dynsym. If a dynamic symbol is a defined one, its version information
// is in .gnu.version_d. Otherwise, it's in .gnu.version_r.
//
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of undefined symbols. A symbol having an entry in .gnu.version
// must be resolved to a symbol with the exact same version string at
// runtime.
#[derive(Debug)]
pub struct VersymSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u16>,
}
impl<E: Layout> VersymSection<E> {
pub fn new() -> VersymSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version", SHT_GNU_VERSYM, SHF_ALLOC as u64);
hdr.shdr.sh_entsize.set(2);
hdr.shdr.sh_addralign.set(2);
VersymSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VersymSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod versym {
use super::*;
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.versym
.hdr
.shdr
.sh_size
.set(ctx.versym.contents.len() as u64 * 2);
ctx.versym.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
for (i, &v) in ctx.versym.contents.iter().enumerate() {
E::Endian::write_u16(&mut buf[i * 2..], v);
}
}
}
// .gnu.version_r contains information to refer to shared libraries and
// their symbol versions.
#[derive(Debug)]
pub struct VerneedSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> VerneedSection<E> {
pub fn new() -> VerneedSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version_r", SHT_GNU_VERNEED, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
VerneedSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VerneedSection<E> {
fn default() -> Self {
Self::new()
}
}
// If `-z pack-relative-relocs` is specified, we'll create a .relr.dyn
// section and store base relocation records to that section instead of
// to the usual .rela.dyn section.
//
// .relr.dyn is relatively new feature and not supported by glibc until
// 2.38 which was released in 2022. If we don't do anything, executables
// built with `-z pack-relative-relocs` would just crash immediately on
// startup with an older version of glibc.
//
// As a workaround, we'll add a dependency to a dummy version name
// "GLIBC_ABI_DT_RELR" if `-z pack-relative-relocs` is given so that
// executables built with the option failed with a more friendly "version
// `GLIBC_ABI_DT_RELR' not found" error message. glibc 2.38 or later knows
// about this dummy version name and simply ignores it.
fn is_glibc2<E: Layout>(dso: &crate::input_files::SharedFile<E>) -> bool {
dso.soname.starts_with("libc.so.")
&& dso
.version_strings
.iter()
.any(|v| v.starts_with(b"GLIBC_2."))
}
pub mod verneed {
use super::*;
pub fn construct<E: Arch>(ctx: &mut Context<E>) {
let _t = ctx.timer("fill_verneed");
// Create a list of versioned symbols and sort by file and version.
let mut syms: Vec<(DsoId, SymbolId)> = ctx
.dynsym
.symbols
.iter()
.skip(1)
.flatten()
.filter_map(|&id| {
let sym = &ctx.symbols[id];
match sym.file() {
Some(FileId::Dso(dso)) if (sym.ver_idx as u32) > VER_NDX_LAST_RESERVED => {
Some((dso, id))
}
_ => None,
}
})
.collect();
if syms.is_empty() {
return;
}
syms.sort_by_key(|&(dso, id)| {
(
ctx.dsos[dso.index()].soname.clone(),
ctx.symbols[id].ver_idx,
)
});
// Resize .gnu.version
let n = ctx.dynsym.symbols.len();
ctx.versym.contents.resize(n, VER_NDX_GLOBAL as u16);
ctx.versym.contents[0] = VER_NDX_LOCAL as u16;
// Allocate a large enough buffer for .gnu.version_r.
let capacity = (ElfVerneed::<E>::size() + ElfVernaux::<E>::size()) * (syms.len() + 1);
let mut builder = VerneedBuilder::<E> {
contents: Vec::with_capacity(capacity),
veridx: VER_NDX_LAST_RESERVED as u16 + ctx.args.version_definitions.len() as u16,
num_groups: 0,
group_pos: None,
aux_pos: None,
_arch: std::marker::PhantomData,
};
// Fill .gnu.version_r.
// Create version entries.
for i in 0..syms.len() {
let (dso, id) = syms[i];
let start_group = i == 0 || syms[i - 1].0 != dso;
if start_group {
let soname = ctx.dsos[dso.index()].soname.clone();
let vn_file = ctx.dynstr.find_string(soname.as_bytes()) as u32;
builder.start_group(vn_file);
if ctx.args.pack_dyn_relocs_relr && is_glibc2(&ctx.dsos[dso.index()]) {
builder.add_entry(&mut ctx.dynstr, b"GLIBC_ABI_DT_RELR");
}
let version = ctx.symbols[id].version(ctx);
builder.add_entry(&mut ctx.dynstr, version);
} else if ctx.symbols[syms[i - 1].1].ver_idx != ctx.symbols[id].ver_idx {
let version = ctx.symbols[id].version(ctx);
builder.add_entry(&mut ctx.dynstr, version);
}
let dynsym_idx = ctx.symbols[id].dynsym_idx(&ctx.symbols).unwrap() as usize;
ctx.versym.contents[dynsym_idx] = builder.veridx;
}
// Resize .gnu.version_r to fit to its contents.
ctx.verneed.hdr.shdr.sh_info.set(builder.num_groups);
ctx.verneed.contents = builder.contents;
}
/// Builds `.gnu.version_r` incrementally: a verneed entry per shared
/// library, each followed by its vernaux entries.
struct VerneedBuilder<E: Arch> {
contents: Vec<u8>,
veridx: u16,
num_groups: u32,
/// Positions of the current group's verneed and last vernaux.
group_pos: Option<usize>,
aux_pos: Option<usize>,
_arch: std::marker::PhantomData<E>,
}
impl<E: Arch> VerneedBuilder<E> {
fn start_group(&mut self, vn_file: u32) {
self.num_groups += 1;
if let Some(gp) = self.group_pos {
let mut vn = ElfVerneed::<E>::parse(&self.contents[gp..]);
vn.vn_next.set((self.contents.len() - gp) as u32);
vn.write(&mut self.contents[gp..]);
}
let pos = self.contents.len();
self.group_pos = Some(pos);
self.aux_pos = None;
self.contents.resize(pos + ElfVerneed::<E>::size(), 0);
ElfVerneed::<E> {
vn_version: U16::new(1),
vn_cnt: U16::default(),
vn_file: U32::new(vn_file),
vn_aux: U32::new(ElfVerneed::<E>::size() as u32),
vn_next: U32::default(),
}
.write(&mut self.contents[pos..]);
}
fn add_entry(
&mut self,
dynstr: &mut super::super::symtab::DynstrSection<E>,
verstr: &[u8],
) {
let gp = self.group_pos.unwrap();
let mut vn = ElfVerneed::<E>::parse(&self.contents[gp..]);
vn.vn_cnt.set(vn.vn_cnt.get() + 1);
vn.write(&mut self.contents[gp..]);
if let Some(ap) = self.aux_pos {
let mut aux = ElfVernaux::<E>::parse(&self.contents[ap..]);
aux.vna_next.set(ElfVernaux::<E>::size() as u32);
aux.write(&mut self.contents[ap..]);
}
self.veridx += 1;
let aux = ElfVernaux::<E> {
vna_hash: U32::new(elf_hash(verstr)),
vna_flags: U16::default(),
vna_other: U16::new(self.veridx),
vna_name: U32::new(dynstr.add_string(verstr) as u32),
vna_next: U32::default(),
};
let pos = self.contents.len();
self.aux_pos = Some(pos);
self.contents.resize(pos + ElfVernaux::<E>::size(), 0);
aux.write(&mut self.contents[pos..]);
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.verneed
.hdr
.shdr
.sh_size
.set(ctx.verneed.contents.len() as u64);
ctx.verneed.hdr.shdr.sh_link.set(ctx.dynstr.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
buf[..ctx.verneed.contents.len()].copy_from_slice(&ctx.verneed.contents);
}
}
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of defined symbols. This section appears only in .so files,
// and it specifies the symbol version for each defined dynamic symbol.
#[derive(Debug)]
pub struct VerdefSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u8>,
}
impl<E: Layout> VerdefSection<E> {
pub fn new() -> VerdefSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version_d", SHT_GNU_VERDEF, SHF_ALLOC as u64);
hdr.shdr.sh_addralign.set(4);
VerdefSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VerdefSection<E> {
fn default() -> Self {
Self::new()
}
}
pub mod verdef {
use super::*;
pub fn construct<E: Arch>(ctx: &mut Context<E>) {
let _t = ctx.timer("fill_verdef");
if ctx.args.version_definitions.is_empty() {
return;
}
// Handle --default-symver
if ctx.args.default_symver {
for &id in ctx.dynsym.symbols.iter().flatten() {
let sym = &mut ctx.symbols[id];
if matches!(sym.file(), Some(FileId::Obj(_))) && !sym.is_undef() {
let ver = sym.ver_idx as u32;
if ver == VER_NDX_GLOBAL || ver == VER_NDX_UNSPECIFIED {
sym.ver_idx = VER_NDX_LAST_RESERVED as u16 + 1;
}
}
}
}
// Resize .gnu.version and write to it
let n = ctx.dynsym.symbols.len();
ctx.versym.contents.resize(n, VER_NDX_GLOBAL as u16);
ctx.versym.contents[0] = VER_NDX_LOCAL as u16;
for &id in ctx.dynsym.symbols.iter().flatten() {
let sym = &ctx.symbols[id];
if !matches!(sym.file(), Some(FileId::Obj(_))) {
continue;
}
let idx = sym.dynsym_idx(&ctx.symbols).unwrap() as usize;
// An unversioned undefined symbol takes version index 0.
if sym.ver_idx as u32 != VER_NDX_UNSPECIFIED {
ctx.versym.contents[idx] = sym.ver_idx;
} else if sym.is_undef() {
ctx.versym.contents[idx] = VER_NDX_LOCAL as u16;
}
}
// Allocate a buffer for .gnu.version_d and write to it
let verdef_size = ElfVerdef::<E>::size();
let verdaux_size = ElfVerdaux::<E>::size();
let mut contents: Vec<u8> = Vec::with_capacity(
(verdef_size + verdaux_size) * (ctx.args.version_definitions.len() + 1),
);
let mut prev: Option<usize> = None;
let mut count = 0u32;
let mut write = |contents: &mut Vec<u8>,
dynstr: &mut super::super::symtab::DynstrSection<E>,
verstr: &[u8],
idx: u16,
flags: u16| {
count += 1;
if let Some(p) = prev {
let mut vd = ElfVerdef::<E>::parse(&contents[p..]);
vd.vd_next.set((contents.len() - p) as u32);
vd.write(&mut contents[p..]);
}
let pos = contents.len();
prev = Some(pos);
contents.resize(pos + verdef_size + verdaux_size, 0);
ElfVerdef::<E> {
vd_version: U16::new(1),
vd_flags: U16::new(flags),
vd_ndx: U16::new(idx),
vd_cnt: U16::new(1),
vd_hash: U32::new(elf_hash(verstr)),
vd_aux: U32::new(verdef_size as u32),
vd_next: U32::default(),
}
.write(&mut contents[pos..]);
ElfVerdaux::<E> {
vda_name: U32::new(dynstr.add_string(verstr) as u32),
vda_next: U32::default(),
}
.write(&mut contents[pos + verdef_size..]);
};
let soname = if ctx.args.soname.is_empty() {
path_filename(&ctx.args.output)
} else {
ctx.args.soname.clone()
};
write(
&mut contents,
&mut ctx.dynstr,
soname.as_bytes(),
1,
VER_FLG_BASE as u16,
);
let defs = ctx.args.version_definitions.clone();
for (i, verstr) in defs.iter().enumerate() {
write(
&mut contents,
&mut ctx.dynstr,
verstr.as_bytes(),
VER_NDX_LAST_RESERVED as u16 + 1 + i as u16,
0,
);
}
let verdef = ctx.verdef.as_mut().unwrap();
verdef.hdr.shdr.sh_info.set(count);
verdef.contents = contents;
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
let shndx = ctx.dynstr.hdr.shndx;
let verdef = ctx.verdef.as_mut().unwrap();
verdef.hdr.shdr.sh_size.set(verdef.contents.len() as u64);
verdef.hdr.shdr.sh_link.set(shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
let verdef = ctx.verdef.as_ref().unwrap();
buf[..verdef.contents.len()].copy_from_slice(&verdef.contents);
}
}
+53
View File
@@ -0,0 +1,53 @@
//! `.gnu.version`, symbol version indices.
use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::output_chunks::ChunkHeader;
// .gnu.version section contains version indices as a parallel array for
// .dynsym. If a dynamic symbol is a defined one, its version information
// is in .gnu.version_d. Otherwise, it's in .gnu.version_r.
//
// .gnu.version contains a parallel table for .dynsym to specify symbol
// versions of undefined symbols. A symbol having an entry in .gnu.version
// must be resolved to a symbol with the exact same version string at
// runtime.
#[derive(Debug)]
pub struct VersymSection<E: Layout> {
pub hdr: ChunkHeader<E>,
pub contents: Vec<u16>,
}
impl<E: Layout> VersymSection<E> {
pub fn new() -> VersymSection<E> {
let mut hdr = ChunkHeader::<E>::new(".gnu.version", SHT_GNU_VERSYM, SHF_ALLOC as u64);
hdr.shdr.sh_entsize.set(2);
hdr.shdr.sh_addralign.set(2);
VersymSection {
hdr,
contents: Vec::new(),
}
}
}
impl<E: Layout> Default for VersymSection<E> {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Arch>(ctx: &mut Context<E>) {
ctx.versym
.hdr
.shdr
.sh_size
.set(ctx.versym.contents.len() as u64 * 2);
ctx.versym.hdr.shdr.sh_link.set(ctx.dynsym.hdr.shndx);
}
pub fn copy_buf<E: Arch>(ctx: &Context<E>, buf: &mut [u8]) {
for (i, &v) in ctx.versym.contents.iter().enumerate() {
E::Endian::write_u16(&mut buf[i * 2..], v);
}
}
+38 -34
View File
@@ -24,20 +24,24 @@ use crate::input_files::{
};
use crate::input_sections::{InputSection, InputSectionId, SectionRef};
use crate::linker_script::VersionPattern;
use crate::output_chunks::dynamic::{DynamicSection, RelrDynSection};
use crate::output_chunks::eh_frame::{EhFrameHdrSection, EhFrameRelocSection};
use crate::output_chunks::misc::{
self, BuildIdSection, GnuDebuglinkSection, InterpSection, NotePropertySection,
RelroPaddingSection,
};
use crate::output_chunks::build_id::{self, BuildIdSection};
use crate::output_chunks::dynamic::DynamicSection;
use crate::output_chunks::eh_frame_hdr::EhFrameHdrSection;
use crate::output_chunks::eh_frame_reloc::EhFrameRelocSection;
use crate::output_chunks::gnu_debuglink::{self, GnuDebuglinkSection};
use crate::output_chunks::gnu_hash::{self, GnuHashSection};
use crate::output_chunks::hash::HashSection;
use crate::output_chunks::interp::InterpSection;
use crate::output_chunks::note_property::NotePropertySection;
use crate::output_chunks::output_section::OutputSection;
use crate::output_chunks::symtab::{
self, GnuHashSection, HashSection, ShstrtabSection, SymtabShndxSection,
};
use crate::output_chunks::version::VerdefSection;
use crate::output_chunks::relrdyn::RelrDynSection;
use crate::output_chunks::relro_padding::RelroPaddingSection;
use crate::output_chunks::shstrtab::ShstrtabSection;
use crate::output_chunks::symtab_shndx::SymtabShndxSection;
use crate::output_chunks::verdef::VerdefSection;
use crate::output_chunks::{
self, ChunkHeader, ChunkId, GdbIndexSection, OutputEhdr, OutputPhdr, OutputSectionId,
OutputShdr,
self, compressed, copyrel, dynsym, reloc, ChunkHeader, ChunkId, GdbIndexSection, OutputEhdr,
OutputPhdr, OutputSectionId, OutputShdr,
};
use crate::output_file::OutputFile;
use crate::symbol::{
@@ -198,11 +202,13 @@ pub fn create_synthetic_sections<E: Arch>(ctx: &mut Context<E>) {
chunks.push(ChunkId::NoteProperty);
}
if E::IS_RISCV {
ctx.riscv_attributes = Some(crate::output_chunks::misc::RiscvAttributesSection::new());
ctx.riscv_attributes =
Some(crate::output_chunks::riscv_attributes::RiscvAttributesSection::new());
chunks.push(ChunkId::RiscvAttributes);
}
if E::FAMILY == Family::Ppc64V2 {
ctx.ppc64_save_restore = Some(crate::output_chunks::misc::Ppc64SaveRestoreSection::new());
ctx.ppc64_save_restore =
Some(crate::output_chunks::ppc64_save_restore::Ppc64SaveRestoreSection::new());
chunks.push(ChunkId::Ppc64SaveRestore);
}
if E::FAMILY == Family::Ppc64V1 {
@@ -2789,7 +2795,7 @@ pub fn scan_relocations<E: Arch>(ctx: &mut Context<E>) {
};
if ctx.needs_tlsld.load(Ordering::Relaxed) {
crate::output_chunks::got::got::add_tlsld(ctx);
crate::output_chunks::got::add_tlsld(ctx);
}
// Every dynamic symbol gets its auxiliary record. The loop below
@@ -2811,10 +2817,10 @@ pub fn scan_relocations<E: Arch>(ctx: &mut Context<E>) {
};
if is_imported || is_exported {
symtab::dynsym::add_symbol(ctx, id);
dynsym::add_symbol(ctx, id);
}
if flags & NEEDS_GOT != 0 {
crate::output_chunks::got::got::add_got_symbol(ctx, id);
crate::output_chunks::got::add_got_symbol(ctx, id);
}
if flags & NEEDS_CANONICAL != 0 && ty == STT_FUNC {
let sym = &mut ctx.symbols[id];
@@ -2826,22 +2832,22 @@ pub fn scan_relocations<E: Arch>(ctx: &mut Context<E>) {
// We can't use .plt.got for a canonical PLT because otherwise
// .plt.got and .got would refer to each other, resulting in an
// infinite loop at runtime.
crate::output_chunks::got::plt::add_symbol(ctx, id);
crate::output_chunks::plt::add_symbol(ctx, id);
} else if flags & NEEDS_PLT != 0 {
if flags & NEEDS_GOT != 0 {
crate::output_chunks::got::pltgot::add_symbol(ctx, id);
crate::output_chunks::pltgot::add_symbol(ctx, id);
} else {
crate::output_chunks::got::plt::add_symbol(ctx, id);
crate::output_chunks::plt::add_symbol(ctx, id);
}
}
if flags & NEEDS_GOTTP != 0 {
crate::output_chunks::got::got::add_gottp_symbol(ctx, id);
crate::output_chunks::got::add_gottp_symbol(ctx, id);
}
if flags & NEEDS_TLSGD != 0 {
crate::output_chunks::got::got::add_tlsgd_symbol(ctx, id);
crate::output_chunks::got::add_tlsgd_symbol(ctx, id);
}
if flags & NEEDS_TLSDESC != 0 {
crate::output_chunks::got::got::add_tlsdesc_symbol(ctx, id);
crate::output_chunks::got::add_tlsdesc_symbol(ctx, id);
}
if flags & NEEDS_CANONICAL != 0 && ty != STT_FUNC {
let relro = ctx.args.z_relro
@@ -2849,7 +2855,7 @@ pub fn scan_relocations<E: Arch>(ctx: &mut Context<E>) {
Some(FileId::Dso(dso)) => ctx.dsos[dso.index()].is_readonly(&ctx.symbols[id]),
_ => false,
};
misc::copyrel::add_symbol(ctx, relro, id);
copyrel::add_symbol(ctx, relro, id);
}
if E::FAMILY == Family::Ppc64V1 && flags & NEEDS_PPC_OPD != 0 {
crate::output_chunks::opd::add_symbol(ctx, id);
@@ -2931,11 +2937,9 @@ pub fn create_reloc_sections<E: Arch>(ctx: &mut Context<E>) {
.iter()
.filter_map(|c| c.as_output_section())
.collect();
let secs: Vec<misc::RelocSection<E>> = {
let secs: Vec<reloc::RelocSection<E>> = {
let ctx_ref: &Context<E> = ctx;
ids.par_iter()
.map(|&id| misc::reloc::new(ctx_ref, id))
.collect()
ids.par_iter().map(|&id| reloc::new(ctx_ref, id)).collect()
};
for (id, sec) in ids.into_iter().zip(secs) {
ctx.reloc_sections.push(sec);
@@ -2972,7 +2976,7 @@ pub fn sort_dynsyms<E: Arch>(ctx: &mut Context<E>) {
// subset of it. Every dynamic symbol already has an auxiliary record.
unsafe {
symbols.par_for_each_aux_mut(&exported, |_, sym, aux| {
aux.djb_hash = symtab::djb_hash(sym.name());
aux.djb_hash = gnu_hash::djb_hash(sym.name());
});
}
exported.par_sort_unstable_by(|&a, &b| {
@@ -4077,7 +4081,7 @@ pub fn set_osec_offsets<E: Arch>(ctx: &mut Context<E>) -> u64 {
if ctx.args.pack_dyn_relocs_android {
let before = ctx.reldyn.hdr.shdr.sh_size.get();
crate::output_chunks::dynamic::reldyn::update_shdr(ctx);
crate::output_chunks::reldyn::update_shdr(ctx);
if before != ctx.reldyn.hdr.shdr.sh_size.get() {
continue;
}
@@ -4382,11 +4386,11 @@ pub fn compress_debug_sections<E: Arch>(ctx: &mut Context<E>) {
})
.map(|(i, &id)| (i, id))
.collect();
let compressed: Vec<misc::CompressedSection<E>> = {
let compressed: Vec<compressed::CompressedSection<E>> = {
let ctx_ref: &Context<E> = ctx;
targets
.par_iter()
.map(|&(_, id)| misc::compressed::new(ctx_ref, id))
.map(|&(_, id)| compressed::new(ctx_ref, id))
.collect()
};
for ((i, _), sec) in targets.into_iter().zip(compressed) {
@@ -4443,7 +4447,7 @@ pub fn write_build_id<E: Arch>(ctx: &mut Context<E>, buf: &mut [u8], is_mmapped:
let hdr = ctx.buildid.as_ref().unwrap().hdr.shdr;
let start = hdr.sh_offset.get() as usize;
let end = (hdr.sh_offset.get() + hdr.sh_size.get()) as usize;
misc::build_id::copy_buf(ctx, &mut buf[start..end]);
build_id::copy_buf(ctx, &mut buf[start..end]);
}
// A .gnu_debuglink section contains a filename and a CRC32 checksum of a
@@ -4478,7 +4482,7 @@ pub fn write_gnu_debuglink<E: Arch>(ctx: &mut Context<E>, buf: &mut [u8]) {
let hdr = ctx.gnu_debuglink.as_ref().unwrap().hdr.shdr;
let start = hdr.sh_offset.get() as usize;
let end = (hdr.sh_offset.get() + hdr.sh_size.get()) as usize;
misc::gnu_debuglink::copy_buf(ctx, &mut buf[start..end]);
gnu_debuglink::copy_buf(ctx, &mut buf[start..end]);
}
/// The CRC32 of a large buffer, computed in parallel.
+6 -4
View File
@@ -36,10 +36,12 @@ use crate::arch::Arch;
use crate::context::Context;
use crate::elf::*;
use crate::input_files::FileId;
use crate::output_chunks::eh_frame::EhFrameRelocSection;
use crate::output_chunks::misc::{ComdatGroupSection, NotePropertySection, RiscvAttributesSection};
use crate::output_chunks::sframe::SFrameRelocSection;
use crate::output_chunks::symtab::ShstrtabSection;
use crate::output_chunks::comdat_group::ComdatGroupSection;
use crate::output_chunks::eh_frame_reloc::EhFrameRelocSection;
use crate::output_chunks::note_property::NotePropertySection;
use crate::output_chunks::riscv_attributes::RiscvAttributesSection;
use crate::output_chunks::sframe_reloc::SFrameRelocSection;
use crate::output_chunks::shstrtab::ShstrtabSection;
use crate::output_chunks::{self, ChunkId, OutputEhdr, OutputShdr};
use crate::output_file::OutputFile;
use crate::passes;
+3 -3
View File
@@ -1079,9 +1079,9 @@ impl Symbol {
pub fn gotplt_addr<E: Arch>(&self, ctx: &Context<E>) -> u64 {
ctx.gotplt.hdr.shdr.sh_addr.get()
+ crate::output_chunks::got::gotplt::header_size::<E>()
+ crate::output_chunks::gotplt::header_size::<E>()
+ self.plt_idx(&ctx.symbols).unwrap() as u64
* crate::output_chunks::got::gotplt::entry_size::<E>()
* crate::output_chunks::gotplt::entry_size::<E>()
}
pub fn gottp_addr<E: Arch>(&self, ctx: &Context<E>) -> u64 {
@@ -1103,7 +1103,7 @@ impl Symbol {
pub fn plt_addr<E: Arch>(&self, ctx: &Context<E>) -> u64 {
if let Some(idx) = self.plt_idx(&ctx.symbols) {
return ctx.plt.hdr.shdr.sh_addr.get()
+ crate::output_chunks::got::plt::entry_offset::<E>(idx);
+ crate::output_chunks::plt::entry_offset::<E>(idx);
}
ctx.pltgot.hdr.shdr.sh_addr.get()
+ self.pltgot_idx(&ctx.symbols).unwrap() as u64 * E::PLTGOT_SIZE