Keep SHF_LINK_ORDER sections alive with their linked sections

An SHF_LINK_ORDER section such as __patchable_function_entries holds
metadata for the section that its sh_link refers to, and usually
nothing else refers to it. --gc-sections therefore discarded all of
them except .ARM.exidx, for which there was an ARM-specific edge from
a section to its unwind index. Replace that edge with a generic one
from a section to the SHF_LINK_ORDER sections linked to it, as GNU ld
and lld do.
This commit is contained in:
Rui Ueyama committed 2026-10-01 09:53:52 +09:00
1 parent b2c3d1dc67
commit 49129fbd59
5 files changed
+38 -69

No files matched your search

+4 -4
View File
@@ -209,10 +209,10 @@ fn visit_section<'scope, E: Target>(
}
}
if E::FAMILY == Family::Arm32
&& let Some(exidx) = isec.exidx()
{
mark(file.section_at(exidx));
// SHF_LINK_ORDER sections, such as .ARM.exidx, are metadata for the
// sections they are linked to and are alive as long as those are.
for shndx in file.link_order_dependents(isec.shndx) {
mark(file.section_at(shndx));
}
}
+19 -18
View File
@@ -731,6 +731,10 @@ pub struct ObjectFile<E: Target> {
pub sframe_sections: Vec<u32>,
pub sframe_fdes: Vec<SFrameFde>,
/// SHF_LINK_ORDER sections as pairs of sh_link and section index,
/// sorted by sh_link.
link_order_sections: Vec<(u32, u32)>,
pub exclude_libs: bool,
pub gnu_properties: BTreeMap<u32, u32>,
pub needs_executable_stack: bool,
@@ -1035,6 +1039,7 @@ impl<E: Target> ObjectFile<E> {
eh_frame_sections: Vec::new(),
sframe_sections: Vec::new(),
sframe_fdes: Vec::new(),
link_order_sections: Vec::new(),
exclude_libs: false,
gnu_properties: BTreeMap::new(),
needs_executable_stack: false,
@@ -1147,6 +1152,16 @@ impl<E: Target> ObjectFile<E> {
self.section(shndx as usize).expect("no such input section")
}
/// The indices of the SHF_LINK_ORDER sections linked to the section
/// at `shndx`.
pub fn link_order_dependents(&self, shndx: u32) -> impl Iterator<Item = u32> + '_ {
let begin = self.link_order_sections.partition_point(|&(link, _)| link < shndx);
self.link_order_sections[begin..]
.iter()
.take_while(move |&&(link, _)| link == shndx)
.map(|&(_, i)| i)
}
/// Iterates over relocations without materializing a deferred CREL table.
#[inline(always)]
pub(crate) fn relocation_iter(
@@ -1700,6 +1715,9 @@ impl<E: Target> ObjectFile<E> {
if E::FAMILY == Family::Ppc32 && name == b".got2" {
self.got2 = Some(i as u32);
}
if flags & SHF_LINK_ORDER as u64 != 0 {
self.link_order_sections.push((shdr.sh_link.get(), i as u32));
}
if args.gdb_index {
// Save debug sections for --gdb-index.
@@ -1759,24 +1777,7 @@ impl<E: Target> ObjectFile<E> {
isec.set_relsec(i as u32, has_relocs);
}
// Attach .arm.exidx sections to their corresponding sections
if E::FAMILY == Family::Arm32 {
let pairs: Vec<(usize, usize)> = self
.input_sections()
.filter(|isec| isec.sh_type(self) == SHT_ARM_EXIDX)
.map(|isec| {
(
self.base.shdrs[isec.shndx as usize].sh_link.get() as usize,
isec.shndx as usize,
)
})
.collect();
for (target, exidx) in pairs {
if let Some(isec) = self.section_mut(target) {
isec.set_exidx(exidx as u32);
}
}
}
self.link_order_sections.sort_unstable();
}
// Relocations are usually sorted by r_offset in relocation tables,
+1 -46
View File
@@ -139,21 +139,6 @@ const NO_FDE: u32 = u32::MAX;
/// Target-specific state embedded in an input section.
pub trait InputSectionExtra: fmt::Debug + Default + Send + Sync + 'static {
/// Creates empty metadata before target-specific parsing.
#[inline]
fn empty() -> Self {
Self::default()
}
#[inline]
fn exidx(&self) -> Option<u32> {
None
}
fn set_exidx(&mut self, _exidx: u32) {
unreachable!("only ARM32 input sections have exidx metadata")
}
#[inline]
fn r_deltas(&self) -> &[RelocDelta] {
&[]
@@ -166,27 +151,6 @@ pub trait InputSectionExtra: fmt::Debug + Default + Send + Sync + 'static {
impl InputSectionExtra for () {}
/// ARM32's link from a code section to its `.ARM.exidx` section.
/// `u32::MAX` means that there is no associated section.
impl InputSectionExtra for u32 {
#[inline]
fn empty() -> Self {
Self::MAX
}
#[inline]
fn exidx(&self) -> Option<u32> {
(*self != Self::MAX).then_some(*self)
}
#[inline]
fn set_exidx(&mut self, exidx: u32) {
debug_assert_ne!(exidx, 0);
debug_assert_ne!(exidx, Self::MAX);
*self = exidx;
}
}
/// Relaxation bookkeeping embedded only for RISC-V and LoongArch.
impl InputSectionExtra for Box<[RelocDelta]> {
#[inline]
@@ -316,7 +280,7 @@ impl<E: Target> InputSection<E> {
relsec_idx: NO_RELSEC,
fde_begin: NO_FDE,
flags: AtomicU8::new(IS_ALIVE),
extra: E::InputSectionExtra::empty(),
extra: E::InputSectionExtra::default(),
};
// Sections may have been compressed. We usually uncompress them
@@ -717,15 +681,6 @@ impl<E: Target> InputSection<E> {
self.set_offset(leader.encode());
}
#[inline]
pub fn exidx(&self) -> Option<u32> {
self.extra.exidx()
}
pub fn set_exidx(&mut self, exidx: u32) {
self.extra.set_exidx(exidx);
}
#[inline]
pub fn r_deltas(&self) -> &[RelocDelta] {
self.extra.r_deltas()
+1 -1
View File
@@ -274,7 +274,7 @@ impl<const LE: bool> Target for Arm32Target<LE> {
type Chdr = Elf32Chdr<Self>;
type Rel = ElfRelNoAddend<Self>;
type InputSectionExtra = u32;
type InputSectionExtra = ();
const NAME: &'static str = if Self::IS_LITTLE { "arm32" } else { "arm32be" };
const FAMILY: Family = Family::Arm32;
+13
View File
@@ -0,0 +1,13 @@
#!/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
$CC -B. -o $t/exe $t/a.o -Wl,--gc-sections
$QEMU $t/exe
readelf -W --sections $t/exe | grep -F __patchable_function_entries
nm $t/exe | not grep -w bar