[lld] [ELF] --discard-{locals, all}: mark local symbols referenced by retained non-SHF_ALLOC sections (PR #209042)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Sun Jul 12 11:23:44 PDT 2026
https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/209042
With --gc-sections, mark() does not scan relocations of retained
non-SHF_ALLOC sections, so local symbols referenced only by such
sections (e.g. .L symbols in .debug_str_offsets referenced by
.debug_info) do not get the USED flag.
-r/--emit-relocs with --discard-{locals,all} would discard the symbols
and rewrite relocations to reference the null symbol, corrupting DWARF
in the output. This is common on RISC-V, where
RISCVELFObjectWriter::needsRelocateWithSymbol returns true, but is also
possible on other targets with --reloc-section-sym=none.
Call markUsedLocalSymbols from the retention loop (introduced by #209035)
to set the flag.
Fix #160789
>From a3bffde9127e39c75c3423ad82c9b0213767b16d Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 12 Jul 2026 10:01:25 -0700
Subject: [PATCH] [ELF] --discard-{locals,all}: mark local symbols referenced
by retained non-SHF_ALLOC sections
With --gc-sections, mark() does not scan relocations of retained
non-SHF_ALLOC sections, so local symbols referenced only by such
sections (e.g. .L symbols in .debug_str_offsets referenced by
.debug_info) do not get the USED flag.
-r/--emit-relocs with --discard-{locals,all} would discard the symbols
and rewrite relocations to reference the null symbol, corrupting DWARF
in the output. This is common on RISC-V, where
RISCVELFObjectWriter::needsRelocateWithSymbol returns true, but is also
possible on other targets with --reloc-section-sym=none.
Call markUsedLocalSymbols from the retention loop (introduced by #209035)
to set the flag.
Fix #160789
---
lld/ELF/MarkLive.cpp | 5 +++++
lld/test/ELF/emit-relocs-discard-locals.s | 27 ++++++++++++++++++++---
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/lld/ELF/MarkLive.cpp b/lld/ELF/MarkLive.cpp
index 0f0a49f0d1333..d1e05b5eb9296 100644
--- a/lld/ELF/MarkLive.cpp
+++ b/lld/ELF/MarkLive.cpp
@@ -385,6 +385,7 @@ void MarkLive<ELFT, TrackWhyLive>::run() {
// referenced by .eh_frame sections, so we scan them for that here.
for (EhInputSection *eh : ctx.ehInputSections)
scanEhFrameSection(*eh);
+ bool markUsed = ctx.arg.copyRelocs && ctx.arg.discard != DiscardPolicy::None;
for (InputSectionBase *sec : ctx.inputSections) {
if (sec->flags & SHF_GNU_RETAIN) {
enqueue(sec, /*offset=*/0, /*sym=*/nullptr, {std::nullopt, "retained"});
@@ -420,6 +421,10 @@ void MarkLive<ELFT, TrackWhyLive>::run() {
sec->markLive();
for (InputSection *isec : sec->dependentSections)
isec->markLive();
+ // If -r or --emit-relocs, ensure referenced local symbols are
+ // preserved by --discard-{locals,all} (see shouldKeepInSymtab).
+ if (markUsed)
+ markUsedLocalSymbols<ELFT>(*sec);
}
}
diff --git a/lld/test/ELF/emit-relocs-discard-locals.s b/lld/test/ELF/emit-relocs-discard-locals.s
index c9841a6fb5186..0fa9678f51cae 100644
--- a/lld/test/ELF/emit-relocs-discard-locals.s
+++ b/lld/test/ELF/emit-relocs-discard-locals.s
@@ -4,8 +4,10 @@
## There are two separate code paths that mark used local symbols when
## --gc-sections is specified and when is not. The test checks both.
+## --reloc-section-sym=none keeps relocations referencing local symbols
+## instead of section symbols.
-# RUN: llvm-mc -filetype=obj -triple=x86_64 -save-temp-labels %s -o %t.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 -save-temp-labels --reloc-section-sym=none %s -o %t.o
# RUN: ld.lld --emit-relocs --discard-locals %t.o -o %tlocal
# RUN: llvm-readelf -s %tlocal | FileCheck --check-prefixes=DISCARD-LOCALS,DISCARD-LOCALS-NOGC %s
@@ -14,7 +16,7 @@
## --gc-sections can discard symbols relative to GCed sections (including STT_SECTION).
# RUN: ld.lld --emit-relocs --discard-locals --gc-sections %t.o -o %tlocal.gc
# RUN: llvm-readelf -s %tlocal.gc | FileCheck --check-prefix=DISCARD-LOCALS %s
-# RUN: llvm-readobj -r %tlocal | FileCheck --check-prefix=REL %s
+# RUN: llvm-readobj -r %tlocal.gc | FileCheck --check-prefix=REL %s
# RUN: ld.lld --emit-relocs --discard-all %t.o -o %tall
# RUN: llvm-readelf -s %tall | FileCheck --check-prefixes=DISCARD-ALL,DISCARD-ALL-NOGC %s
@@ -24,15 +26,20 @@
# RUN: llvm-readelf -s %tall.gc | FileCheck --check-prefix=DISCARD-ALL %s
# RUN: llvm-readobj -r %tall.gc | FileCheck --check-prefix=REL %s
-## --discard-locals removes unused local symbols which start with ".L"
+## --discard-locals removes unused local symbols which start with ".L".
+## .Llive is referenced by a relocation from a retained non-SHF_ALLOC section
+## and must be kept (#160789).
# DISCARD-LOCALS: 0: {{0+}} 0 NOTYPE LOCAL DEFAULT UND
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Lused
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} used
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} unused
# DISCARD-LOCALS-NOGC-NEXT: NOTYPE LOCAL DEFAULT {{.*}} unused_gc
+# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Llive
# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} .text
# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} text
# DISCARD-LOCALS-NOGC-NEXT: SECTION LOCAL DEFAULT {{.*}} gc
+# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} .debug_str_offsets
+# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} .debug_info
# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} .comment
# DISCARD-LOCALS-NEXT: NOTYPE GLOBAL DEFAULT {{.*}} _start
@@ -40,9 +47,12 @@
# DISCARD-ALL: 0: {{0+}} 0 NOTYPE LOCAL DEFAULT UND
# DISCARD-ALL-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Lused
# DISCARD-ALL-NEXT: NOTYPE LOCAL DEFAULT {{.*}} used
+# DISCARD-ALL-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Llive
# DISCARD-ALL-NEXT: SECTION LOCAL DEFAULT {{.*}} .text
# DISCARD-ALL-NEXT: SECTION LOCAL DEFAULT {{.*}} text
# DISCARD-ALL-NOGC-NEXT: SECTION LOCAL DEFAULT {{.*}} gc
+# DISCARD-ALL-NEXT: SECTION LOCAL DEFAULT {{.*}} .debug_str_offsets
+# DISCARD-ALL-NEXT: SECTION LOCAL DEFAULT {{.*}} .debug_info
# DISCARD-ALL-NEXT: SECTION LOCAL DEFAULT {{.*}} .comment
# DISCARD-ALL-NEXT: NOTYPE GLOBAL DEFAULT {{.*}} _start
@@ -51,6 +61,9 @@
# REL-NEXT: R_X86_64_PLT32 .Lused 0xFFFFFFFFFFFFFFFC
# REL-NEXT: R_X86_64_PLT32 used 0xFFFFFFFFFFFFFFFC
# REL-NEXT: }
+# REL: .rela.debug_info {
+# REL-NEXT: R_X86_64_32 .Llive 0x3
+# REL-NEXT: }
.globl _start
_start:
@@ -68,3 +81,11 @@ used:
.Lunused_gc:
unused_gc:
ret
+
+## .Llive, relative to a retained non-SHF_ALLOC section, is live.
+.section .debug_str_offsets,"", at progbits
+.Llive:
+.byte 0
+
+.section .debug_info,"", at progbits
+.long .Llive+3
More information about the llvm-commits
mailing list