[lld] [ELF] Synthesize STT_FILE if necessary (PR #209087)
Fangrui Song via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 13 22:45:56 PDT 2026
https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/209087
>From 67ae5ab8a650f692d23bb294ac5f30c66d924bb1 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 12 Jul 2026 18:44:13 -0700
Subject: [PATCH 1/2] [ELF] Synthesize STT_FILE if necessary
lld groups local symbols in .symtab by input file, appending a symbol
converted to STB_LOCAL (hidden visibility, version script `local:`, or
--exclude-libs) to its file's group. The nearest preceding STT_FILE then
claims the symbol, but the attribution can be wrong: the file may
contain multiple STT_FILE symbols (-r output), or lack an STT_FILE for
the symbol's unit.
```
FILE LOCAL DEFAULT ABS a.c
NOTYPE LOCAL DEFAULT 7 a_local
FILE LOCAL DEFAULT ABS b.c
NOTYPE LOCAL DEFAULT 7 b_local
NOTYPE LOCAL DEFAULT 7 a_localized
```
Follow GNU ld:
- If the output contains an STT_FILE symbol, place all converted symbols
(including linker-synthesized ones such as `_DYNAMIC`) at the end of
the local part, after a synthetic STT_FILE with an empty name.
- For -r output, synthesize an STT_FILE named after the input file for
an input that outputs local symbols but no STT_FILE, so that its locals
are not attributed to a preceding file. We use the basename instead of
the full name for local determinism (#47367).
GNU ld synthesizes this per-input STT_FILE for executable and
shared-object output as well. Restrict it to -r for now to minimize test
updates.
Close #47367 and #191478
---
lld/ELF/SyntheticSections.cpp | 63 ++++---
lld/ELF/SyntheticSections.h | 12 ++
lld/ELF/Writer.cpp | 23 ++-
lld/test/ELF/local-symbols-order.s | 186 +++++++++++++++++----
lld/test/ELF/lto/parallel-internalize.ll | 9 +
lld/test/ELF/ppc64-local-entry.s | 13 +-
lld/test/ELF/relocatable-comdat-multiple.s | 4 +-
lld/test/ELF/relocatable-comdat2.s | 4 +-
lld/test/ELF/relocatable-discard-locals.s | 6 +-
lld/test/ELF/relocatable-gc.s | 27 +--
lld/test/ELF/relocatable.s | 6 +-
lld/test/ELF/strtab-nodedup.s | 12 +-
12 files changed, 274 insertions(+), 91 deletions(-)
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 06d5129844e5c..34f77e751e216 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -2003,34 +2003,57 @@ void SymbolTableBaseSection::finalizeContents() {
s.sym->dynsymIndex = ++i;
}
-// The ELF spec requires that all local symbols precede global symbols, so we
-// sort symbol entries in this function. (For .dynsym, we don't do that because
-// symbols for dynamic linking are inherently all globals.)
+// The ELF spec requires local symbols to precede globals. We additionally group
+// the locals by file, each led by its first STT_FILE.
//
-// Aside from above, we put local symbols in groups starting with the STT_FILE
-// symbol. That is convenient for purpose of identifying where are local symbols
-// coming from.
+// symbols[firstGlobalIdx, sttFileIdx) can be converted local: move them after
+// the per-file groups, behind the synthetic STT_FILE sttFileSym. Locals added
+// later (e.g. thunks) fall outside that range and stay in their file's group.
void SymbolTableBaseSection::sortSymTabSymbols() {
- // Move all local symbols before global symbols.
- auto e = std::stable_partition(
- symbols.begin(), symbols.end(),
- [](const SymbolTableEntry &s) { return s.sym->isLocal(); });
- size_t numLocals = e - symbols.begin();
- getParent()->info = numLocals + 1;
-
- // We want to group the local symbols by file. For that we rebuild the local
- // part of the symbols vector. We do not need to care about the STT_FILE
- // symbols, they are already naturally placed first in each group. That
- // happens because STT_FILE is always the first symbol in the object and hence
- // precede all other local symbols we add for a file.
MapVector<InputFile *, SmallVector<SymbolTableEntry, 0>> arr;
- for (const SymbolTableEntry &s : llvm::make_range(symbols.begin(), e))
- arr[s.sym->file].push_back(s);
+ SmallVector<SymbolTableEntry, 0> localized, globals;
+ SymbolTableEntry fileEntry{};
+ for (size_t i = 0, e = symbols.size(); i != e; ++i) {
+ const SymbolTableEntry &s = symbols[i];
+ if (!s.sym->isLocal())
+ globals.push_back(s);
+ else if (sttFileSym && i >= firstGlobalIdx && i < sttFileIdx)
+ localized.push_back(s);
+ else if (s.sym != sttFileSym)
+ arr[s.sym->file].push_back(s);
+ else
+ fileEntry = s;
+ }
auto i = symbols.begin();
for (auto &p : arr)
for (SymbolTableEntry &entry : p.second)
*i++ = entry;
+ if (sttFileSym) {
+ *i++ = fileEntry;
+ i = std::copy(localized.begin(), localized.end(), i);
+ }
+ getParent()->info = i - symbols.begin() + 1;
+ std::copy(globals.begin(), globals.end(), i);
+}
+
+// A symbol converted to STB_LOCAL cannot be reliably attributed to a file:
+// within a file's group the wrong STT_FILE would claim it, as a file may hold
+// several STT_FILE symbols (relocatable output) or none. Like GNU ld, when the
+// output has an STT_FILE, add a synthetic empty-name STT_FILE.
+void SymbolTableBaseSection::maybeAddSttFile() {
+ ArrayRef<SymbolTableEntry> syms = symbols;
+ if (llvm::none_of(syms.take_front(firstGlobalIdx),
+ [](const SymbolTableEntry &s) { return s.sym->isFile(); }))
+ return;
+ if (llvm::any_of(
+ syms.drop_front(firstGlobalIdx),
+ [](const SymbolTableEntry &s) { return s.sym->isLocal(); })) {
+ sttFileIdx = symbols.size();
+ sttFileSym = makeDefined(ctx, ctx.internalFile, "", STB_LOCAL, STV_DEFAULT,
+ STT_FILE, /*value=*/0, /*size=*/0, nullptr);
+ addSymbol(sttFileSym);
+ }
}
void SymbolTableBaseSection::addSymbol(Symbol *b) {
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 523f6587899fe..1a29a96013723 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -659,6 +659,8 @@ class SymbolTableBaseSection : public SyntheticSection {
void finalizeContents() override;
size_t getSize() const override { return getNumSymbols() * entsize; }
void addSymbol(Symbol *sym);
+ void maybeAddSttFile();
+ void markGlobalPart() { firstGlobalIdx = symbols.size(); }
unsigned getNumSymbols() const { return symbols.size() + 1; }
size_t getSymbolIndex(const Symbol &sym);
ArrayRef<SymbolTableEntry> getSymbols() const { return symbols; }
@@ -669,6 +671,16 @@ class SymbolTableBaseSection : public SyntheticSection {
// A vector of symbols and their string table offsets.
SmallVector<SymbolTableEntry, 0> symbols;
+ // Synthetic STT_FILE with an empty name, added by maybeAddSttFile and placed
+ // by sortSymTabSymbols before all symbols demoted to STB_LOCAL.
+ Defined *sttFileSym = nullptr;
+
+ // symbols[firstGlobalIdx, sttFileIdx) were originally non-local and may be
+ // converted to local. sttFileIdx is where sttFileSym lands. Locals added
+ // later (thunks via addSyntheticLocal) fall outside and stay grouped with
+ // their file.
+ size_t firstGlobalIdx = 0, sttFileIdx = 0;
+
StringTableSection &strTabSec;
llvm::once_flag onceFlag;
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 2da732c857be9..5766c1c2bf6d7 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -30,6 +30,7 @@
#include "llvm/ADT/StringMap.h"
#include "llvm/Support/BLAKE3.h"
#include "llvm/Support/Parallel.h"
+#include "llvm/Support/Path.h"
#include "llvm/Support/RandomNumberGenerator.h"
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/xxhash.h"
@@ -456,9 +457,24 @@ static void demoteAndCopyLocalSymbols(Ctx &ctx) {
symsVec[i].push_back(b);
}
});
- for (auto &syms : ArrayRef(symsVec.get(), ctx.objectFiles.size()))
+ for (size_t i = 0, e = ctx.objectFiles.size(); i != e; ++i) {
+ // For -r, synthesize an STT_FILE named after the input file for an input
+ // that contributes local symbols but no STT_FILE, so that its symbols are
+ // not attributed to another file's STT_FILE (matching GNU ld).
+ // --discard-all discards STT_FILE symbols.
+ auto &syms = symsVec[i];
+ if (ctx.arg.relocatable && ctx.arg.discard != DiscardPolicy::All &&
+ !syms.empty() &&
+ llvm::none_of(syms, [](Symbol *s) { return s->isFile(); })) {
+ InputFile *file = ctx.objectFiles[i];
+ ctx.in.symTab->addSymbol(
+ makeDefined(ctx, file, sys::path::filename(file->getName()),
+ STB_LOCAL, /*stOther=*/0, STT_FILE, /*value=*/0,
+ /*size=*/0, nullptr));
+ }
for (Symbol *sym : syms)
ctx.in.symTab->addSymbol(sym);
+ }
}
// Create a section symbol for each output section so that we can represent
@@ -1934,6 +1950,8 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
{
llvm::TimeTraceScope timeScope("Add symbols to symtabs");
+ if (ctx.in.symTab)
+ ctx.in.symTab->markGlobalPart();
// Now that we have defined all possible global symbols including linker-
// synthesized ones. Visit all symbols to give the finishing touches.
for (Symbol *sym : ctx.symtab->getSymbols()) {
@@ -1953,7 +1971,8 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
addVerneed(ctx, *sym);
}
}
-
+ if (ctx.in.symTab && !ctx.arg.relocatable)
+ ctx.in.symTab->maybeAddSttFile();
}
if (ctx.in.mipsGot)
diff --git a/lld/test/ELF/local-symbols-order.s b/lld/test/ELF/local-symbols-order.s
index 46530a4faa8b6..0e3abbce19daa 100644
--- a/lld/test/ELF/local-symbols-order.s
+++ b/lld/test/ELF/local-symbols-order.s
@@ -1,37 +1,149 @@
-# REQUIRES: x86
-
-# RUN: echo '.data; .file "file2"; foo2:; .global bar2; .hidden bar2; bar2:' > %t2.s
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t2.s -o %t2.o
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t1.o
-
-# RUN: ld.lld -o %t %t1.o %t2.o --emit-relocs
-# RUN: llvm-readelf --symbols --sections %t | FileCheck %s
-
-## Check we sort local symbols to match the following order:
-## file1, local1, section1, hidden1, file2, local2, section2, hidden2 ...
-
-# CHECK: Section Headers:
-# CHECK: [Nr] Name
-# CHECK: [ [[ST:.*]]] .text
-# CHECK: [ [[SD:.*]]] .data
-# CHECK: [ [[SC:.*]]] .comment
-
-# CHECK: Size Type Bind Vis Ndx Name
-# CHECK-NEXT: 0 NOTYPE LOCAL DEFAULT UND
-# CHECK-NEXT: 0 FILE LOCAL DEFAULT ABS file1
-# CHECK-NEXT: 0 NOTYPE LOCAL DEFAULT 1 foo1
-# CHECK-NEXT: 0 SECTION LOCAL DEFAULT [[ST]]
-# CHECK-NEXT: 0 NOTYPE LOCAL HIDDEN 1 bar1
-# CHECK-NEXT: 0 FILE LOCAL DEFAULT ABS file2
-# CHECK-NEXT: 0 NOTYPE LOCAL DEFAULT 2 foo2
-# CHECK-NEXT: 0 SECTION LOCAL DEFAULT [[SD]]
-# CHECK-NEXT: 0 NOTYPE LOCAL HIDDEN 2 bar2
-# CHECK-NEXT: 0 SECTION LOCAL DEFAULT [[SC]]
-
-foo1:
-
-.global bar1
-.hidden bar1
-bar1:
-
-.file "file1"
+# REQUIRES: aarch64
+## Check the order of local symbols: grouped by file, each group led by the file's STT symbol.
+## Symbols converted to STB_LOCAL are placed at the end of the local part after a synthetic
+## STT_FILE with an empty name, omitted if the output has no STT_FILE.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=aarch64 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=aarch64 b.s -o b.o
+# RUN: mkdir c && llvm-mc -filetype=obj -triple=aarch64 c.s -o c/a.o
+
+## Only b.c has an STT_FILE. a.o's a_local has no leading STT_FILE; the hidden
+## a_hidden follows the synthetic STT_FILE. --emit-relocs adds STT_SECTION
+## symbols to their file's group.
+# RUN: ld.lld --emit-relocs a.o b.o -o ab
+# RUN: llvm-readelf -s ab | FileCheck %s
+
+# CHECK: NOTYPE LOCAL DEFAULT [[#]] a_local
+# CHECK-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# CHECK-NEXT: SECTION LOCAL DEFAULT [[#]] .text
+# CHECK-NEXT: FILE LOCAL DEFAULT ABS b.c
+# CHECK-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_local
+# CHECK-NEXT: NOTYPE LOCAL DEFAULT [[#]] $d
+# CHECK-NEXT: SECTION LOCAL DEFAULT [[#]] .data
+# CHECK-NEXT: SECTION LOCAL DEFAULT [[#]] .comment
+# CHECK-NEXT: FILE LOCAL DEFAULT ABS{{ $}}
+# CHECK-NEXT: NOTYPE LOCAL HIDDEN [[#]] a_hidden
+# CHECK-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_localized
+# CHECK-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_exported
+# CHECK-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_localized
+# CHECK-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_exported
+
+## For -r, synthesize STT_FILE for a.o, which lacks one.
+# RUN: ld.lld -r a.o b.o -o ab.o
+# RUN: llvm-readelf -s ab.o | FileCheck %s --check-prefix=RO
+
+# RO: FILE LOCAL DEFAULT ABS a.o
+# RO-NEXT: NOTYPE LOCAL DEFAULT [[#]] a_local
+# RO-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# RO-NEXT: SECTION LOCAL DEFAULT [[#]] .text
+# RO-NEXT: FILE LOCAL DEFAULT ABS b.c
+# RO-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_local
+# RO-NEXT: NOTYPE LOCAL DEFAULT [[#]] $d
+# RO-NEXT: SECTION LOCAL DEFAULT [[#]] .data
+# RO-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_localized
+# RO-NEXT: NOTYPE GLOBAL HIDDEN [[#]] a_hidden
+# RO-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_exported
+# RO-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_localized
+# RO-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_exported
+
+## Synthesize STT_FILE named after its basename, not its path, for local determinism (#47367).
+# RUN: ld.lld -r ab.o c/a.o -o abc.o
+# RUN: llvm-readelf -s abc.o | FileCheck %s --check-prefix=MULTI
+
+# MULTI: FILE LOCAL DEFAULT ABS a.o
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] a_local
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# MULTI-NEXT: FILE LOCAL DEFAULT ABS b.c
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_local
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] $d
+# MULTI-NEXT: SECTION LOCAL DEFAULT [[#]] .text
+# MULTI-NEXT: SECTION LOCAL DEFAULT [[#]] .data
+# MULTI-NEXT: FILE LOCAL DEFAULT ABS a.o
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# MULTI-NEXT: NOTYPE LOCAL DEFAULT [[#]] far
+
+## Symbols localized by a version script follow a synthetic STT_FILE with empty name.
+# RUN: ld.lld -shared --version-script=ver ab.o -o ab.so
+# RUN: llvm-readelf -s ab.so | FileCheck %s --check-prefix=VER
+
+# VER: FILE LOCAL DEFAULT ABS a.o
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] a_local
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# VER-NEXT: FILE LOCAL DEFAULT ABS b.c
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_local
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] $d
+# VER-NEXT: FILE LOCAL DEFAULT ABS{{ $}}
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] a_localized
+# VER-NEXT: NOTYPE LOCAL HIDDEN [[#]] a_hidden
+# VER-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_localized
+# VER-NEXT: NOTYPE LOCAL HIDDEN [[#]] _DYNAMIC
+# VER-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_exported
+# VER-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_exported
+
+## A range-extension thunk is added to .symtab by finalizeAddressDependentContent,
+## after the global part. It must stay in the local part before the synthetic
+## STT_FILE that groups the demoted c_hidden, not be moved behind it.
+# RUN: ld.lld c/a.o b.o -T lds -o cb
+# RUN: llvm-readelf -s cb | FileCheck %s --check-prefix=THUNK
+
+# THUNK: NOTYPE LOCAL DEFAULT [[#]] far
+# THUNK: FUNC LOCAL DEFAULT [[#]] __AArch64AbsLongThunk_{{.*}}
+# THUNK-NEXT: NOTYPE LOCAL DEFAULT [[#]] $x
+# THUNK-NEXT: NOTYPE LOCAL DEFAULT [[#]] $d
+# THUNK-NEXT: FILE LOCAL DEFAULT ABS{{ $}}
+# THUNK-NEXT: NOTYPE LOCAL HIDDEN [[#]] c_hidden
+# THUNK-NEXT: NOTYPE GLOBAL DEFAULT [[#]] _start
+
+## --discard-all discards STT_FILE symbols as well. With no STT_FILE in the
+## output, no synthetic STT_FILE is added.
+# RUN: ld.lld -shared --discard-all --version-script=ver ab.o -o ab2.so
+# RUN: llvm-readelf -s ab2.so | FileCheck %s --check-prefix=DISCARD --implicit-check-not=FILE
+
+# DISCARD: NOTYPE LOCAL DEFAULT [[#]] a_localized
+# DISCARD-NEXT: NOTYPE LOCAL HIDDEN [[#]] a_hidden
+# DISCARD-NEXT: NOTYPE LOCAL DEFAULT [[#]] b_localized
+# DISCARD-NEXT: NOTYPE LOCAL HIDDEN [[#]] _DYNAMIC
+# DISCARD-NEXT: NOTYPE GLOBAL DEFAULT [[#]] a_exported
+# DISCARD-NEXT: NOTYPE GLOBAL DEFAULT [[#]] b_exported
+
+#--- a.s
+a_local:
+.globl a_localized
+a_localized:
+.globl a_hidden
+.hidden a_hidden
+a_hidden:
+.globl a_exported
+a_exported:
+ nop
+
+#--- b.s
+.file "b.c"
+.data
+b_local:
+.globl b_localized
+b_localized:
+.globl b_exported
+b_exported:
+ .byte 0
+
+#--- c.s
+.globl _start, c_hidden
+.hidden c_hidden
+_start:
+ bl far
+c_hidden:
+ ret
+.section .far,"ax"
+far:
+ ret
+
+#--- lds
+SECTIONS {
+ .text 0x10000 : { *(.text) }
+ .far 0x10000000 : { *(.far) }
+}
+
+#--- ver
+v1 { global: *_exported; local: *; };
diff --git a/lld/test/ELF/lto/parallel-internalize.ll b/lld/test/ELF/lto/parallel-internalize.ll
index a1511d6f6867d..d85096263f9f9 100644
--- a/lld/test/ELF/lto/parallel-internalize.ll
+++ b/lld/test/ELF/lto/parallel-internalize.ll
@@ -35,6 +35,15 @@
; CHECK-NEXT: Section: Absolute
; CHECK-NEXT: }
; CHECK-NEXT: Symbol {
+; CHECK-NEXT: Name: (0)
+; CHECK-NEXT: Value: 0x0
+; CHECK-NEXT: Size: 0
+; CHECK-NEXT: Binding: Local
+; CHECK-NEXT: Type: File
+; CHECK-NEXT: Other: 0
+; CHECK-NEXT: Section: Absolute
+; CHECK-NEXT: }
+; CHECK-NEXT: Symbol {
; CHECK-NEXT: Name: bar
; CHECK-NEXT: Value:
; CHECK-NEXT: Size: 8
diff --git a/lld/test/ELF/ppc64-local-entry.s b/lld/test/ELF/ppc64-local-entry.s
index 00940b7b27f76..ce2c361d8dff6 100644
--- a/lld/test/ELF/ppc64-local-entry.s
+++ b/lld/test/ELF/ppc64-local-entry.s
@@ -2,7 +2,7 @@
# RUN: llvm-mc -filetype=obj -triple=powerpc64 %s -o %t
# RUN: ld.lld -r %t -o %t2
-# RUN: llvm-objdump -s --section=.symtab %t2 | FileCheck %s
+# RUN: llvm-readelf -s %t2 | FileCheck %s
.text
.abiversion 2
@@ -37,11 +37,6 @@ _start:
.type g, at object # @g
.lcomm g,4,4
-// We expect the st_other byte to be 0x60:
-// localentry = 011 (gep + 2 instructions), reserved = 000,
-// visibility = 00 (STV_DEFAULT)
-// Currently, llvm-objdump does not support displaying
-// st_other's PPC64 specific flags, thus we check the
-// result of the hexdump of .symtab section.
-
-// CHECK: 0060 00000003 12600001 00000000 00000000
+## We expect st_other to be 0x60: localentry = 011 (gep + 2 instructions),
+## reserved = 000, visibility = 00 (STV_DEFAULT).
+# CHECK: FUNC GLOBAL DEFAULT [<other: 0x60>] [[#]] _start
diff --git a/lld/test/ELF/relocatable-comdat-multiple.s b/lld/test/ELF/relocatable-comdat-multiple.s
index 7a4eddd837a9b..6935e0a0fd79a 100644
--- a/lld/test/ELF/relocatable-comdat-multiple.s
+++ b/lld/test/ELF/relocatable-comdat-multiple.s
@@ -9,7 +9,7 @@
# CHECK-NEXT: Name: .group
# CHECK-NEXT: Index: 2
# CHECK-NEXT: Link: 9
-# CHECK-NEXT: Info: 1
+# CHECK-NEXT: Info: 2
# CHECK-NEXT: Type: COMDAT
# CHECK-NEXT: Signature: aaa
# CHECK-NEXT: Section(s) in group [
@@ -21,7 +21,7 @@
# CHECK-NEXT: Name: .group
# CHECK-NEXT: Index: 5
# CHECK-NEXT: Link: 9
-# CHECK-NEXT: Info: 6
+# CHECK-NEXT: Info: 8
# CHECK-NEXT: Type: COMDAT
# CHECK-NEXT: Signature: bbb
# CHECK-NEXT: Section(s) in group [
diff --git a/lld/test/ELF/relocatable-comdat2.s b/lld/test/ELF/relocatable-comdat2.s
index 8e3161d49f8fc..cb2074bf9abb1 100644
--- a/lld/test/ELF/relocatable-comdat2.s
+++ b/lld/test/ELF/relocatable-comdat2.s
@@ -14,7 +14,7 @@
# CHECK-NEXT: Name: .group
# CHECK-NEXT: Index: 2
# CHECK-NEXT: Link: 8
-# CHECK-NEXT: Info: 1
+# CHECK-NEXT: Info: 2
# CHECK-NEXT: Type: COMDAT
# CHECK-NEXT: Signature: bar
# CHECK-NEXT: Section(s) in group [
@@ -25,7 +25,7 @@
# CHECK-NEXT: Name: .group
# CHECK-NEXT: Index: 4
# CHECK-NEXT: Link: 8
-# CHECK-NEXT: Info: 2
+# CHECK-NEXT: Info: 3
# CHECK-NEXT: Type: COMDAT
# CHECK-NEXT: Signature: zed
# CHECK-NEXT: Section(s) in group [
diff --git a/lld/test/ELF/relocatable-discard-locals.s b/lld/test/ELF/relocatable-discard-locals.s
index 8941e8de7abc8..be8752467f4a8 100644
--- a/lld/test/ELF/relocatable-discard-locals.s
+++ b/lld/test/ELF/relocatable-discard-locals.s
@@ -9,11 +9,12 @@
# RUN: llvm-readobj -r %tlocal.ro | FileCheck --check-prefix=REL %s
# RUN: ld.lld -r --discard-all %t.o -o %tall.ro
-# RUN: llvm-readelf -s %tall.ro | FileCheck --check-prefix=DISCARD-ALL %s
+# RUN: llvm-readelf -s %tall.ro | FileCheck --check-prefix=DISCARD-ALL --implicit-check-not=FILE %s
# RUN: llvm-readobj -r %tall.ro | FileCheck --check-prefix=REL %s
## --discard-locals removes unused local symbols which start with ".L"
# DISCARD-LOCALS: 0: {{0+}} 0 NOTYPE LOCAL DEFAULT UND
+# DISCARD-LOCALS-NEXT: FILE LOCAL DEFAULT ABS {{.*}}.o
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Lused
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} used
# DISCARD-LOCALS-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .L.str
@@ -25,7 +26,8 @@
# DISCARD-LOCALS-NEXT: SECTION LOCAL DEFAULT {{.*}} .rodata.str1.1
# DISCARD-LOCALS-NEXT: NOTYPE GLOBAL DEFAULT {{.*}} _start
-## --discard-all removes all unused regular local symbols.
+## --discard-all removes all unused regular local symbols and STT_FILE
+## symbols; no STT_FILE is synthesized.
# DISCARD-ALL: 0: {{0+}} 0 NOTYPE LOCAL DEFAULT UND
# DISCARD-ALL-NEXT: NOTYPE LOCAL DEFAULT {{.*}} .Lused
# DISCARD-ALL-NEXT: NOTYPE LOCAL DEFAULT {{.*}} used
diff --git a/lld/test/ELF/relocatable-gc.s b/lld/test/ELF/relocatable-gc.s
index 7d367ba57ec46..3304ed807ad72 100644
--- a/lld/test/ELF/relocatable-gc.s
+++ b/lld/test/ELF/relocatable-gc.s
@@ -12,11 +12,12 @@
# CHECK: [ 1] .group
# CHECK-NEXT: [ 2] .note.GNU-stack
-# CHECK: Symbol table '.symtab' contains 3 entries:
+# CHECK: Symbol table '.symtab' contains 4 entries:
# CHECK-NEXT: Num:
# CHECK-NEXT: 0:
-# CHECK-NEXT: 1: {{.*}} NOTYPE LOCAL DEFAULT 1 group
-# CHECK-NEXT: 2: {{.*}} SECTION LOCAL DEFAULT 1
+# CHECK-NEXT: 1: {{.*}} FILE LOCAL DEFAULT ABS {{.*}}.o
+# CHECK-NEXT: 2: {{.*}} NOTYPE LOCAL DEFAULT 1 group
+# CHECK-NEXT: 3: {{.*}} SECTION LOCAL DEFAULT 1
## -u keeps .text.bar alive. Other group members are kept alive as well.
# RUN: ld.lld -r --gc-sections -u bar %t.o -o - | llvm-readelf -Ss - | \
@@ -34,10 +35,10 @@
# KEEP_GROUP-NEXT: [ 3] .text.foo
# KEEP_GROUP-NEXT: [ 4] .note.GNU-stack
-# KEEP_GROUP: Symbol table '.symtab' contains 7 entries:
-# KEEP_GROUP: 4: {{.*}} SECTION
-# KEEP_GROUP-NEXT: 5: {{.*}} 2 bar
-# KEEP_GROUP-NEXT: 6: {{.*}} 3 foo
+# KEEP_GROUP: Symbol table '.symtab' contains 8 entries:
+# KEEP_GROUP: 5: {{.*}} SECTION
+# KEEP_GROUP-NEXT: 6: {{.*}} 2 bar
+# KEEP_GROUP-NEXT: 7: {{.*}} 3 foo
## If .text is retained, its referenced qux and .fred are retained as well.
## fred_und is used (by .fred) and thus emitted.
@@ -53,12 +54,12 @@
# KEEP_START-NEXT: [ 6] .rela.fred
# KEEP_START-NEXT: [ 7] .note.GNU-stack
-# KEEP_START: Symbol table '.symtab' contains 10 entries:
-# KEEP_START: 5: {{.*}} SECTION
-# KEEP_START-NEXT: 6: {{.*}} 1 _start
-# KEEP_START-NEXT: 7: {{.*}} 5 fred
-# KEEP_START-NEXT: 8: {{.*}} UND __start_qux
-# KEEP_START-NEXT: 9: {{.*}} UND fred_und
+# KEEP_START: Symbol table '.symtab' contains 11 entries:
+# KEEP_START: 6: {{.*}} SECTION
+# KEEP_START-NEXT: 7: {{.*}} 1 _start
+# KEEP_START-NEXT: 8: {{.*}} 5 fred
+# KEEP_START-NEXT: 9: {{.*}} UND __start_qux
+# KEEP_START-NEXT: 10: {{.*}} UND fred_und
.section qux,"a", at progbits
.byte 0
diff --git a/lld/test/ELF/relocatable.s b/lld/test/ELF/relocatable.s
index 5af01c82cd08f..3be3ca9ac2e12 100644
--- a/lld/test/ELF/relocatable.s
+++ b/lld/test/ELF/relocatable.s
@@ -59,9 +59,9 @@
# SECTION: 2 .rela.text 00000090 0000000000000000
# SECTION: 3 .bss 00000018 0000000000000000 BSS
# SECTION: 4 .note.GNU-stack 00000000 0000000000000000
-# SECTION: 5 .symtab 00000168 0000000000000000
+# SECTION: 5 .symtab 00000180 0000000000000000
# SECTION: 6 .shstrtab 00000041 0000000000000000
-# SECTION: 7 .strtab 0000002d 0000000000000000
+# SECTION: 7 .strtab {{[0-9a-f]+}} 0000000000000000
# CHECKTEXT: Disassembly of section .text:
# CHECKTEXT-EMPTY:
@@ -94,7 +94,7 @@
# CHECKEXE-NEXT: Version: 1
# CHECKEXE-NEXT: Entry: 0x201160
# CHECKEXE-NEXT: ProgramHeaderOffset: 0x40
-# CHECKEXE-NEXT: SectionHeaderOffset: 0x358
+# CHECKEXE-NEXT: SectionHeaderOffset:
# CHECKEXE-NEXT: Flags [
# CHECKEXE-NEXT: ]
# CHECKEXE-NEXT: HeaderSize: 64
diff --git a/lld/test/ELF/strtab-nodedup.s b/lld/test/ELF/strtab-nodedup.s
index b20e738de0679..4deae29ce2da1 100644
--- a/lld/test/ELF/strtab-nodedup.s
+++ b/lld/test/ELF/strtab-nodedup.s
@@ -8,18 +8,28 @@
# RUN: ld.lld %t/a.o %t/b.o -o %t/a
# RUN: llvm-readelf -p .strtab %t/a | FileCheck %s --check-prefix=NODEDUP
# RUN: ld.lld -r -O2 %t/a.o %t/b.o -o %t/a.ro
-# RUN: llvm-readelf -p .strtab %t/a.ro | FileCheck %s --check-prefix=NODEDUP
+# RUN: llvm-readelf -p .strtab %t/a.ro | FileCheck %s --check-prefix=RO
# NODEDUP: [ 1] local
# NODEDUP-NEXT: [ 7] local
# NODEDUP-NEXT: [ d] foo
# NODEDUP-EMPTY:
+## For -r, a.o and b.o get a synthesized STT_FILE.
+# RO: [ 1] a.o
+# RO-NEXT: [ 5] local
+# RO-NEXT: [ b] b.o
+# RO-NEXT: [ f] local
+# RO-NEXT: [ 15] foo
+# RO-EMPTY:
+
# RUN: llvm-readelf -s %t/a.ro | FileCheck %s --check-prefix=SYMTAB
# SYMTAB: 0: {{0+}} 0 NOTYPE LOCAL DEFAULT UND
+# SYMTAB-NEXT: FILE LOCAL DEFAULT ABS a.o
# SYMTAB-NEXT: NOTYPE LOCAL DEFAULT [[#]] local
# SYMTAB-NEXT: SECTION LOCAL DEFAULT [[#]] .text
+# SYMTAB-NEXT: FILE LOCAL DEFAULT ABS b.o
# SYMTAB-NEXT: NOTYPE LOCAL DEFAULT [[#]] local
# SYMTAB-NEXT: NOTYPE GLOBAL DEFAULT [[#]] foo
>From 1ffc53571d3e42f0fd3ad57eb421d79c8b7d1dd3 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 13 Jul 2026 22:45:44 -0700
Subject: [PATCH 2/2] rename to synthSttFileIdx
---
lld/ELF/SyntheticSections.cpp | 22 ++++++++++++----------
lld/ELF/SyntheticSections.h | 12 ++++++------
2 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 34f77e751e216..599184bef3fe0 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -2006,9 +2006,10 @@ void SymbolTableBaseSection::finalizeContents() {
// The ELF spec requires local symbols to precede globals. We additionally group
// the locals by file, each led by its first STT_FILE.
//
-// symbols[firstGlobalIdx, sttFileIdx) can be converted local: move them after
-// the per-file groups, behind the synthetic STT_FILE sttFileSym. Locals added
-// later (e.g. thunks) fall outside that range and stay in their file's group.
+// symbols[firstGlobalIdx, synthSttFileIdx) can be converted local: move them
+// after the per-file groups, behind the synthetic STT_FILE synthSttFileSym.
+// Locals added later (e.g. thunks) fall outside that range and stay in their
+// file's group.
void SymbolTableBaseSection::sortSymTabSymbols() {
MapVector<InputFile *, SmallVector<SymbolTableEntry, 0>> arr;
SmallVector<SymbolTableEntry, 0> localized, globals;
@@ -2017,9 +2018,9 @@ void SymbolTableBaseSection::sortSymTabSymbols() {
const SymbolTableEntry &s = symbols[i];
if (!s.sym->isLocal())
globals.push_back(s);
- else if (sttFileSym && i >= firstGlobalIdx && i < sttFileIdx)
+ else if (synthSttFileSym && i >= firstGlobalIdx && i < synthSttFileIdx)
localized.push_back(s);
- else if (s.sym != sttFileSym)
+ else if (s.sym != synthSttFileSym)
arr[s.sym->file].push_back(s);
else
fileEntry = s;
@@ -2029,7 +2030,7 @@ void SymbolTableBaseSection::sortSymTabSymbols() {
for (auto &p : arr)
for (SymbolTableEntry &entry : p.second)
*i++ = entry;
- if (sttFileSym) {
+ if (synthSttFileSym) {
*i++ = fileEntry;
i = std::copy(localized.begin(), localized.end(), i);
}
@@ -2049,10 +2050,11 @@ void SymbolTableBaseSection::maybeAddSttFile() {
if (llvm::any_of(
syms.drop_front(firstGlobalIdx),
[](const SymbolTableEntry &s) { return s.sym->isLocal(); })) {
- sttFileIdx = symbols.size();
- sttFileSym = makeDefined(ctx, ctx.internalFile, "", STB_LOCAL, STV_DEFAULT,
- STT_FILE, /*value=*/0, /*size=*/0, nullptr);
- addSymbol(sttFileSym);
+ synthSttFileIdx = symbols.size();
+ synthSttFileSym =
+ makeDefined(ctx, ctx.internalFile, "", STB_LOCAL, STV_DEFAULT, STT_FILE,
+ /*value=*/0, /*size=*/0, nullptr);
+ addSymbol(synthSttFileSym);
}
}
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 1a29a96013723..54d68d279728c 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -673,13 +673,13 @@ class SymbolTableBaseSection : public SyntheticSection {
// Synthetic STT_FILE with an empty name, added by maybeAddSttFile and placed
// by sortSymTabSymbols before all symbols demoted to STB_LOCAL.
- Defined *sttFileSym = nullptr;
+ Defined *synthSttFileSym = nullptr;
- // symbols[firstGlobalIdx, sttFileIdx) were originally non-local and may be
- // converted to local. sttFileIdx is where sttFileSym lands. Locals added
- // later (thunks via addSyntheticLocal) fall outside and stay grouped with
- // their file.
- size_t firstGlobalIdx = 0, sttFileIdx = 0;
+ // symbols[firstGlobalIdx, synthSttFileIdx) were originally non-local and may
+ // be converted to local. synthSttFileIdx is where synthSttFileSym lands.
+ // Locals added later (thunks via addSyntheticLocal) fall outside and stay
+ // grouped with their file.
+ size_t firstGlobalIdx = 0, synthSttFileIdx = 0;
StringTableSection &strTabSec;
More information about the llvm-commits
mailing list