Set sh_link of SHF_LINK_ORDER output sections

An SHF_LINK_ORDER section's sh_link refers to the section it describes,
but mold always wrote zero. Once passed through -r, such sections lost
their links, so relinking with --gc-sections dropped .ARM.exidx and
__patchable_function_entries, and C++ programs on ARM aborted when
throwing. Set sh_link from the members' linked-to sections. Since it
can name only one section, -r also separates SHF_LINK_ORDER sections
whose linked-to sections are in different output sections, as GNU ld
and lld do.
This commit is contained in:
Rui Ueyama committed 2026-10-01 09:53:52 +09:00
1 parent 49129fbd59
commit 9aacb62c03
5 files changed
+119 -1

No files matched your search

+1
View File
@@ -639,6 +639,7 @@ pub fn update_phdr<E: Target>(ctx: &mut Context<E>) {
/// indices are known.
pub fn update_shdr<E: Target>(ctx: &mut Context<E>, id: ChunkId) {
match id {
ChunkId::Output(id) => output_section::update_shdr(ctx, id),
ChunkId::Phdr => update_phdr(ctx),
ChunkId::Interp => interp::update_shdr(ctx),
ChunkId::GotPlt => gotplt::update_shdr(ctx),
+26
View File
@@ -76,6 +76,32 @@ impl<E: Target> OutputSection<E> {
}
}
/// The output section of the section that an SHF_LINK_ORDER input section
/// is linked to.
pub fn link_order_target<E: Target>(
ctx: &Context<E>,
id: InputSectionId,
) -> Option<OutputSectionId> {
let isec = ctx.input_section(id);
if isec.sh_flags & SHF_LINK_ORDER as u64 == 0 {
return None;
}
let file = &ctx.objs[isec.file.index()];
file.section(file.shdr(isec.shndx as usize).sh_link.get() as usize)?.output_section
}
/// An SHF_LINK_ORDER output section's sh_link refers to the output section
/// of the sections its members are linked to.
pub fn update_shdr<E: Target>(ctx: &mut Context<E>, id: OutputSectionId) {
let osec = &ctx.output_sections[id.index()];
if osec.hdr.shdr.sh_flags.get() & SHF_LINK_ORDER as u64 != 0
&& let Some(link) = osec.members.iter().find_map(|&m| link_order_target(ctx, m))
{
let shndx = ctx.output_sections[link.index()].hdr.shndx;
ctx.output_sections[id.index()].hdr.shdr.sh_link.set(shndx);
}
}
// Assign offsets to OutputSection members
pub fn compute_section_size<E: Target>(ctx: &mut Context<E>, id: OutputSectionId) {
let size = layout(ctx, id);
+51 -1
View File
@@ -30,18 +30,67 @@
//!
//! - Relocations are copied, but we need to fix symbol indices.
use std::collections::HashMap;
use crate::chunks::comdat_group::ComdatGroupSection;
use crate::chunks::note_property::NotePropertySection;
use crate::chunks::output_section::{self, OutputSection};
use crate::chunks::riscv_attributes::RiscvAttributesSection;
use crate::chunks::{self, ChunkId};
use crate::chunks::{self, ChunkId, OutputSectionId};
use crate::context::Context;
use crate::elf::*;
use crate::input_files::FileId;
use crate::input_sections::InputSectionId;
use crate::output_file::OutputFile;
use crate::passes;
use crate::target::Target;
use crate::util::align_to;
/// An output section's sh_link can refer to only one section, so
/// SHF_LINK_ORDER sections of the same name share an output section only
/// if the sections they are linked to do too.
fn split_link_order_sections<E: Target>(ctx: &mut Context<E>) {
for i in 0..ctx.chunks.len() {
let ChunkId::Output(id) = ctx.chunks[i] else {
continue;
};
let osec = &ctx.output_sections[id.index()];
if osec.hdr.shdr.sh_flags.get() & SHF_LINK_ORDER as u64 == 0 {
continue;
}
let mut groups: Vec<Vec<InputSectionId>> = Vec::new();
let mut group_of = HashMap::new();
for &m in &osec.members {
let link = output_section::link_order_target(ctx, m);
let j = *group_of.entry(link).or_insert_with(|| {
groups.push(Vec::new());
groups.len() - 1
});
groups[j].push(m);
}
let (name, shdr) = (osec.hdr.name, osec.hdr.shdr);
let mut groups = groups.into_iter();
ctx.output_sections[id.index()].members = groups.next().unwrap();
for members in groups {
let id = OutputSectionId::new(ctx.output_sections.len() as u32);
for &m in &members {
let isec = ctx.input_section(m);
let (file, shndx) = (isec.file, isec.shndx as usize);
ctx.objs[file.index()].section_mut(shndx).unwrap().output_section = Some(id);
}
let mut osec = OutputSection::<E>::new(name, shdr.sh_type.get());
osec.hdr.shdr.sh_flags = shdr.sh_flags;
osec.hdr.shdr.sh_addralign = shdr.sh_addralign;
osec.members = members;
ctx.output_sections.push(osec);
ctx.chunks.push(ChunkId::Output(id));
}
}
}
// Create linker-synthesized sections
fn create_synthetic_sections<E: Target>(ctx: &mut Context<E>) {
ctx.ehdr = Some(chunks::new_ehdr::<E>(0));
@@ -162,6 +211,7 @@ fn set_osec_offsets<E: Target>(ctx: &mut Context<E>) -> u64 {
pub fn combine_objects<E: Target>(ctx: &mut Context<E>) {
passes::create_output_sections(ctx);
split_link_order_sections(ctx);
create_synthetic_sections(ctx);
claim_unresolved_symbols(ctx);
passes::compute_section_sizes(ctx);
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
[ $MACHINE = m68k ] && skip
[ $MACHINE = sh4 ] && skip
[ $MACHINE = sh4aeb ] && skip
cat <<EOF | $CXX -c -o $t/a.o -xc++ - -ffunction-sections
int foo() {
try {
throw 0;
} catch (int x) {
return x;
}
return 1;
}
EOF
cat <<EOF | $CXX -c -o $t/b.o -xc++ - -ffunction-sections
#include <iostream>
int foo();
int main() { std::cout << foo() << "\n"; }
EOF
./mold -r -o $t/c.o $t/a.o $t/b.o
$CXX -B. -o $t/exe $t/c.o -Wl,--gc-sections
$QEMU $t/exe | grep '^0$'
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
. $(dirname $0)/common.inc
cat <<EOF | $CC -c -o $t/a.o -xc - -ffunction-sections -fpatchable-function-entry=1
int foo() { return 3; }
int bar() { return 5; }
int main() { return foo() - 3; }
EOF
./mold -r -o $t/b.o $t/a.o
$CC -B. -o $t/exe $t/b.o -Wl,--gc-sections
$QEMU $t/exe
readelf -W --sections $t/exe | grep -F __patchable_function_entries
nm $t/exe | not grep -w bar