[lld] [llvm] [dyndbg][LLD][ELF] Initial LLD support for dynamic debugging (PR #214188)
Andrew Ng via llvm-commits
llvm-commits at lists.llvm.org
Fri Sep 11 05:06:50 PDT 2026
https://github.com/nga888 updated https://github.com/llvm/llvm-project/pull/214188
>From 0b31bdb1a5d6d4a3b965944d09397286700b222d Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 4 Aug 2026 16:53:52 +0100
Subject: [PATCH 01/15] [dyndbg][LLD][ELF] Initial LLD support for dynamic
debugging
LLVM's Dynamic Debugging feature (`llvm/docs/DynamicDebugging.md`)
generates optimized relocatable files with an unoptimized relocatable
copy of the functions from its translation unit embedded in a
`SHT_LLVM_DYNDBG_ELF` section named `.debug_llvm_dyndbg`. The
unoptimized functions in the "inner" embedded relocatable reference the
functions and data of the "outer" optimized relocatable.
When LLD encounters dynamic debugging objects, a nested "inner"
relocatable link is run with the embedded "inner" unoptimized
objects. The output is embedded in a `SHT_LLVM_DYNDBG_ELF` section named
`.debug_llvm_dyndbg` and described by an `NT_LLVM_DYNAMIC_DEBUGGING`
note in the "outer" output. The "inner" relocatable link more closely
resembles a final link rather than a regular relocatable `-r` one,
unless the "outer" link is itself a relocatable `-r` link.
The embedded "inner" objects' undefined symbols are added to the "outer"
symbol table so that the "outer" link resolves the dependencies of the
"inner" relocatable link. Symbols referenced by "inner" relocations from
an allocated section are assigned a PLT/GOT entry as appropriate and are
diagnosed when unresolved. In this initial implementation, these symbols
also act as `--gc-sections` roots for the "outer" link and
`--gc-sections` is not supported for the "inner" relocatable link.
ICF is disabled so that individual functions can be patched when
debugging with dynamic debugging without affecting other identical
functions.
RFC: https://discourse.llvm.org/t/90113
---
lld/Common/CommonLinkerContext.cpp | 14 +
lld/ELF/Config.h | 19 +-
lld/ELF/Driver.cpp | 79 +++-
lld/ELF/InputFiles.cpp | 91 +++-
lld/ELF/InputFiles.h | 6 +
lld/ELF/LinkerScript.cpp | 4 +-
lld/ELF/MarkLive.cpp | 15 +-
lld/ELF/Symbols.h | 5 +
lld/ELF/SyntheticSections.cpp | 37 ++
lld/ELF/SyntheticSections.h | 14 +
lld/ELF/Writer.cpp | 43 +-
.../ELF/Inputs/trace-symbols-dyndbg-opt.s | 4 +
.../ELF/Inputs/trace-symbols-dyndbg-unopt.s | 5 +
lld/test/ELF/dynamic-debug.test | 401 ++++++++++++++++++
lld/test/ELF/trace-symbols.s | 51 ++-
llvm/include/llvm/BinaryFormat/ELF.h | 1 +
16 files changed, 747 insertions(+), 42 deletions(-)
create mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
create mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
create mode 100644 lld/test/ELF/dynamic-debug.test
diff --git a/lld/Common/CommonLinkerContext.cpp b/lld/Common/CommonLinkerContext.cpp
index 12f56bc10ec963..cafd78df4a2ba5 100644
--- a/lld/Common/CommonLinkerContext.cpp
+++ b/lld/Common/CommonLinkerContext.cpp
@@ -22,7 +22,14 @@ using namespace lld;
// state.
static CommonLinkerContext *lctx;
+static uint32_t numNestedCtx;
+
CommonLinkerContext::CommonLinkerContext() {
+ if (lctx) {
+ ++numNestedCtx;
+ return;
+ }
+
lctx = this;
// Fire off the static initializations in CGF's constructor.
codegen::RegisterCodeGenFlags CGF;
@@ -34,6 +41,13 @@ CommonLinkerContext::~CommonLinkerContext() {
// new in SpecificAlloc::create().
for (auto &it : instances)
it.second->~SpecificAllocBase();
+
+ if (numNestedCtx) {
+ assert(lctx != this);
+ --numNestedCtx;
+ return;
+ }
+
lctx = nullptr;
}
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 54e0aa58591ad0..de61f3674d4ef8 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -209,6 +209,7 @@ class LinkerDriver {
void linkerMain(ArrayRef<const char *> args);
void addFile(StringRef path, bool withLOption);
void addLibrary(StringRef name);
+ void addFile(std::unique_ptr<ELFFileBase> ef);
private:
Ctx &ctx;
@@ -233,8 +234,8 @@ class LinkerDriver {
public:
// See InputFile::groupId.
- uint32_t nextGroupId;
- bool isInGroup;
+ uint32_t nextGroupId = 0;
+ bool isInGroup = false;
std::unique_ptr<InputFile> armCmseImpLib;
SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
};
@@ -646,6 +647,8 @@ struct InStruct {
std::unique_ptr<SymtabShndxSection> symTabShndx;
std::unique_ptr<SyntheticSection> hexagonAttributes;
std::unique_ptr<SyntheticSection> riscvAttributes;
+ std::unique_ptr<SyntheticSection> dynDbg;
+ std::unique_ptr<SyntheticSection> dynDbgNote;
};
struct Ctx : CommonLinkerContext {
@@ -784,6 +787,18 @@ struct Ctx : CommonLinkerContext {
llvm::raw_fd_ostream openAuxiliaryFile(llvm::StringRef, std::error_code &);
std::optional<AArch64PauthAbiCoreInfo> aarch64PauthAbiCoreInfo;
+
+ // True if performing the embedded unoptimized dynamic debugging relocatable
+ // link.
+ bool inDynDbgLink = false;
+ // True if performing dynamic debugging style relocatable link rather than a
+ // regular relocatable link.
+ bool dynDbgRelocatable = false;
+ // True if the link contains dynamic debugging.
+ bool hasDynDbg = false;
+ // Pointer to the output of the embedded unoptimized dynamic debugging
+ // relocatable link.
+ std::unique_ptr<llvm::FileOutputBuffer> dynDbgOutput;
};
// The first two elements of versionDefinitions represent VER_NDX_LOCAL and
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 0393410b35d4d9..7130da9608e610 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,6 +113,22 @@ llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
return {filename, ec, flags};
}
+static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0,
+ llvm::raw_ostream &stdoutOS,
+ llvm::raw_ostream &stderrOS, bool exitEarly,
+ bool disableOutput) {
+ ErrorHandler &e = ctx.e;
+ e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ e.logName = args::getFilenameWithoutExe(arg0);
+ e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
+ "--error-limit=0 to see all errors)";
+ ctx.script = &script;
+ ctx.symAux.emplace_back();
+ ctx.symtab = std::make_unique<SymbolTable>(ctx);
+
+ ctx.arg.progName = arg0;
+}
+
namespace lld {
namespace elf {
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
@@ -120,19 +136,9 @@ bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
// This driver-specific context will be freed later by unsafeLldMain().
auto *context = new Ctx;
Ctx &ctx = *context;
-
- context->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
- context->e.logName = args::getFilenameWithoutExe(args[0]);
- context->e.errorLimitExceededMsg =
- "too many errors emitted, stopping now (use "
- "--error-limit=0 to see all errors)";
-
LinkerScript script(ctx);
- ctx.script = &script;
- ctx.symAux.emplace_back();
- ctx.symtab = std::make_unique<SymbolTable>(ctx);
-
- ctx.arg.progName = args[0];
+ initContext(ctx, script, args[0], stdoutOS, stderrOS, exitEarly,
+ disableOutput);
ctx.driver.linkerMain(args);
@@ -301,6 +307,11 @@ void LinkerDriver::addLibrary(StringRef name) {
{name});
}
+// Add an ELF input file directly.
+void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
+ files.push_back(std::move(ef));
+}
+
// This function is called on startup. We need this for LTO since
// LTO calls LLVM functions to compile bitcode files to native code.
// Technically this can be delayed until we read bitcode files, but
@@ -2230,8 +2241,6 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
// Iterate over argv to process input files and positional arguments.
std::optional<MemoryBufferRef> defaultScript;
- nextGroupId = 0;
- isInGroup = false;
bool hasInput = false, hasScript = false;
for (auto *arg : args) {
switch (arg->getOption().getID()) {
@@ -3240,6 +3249,41 @@ static void postParseObjectFile(ELFFileBase *file) {
}
}
+template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
+ Ctx dc;
+ LinkerScript script(dc);
+ initContext(dc, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
+ ctx.e.exitEarly, ctx.e.disableOutput);
+ dc.inDynDbgLink = true;
+ dc.dynDbgRelocatable = !ctx.arg.relocatable;
+
+ for (auto *file : ctx.objectFiles) {
+ auto *obj = cast<ObjFile<ELFT>>(file);
+ if (obj->dynDbgSec) {
+ auto content = obj->dynDbgSec->content();
+ MemoryBufferRef mb({(const char *)content.data(), content.size()},
+ obj->mb.getBufferIdentifier());
+ dc.driver.addFile(createObjFile(dc, mb));
+ }
+ }
+
+ if (errCount(ctx))
+ return;
+
+ std::vector<const char *> args{
+ dc.arg.progName.data(), "-r", "-o", "-",
+ dc.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
+ if (ctx.arg.resolveGroups)
+ args.push_back("--force-group-allocation");
+ dc.driver.linkerMain(args);
+ if (errCount(dc) > 0 || !dc.dynDbgOutput) {
+ Err(ctx) << "Failed to create relocatable dynamic debug object";
+ return;
+ }
+
+ ctx.dynDbgOutput.swap(dc.dynDbgOutput);
+}
+
// Do actual linking. Note that when this function is called,
// all linker scripts have already been parsed.
template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
@@ -3533,6 +3577,13 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
createTaggedSymbols(ctx);
}
+ if (ctx.hasDynDbg) {
+ llvm::TimeTraceScope timeScope("Link dynamic debugging");
+ linkDynamicDebug<ELFT>(ctx);
+ if (errCount(ctx))
+ return;
+ }
+
// Create synthesized sections such as .got and .plt. This is called before
// processSectionCommands() so that they can be placed by SECTIONS commands.
createSyntheticSections<ELFT>(ctx);
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 23649d62007196..79298baa9c3991 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -591,6 +591,7 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
continue;
+
if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
StringRef signature = getShtGroupSignature(objSections, sec);
ArrayRef<Elf_Word> entries =
@@ -643,6 +644,26 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
continue;
}
+ if (sec.sh_type == SHT_LLVM_DYNDBG_ELF) {
+ // Check for presence of dynamic debugging section and create the input
+ // section but mark for discard.
+ StringRef name = check(obj.getSectionName(sec, shstrtab));
+ if (name == dynDbgSecName) {
+ sections[i] = &InputSection::discarded;
+ dynDbgSec = std::make_unique<InputSection>(*this, sec, name);
+ ctx.hasDynDbg = true;
+
+ // If ICF is enabled, warn and disable it because it's incompatible with
+ // dynamic debugging.
+ if (ctx.arg.icf != ICFLevel::None) {
+ Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
+ "debugging";
+ ctx.arg.icf = ICFLevel::None;
+ }
+ }
+ continue;
+ }
+
switch (ctx.arg.emachine) {
case EM_ARM:
if (sec.sh_type == SHT_ARM_ATTRIBUTES) {
@@ -830,7 +851,9 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
}
switch (type) {
case SHT_GROUP: {
- if (!ctx.arg.relocatable)
+ // Discard groups for the embedded unoptimized dynamic debugging
+ // relocatable link.
+ if (!ctx.arg.relocatable || ctx.dynDbgRelocatable)
sections[i] = &InputSection::discarded;
// Use the verdict parse() recorded for this group instead of repeating
// the signature hashing and comdatGroups lookup.
@@ -1249,6 +1272,72 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
sym->isUsedInRegularObj = true;
sym->referenced = true;
}
+
+ // Process the undefined symbols of the "inner" embedded unoptimized dynamic
+ // debugging object to ensure that the "outer" ELF contains the dependencies
+ // of the "inner" ELF. Also tag which "outer" global symbols are dynamic
+ // debugging references, i.e. used in an "inner" relocation for a SHT_PROGBITS
+ // and SHF_ALLOC section, via the `isDynDbgRef` flag.
+ if (dynDbgSec) {
+ auto content = dynDbgSec->content();
+ MemoryBufferRef dbgMb({(const char *)content.data(), content.size()},
+ mb.getBufferIdentifier());
+ std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
+ ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(efb.get());
+ if (dbgObj) {
+ SmallVector<bool, 0> globalUsed(dbgObj->numSymbols - dbgObj->firstGlobal);
+ ArrayRef<Elf_Shdr> shdrs = dbgObj->template getELFShdrs<ELFT>();
+ for (size_t i = 0, end = shdrs.size(); i != end; ++i) {
+ const Elf_Shdr &sh = shdrs[i];
+ if (!isStaticRelSecType(sh.sh_type))
+ continue;
+
+ const Elf_Shdr &target = shdrs[sh.sh_info];
+ if (target.sh_type != SHT_PROGBITS ||
+ (target.sh_flags & SHF_ALLOC) == 0)
+ continue;
+
+ auto setSymUsed = [&,
+ firstGlobal = dbgObj->firstGlobal](uint32_t symIdx) {
+ if (symIdx >= firstGlobal)
+ globalUsed[symIdx - firstGlobal] = true;
+ };
+
+ auto isec = std::make_unique<InputSection>(*dbgObj, sh, StringRef());
+ isec->relSecIdx = i;
+ auto relocs = isec->template relsOrRelas<ELFT>(/*supportsCrel=*/false);
+ if (relocs.areRelocsRel()) {
+ for (auto const &r : relocs.rels)
+ setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+ } else {
+ for (auto const &r : relocs.relas)
+ setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+ }
+ }
+
+ ArrayRef<Elf_Sym> dbgSyms = dbgObj->template getGlobalELFSyms<ELFT>();
+ assert(dbgSyms.size() == globalUsed.size());
+ for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
+ const Elf_Sym &s = dbgSyms[i];
+ if (s.st_shndx != SHN_UNDEF)
+ continue;
+
+ StringRef name = CHECK2(s.getName(dbgObj->stringTable), this);
+ Symbol *sym = symtab->addSymbol(
+ Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
+ sym->isUsedInRegularObj = true;
+ sym->referenced = true;
+ if (globalUsed[i]) {
+ sym->isDynDbgRef = true;
+ if (sym->traced)
+ Msg(ctx) << this << ": dynamic debugging reference to " << name;
+ }
+ }
+ } else {
+ Err(ctx) << this << ": " << dynDbgSecName
+ << " contains an incompatible ELF type";
+ }
+ }
}
template <class ELFT>
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 0ded9b2fa38e2d..22c29f9ea7909f 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -268,6 +268,9 @@ template <class ELFT> class ObjFile : public ELFFileBase {
// Pointer to this input file's .llvm_addrsig section, if it has one.
const Elf_Shdr *addrsigSec = nullptr;
+ // Embedded unoptimized dynamic debug input section.
+ std::unique_ptr<InputSection> dynDbgSec;
+
// SHT_LLVM_CALL_GRAPH_PROFILE section index.
uint32_t cgProfileSectionIndex = 0;
@@ -389,6 +392,9 @@ std::unique_ptr<ELFFileBase> createObjFile(Ctx &, MemoryBufferRef mb,
std::string replaceThinLTOSuffix(Ctx &, StringRef path);
+// Name of embedded unoptimized dynamic debug input/output section.
+constexpr StringRef dynDbgSecName = ".debug_llvm_dyndbg";
+
} // namespace elf
} // namespace lld
diff --git a/lld/ELF/LinkerScript.cpp b/lld/ELF/LinkerScript.cpp
index dcc84b8755db1b..c69b0616a23678 100644
--- a/lld/ELF/LinkerScript.cpp
+++ b/lld/ELF/LinkerScript.cpp
@@ -67,7 +67,9 @@ StringRef LinkerScript::getOutputSectionName(const InputSectionBase *s) const {
return ss.save(".rela" + out->name);
return ss.save(".rel" + out->name);
}
- if (ctx.arg.relocatable)
+ // Use default LLD behavior for the embedded unoptimized dynamic debugging
+ // relocatable link.
+ if (ctx.arg.relocatable && !ctx.dynDbgRelocatable)
return s->name;
}
diff --git a/lld/ELF/MarkLive.cpp b/lld/ELF/MarkLive.cpp
index 2820818133d5c6..8e5071cdc0073a 100644
--- a/lld/ELF/MarkLive.cpp
+++ b/lld/ELF/MarkLive.cpp
@@ -363,11 +363,20 @@ template <class ELFT, bool TrackWhyLive>
void MarkLive<ELFT, TrackWhyLive>::run() {
// Add GC root symbols.
- // Preserve externally-visible symbols if the symbols defined by this
- // file can interpose other ELF file's symbols at runtime.
- for (Symbol *sym : ctx.symtab->getSymbols())
+ for (Symbol *sym : ctx.symtab->getSymbols()) {
+ // For now, preserve all symbols required by the embedded unoptimized part
+ // of dynamic debugging. TODO: better support for `--gc-sections`.
+ if (sym->isDynDbgRef) {
+ markSymbol(sym, "dynamic debugging");
+ sym->setFlags(USED);
+ continue;
+ }
+
+ // Preserve externally-visible symbols if the symbols defined by this
+ // file can interpose other ELF file's symbols at runtime.
if (sym->isExported)
markSymbol(sym, "externally visible symbol");
+ }
markSymbol(ctx.symtab->find(ctx.arg.entry), "entry point");
markSymbol(ctx.symtab->find(ctx.arg.init), "initializer function");
diff --git a/lld/ELF/Symbols.h b/lld/ELF/Symbols.h
index c489416e1f4dad..15032e472505c1 100644
--- a/lld/ELF/Symbols.h
+++ b/lld/ELF/Symbols.h
@@ -344,6 +344,11 @@ class Symbol {
LLVM_PREFERRED_TYPE(bool)
uint8_t referencedAfterWrap : 1;
+ // True if this symbol is referenced by the embedded unoptimized part of
+ // dynamic debugging.
+ LLVM_PREFERRED_TYPE(bool)
+ uint8_t isDynDbgRef : 1;
+
void setFlags(uint16_t bits) {
flags.fetch_or(bits, std::memory_order_relaxed);
}
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index aa7a859a9da9a1..d3d9889dbbf0b6 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4417,6 +4417,36 @@ size_t MemtagGlobalDescriptors::getSize() const {
return createMemtagGlobalDescriptors(ctx, symbols);
}
+DynamicDebugSection::DynamicDebugSection(Ctx &ctx)
+ : SyntheticSection(ctx, dynDbgSecName, SHT_LLVM_DYNDBG_ELF, 0, 8) {
+ assert(ctx.dynDbgOutput);
+}
+
+size_t DynamicDebugSection::getSize() const {
+ return ctx.dynDbgOutput->getBufferSize();
+}
+
+void DynamicDebugSection::writeTo(uint8_t *buf) {
+ memcpy(buf, ctx.dynDbgOutput->getBufferStart(),
+ ctx.dynDbgOutput->getBufferSize());
+}
+
+DynamicDebugNote::DynamicDebugNote(Ctx &ctx)
+ : SyntheticSection(ctx, ".note.llvm.dyndbg", SHT_NOTE, 0, 4) {}
+
+size_t DynamicDebugNote::getSize() const {
+ return /*hdrsz=*/12 + /*namesz=*/8 + /*descsz=*/sizeof(uint32_t);
+}
+
+void DynamicDebugNote::writeTo(uint8_t *buf) {
+ write32(ctx, buf, 5); // Name size
+ write32(ctx, buf + 4, sizeof(uint32_t)); // Content size
+ write32(ctx, buf + 8, NT_LLVM_DYNAMIC_DEBUGGING); // Type
+ memcpy(buf + 12, "LLVM", 5); // Name string
+ uint32_t version = 0;
+ write32(ctx, buf + 20, version);
+}
+
static OutputSection *findSection(Ctx &ctx, StringRef name) {
for (SectionCommand *cmd : ctx.script->sectionCommands)
if (auto *osd = dyn_cast<OutputDesc>(cmd))
@@ -4641,6 +4671,13 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
add(*ctx.in.shStrTab);
if (ctx.in.strTab)
add(*ctx.in.strTab);
+
+ if (ctx.dynDbgOutput) {
+ ctx.in.dynDbg = std::make_unique<DynamicDebugSection>(ctx);
+ add(*ctx.in.dynDbg);
+ ctx.in.dynDbgNote = std::make_unique<DynamicDebugNote>(ctx);
+ add(*ctx.in.dynDbgNote);
+ }
}
template void elf::splitSections<ELF32LE>(Ctx &);
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index cbdadcceceaafe..f8b895d9d2a999 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -1317,6 +1317,20 @@ class MemtagGlobalDescriptors final : public SyntheticSection {
SmallVector<const Symbol *, 0> symbols;
};
+class DynamicDebugSection final : public SyntheticSection {
+public:
+ DynamicDebugSection(Ctx &);
+ size_t getSize() const override;
+ void writeTo(uint8_t *buf) override;
+};
+
+class DynamicDebugNote final : public SyntheticSection {
+public:
+ DynamicDebugNote(Ctx &);
+ size_t getSize() const override;
+ void writeTo(uint8_t *buf) override;
+};
+
template <class ELFT> void createSyntheticSections(Ctx &);
InputSection *createInterpSection(Ctx &);
MergeInputSection *createCommentSection(Ctx &);
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 8cf88cd99cf8a1..bd0d394386aac4 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -365,10 +365,14 @@ template <class ELFT> void Writer<ELFT>::run() {
if (errCount(ctx))
return;
- // With -o -, write to lld::outs() (the stdoutOS argument of
+ // Capture output for the embedded unoptimized dynamic debugging relocatable
+ // link.
+ // Otherwise, with -o -, write to lld::outs() (the stdoutOS argument of
// link()) instead of committing the buffer, which would write to the
// process's stdout.
- if (ctx.arg.outputFile == "-") {
+ if (ctx.inDynDbgLink)
+ ctx.dynDbgOutput.swap(buffer);
+ else if (ctx.arg.outputFile == "-") {
ctx.e.outs() << StringRef(
reinterpret_cast<const char *>(buffer->getBufferStart()),
buffer->getBufferSize());
@@ -1914,6 +1918,41 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
// called after processSymbolAssignments() because it needs to know whether
// a linker-script-defined symbol is absolute.
scanRelocations<ELFT>(ctx);
+
+ // Process symbols referenced by the embedded unoptimized part of dynamic
+ // debugging.
+ if (ctx.hasDynDbg) {
+ bool ignoreUnresolved =
+ (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
+ bool warnOnly = (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+ for (Symbol *sym : ctx.symtab->getSymbols()) {
+ if (!sym->isDynDbgRef)
+ continue;
+
+ if (sym->isUndefined()) {
+ if (ignoreUnresolved || sym->isWeak())
+ continue;
+
+ static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
+ ArrayRef<uint8_t>());
+ ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
+ InputSectionBase *isec =
+ dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
+ ctx.undefErrs.push_back(
+ {cast<Undefined>(sym), {{isec, 0}}, warnOnly});
+ continue;
+ }
+
+ // Ensure there are PLT/GOT entries for references to shared symbols.
+ if (sym->isShared() && sym->isUsedInRegularObj && sym->dsoDefined) {
+ if (sym->isFunc())
+ sym->setFlags(NEEDS_PLT);
+ else if (sym->isObject())
+ sym->setFlags(NEEDS_GOT);
+ }
+ }
+ }
+
reportUndefinedSymbols(ctx);
postScanRelocations(ctx);
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
new file mode 100644
index 00000000000000..1fba360bb54aba
--- /dev/null
+++ b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
@@ -0,0 +1,4 @@
+.globl baz
+.type baz, @function
+baz:
+nop
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
new file mode 100644
index 00000000000000..7688e526340d96
--- /dev/null
+++ b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
@@ -0,0 +1,5 @@
+.globl __dyndbg.baz
+.hidden __dyndbg.baz
+.type __dyndbg.baz, @function
+__dyndbg.baz:
+callq foo
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
new file mode 100644
index 00000000000000..19a99619e95e77
--- /dev/null
+++ b/lld/test/ELF/dynamic-debug.test
@@ -0,0 +1,401 @@
+REQUIRES: x86
+
+## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
+## of various specific situations that can arise with such objects. These are mainly
+## related to dependencies from the "inner" unoptimized objects.
+
+RUN: rm -rf %t && split-file %s %t
+
+## Create the "outer" optimized objects.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Link only the "outer" optimized objects using `--gc-sections`.
+RUN: ld.lld -e main --gc-sections -o %t/out %t/main.o %t/foo.o
+RUN: llvm-readelf -s %t/out | FileCheck %s
+
+CHECK: Symbol table '.symtab' contains 12 entries:
+CHECK-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
+CHECK-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
+CHECK-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.main
+CHECK-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.main
+CHECK-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
+CHECK-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
+CHECK-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.foo
+CHECK-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
+CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] func
+CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] main
+CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] foo
+
+## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
+## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+
+## Create shared object to test handling of "inner" only dependencies.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/shared.s -o %t/shared.o
+RUN: ld.lld -shared -o %t/shared.so %t/shared.o
+
+## Link simulated dynamic debugging objects.
+RUN: ld.lld -e main -o %t/out-dd %t/main.o %t/foo.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-dd | FileCheck -DNSYM=21 --check-prefixes=DD,NO-GC %s
+
+## Link simulated dynamic debugging objects using `--gc-sections`.
+## Note that `--gc-sections` is not yet supported for the "inner" unoptimized relocatable
+## link.
+RUN: ld.lld -e main --gc-sections -o %t/out-dd-gc %t/main.o %t/foo.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-dd-gc | FileCheck -DNSYM=19 --check-prefixes=DD,GC %s
+
+## Check dynamic debugging related sections.
+DD: There are 20 section headers, starting at offset {{.*}}:
+DD: Name Type Address Off Size ES Flg Lk Inf Al
+DD-DAG: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 0 0 8
+DD-DAG: .note.llvm.dyndbg NOTE 0000000000000000 {{.*}} {{.*}} 00 0 0 4
+
+## Check GOT/PLT entries in "outer" ELF for shared symbols only referenced by "inner"
+## unoptimized objects.
+DD: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+DD: {{.*}} {{.*}} R_X86_64_GLOB_DAT 0000000000000000 shared_data_for_unopt
+DD: Relocation section '.rela.plt' at offset {{.*}} contains 1 entries:
+DD: {{.*}} {{.*}} R_X86_64_JUMP_SLOT 0000000000000000 shared_func_for_unopt
+
+NO-GC: Symbol table '.dynsym' contains 4 entries:
+GC: Symbol table '.dynsym' contains 3 entries:
+DD-DAG: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
+DD-DAG: 0000000000000000 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
+NO-GC-DAG: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+
+DD: Symbol table '.symtab' contains [[NSYM]] entries:
+DD-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
+DD-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
+DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.main
+DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.main
+DD-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
+DD-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
+DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.foo
+DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
+DD-DAG: {{.*}} 0 NOTYPE LOCAL HIDDEN [[#]] _DYNAMIC
+DD-DAG: {{.*}} 0 FUNC WEAK DEFAULT [[#]] comdat_func
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] func
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] main
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] foo
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_in_opt
+NO-GC-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] unused_func
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] used_by_unused_func
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
+DD-DAG: {{.*}} 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
+DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
+NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
+REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
+REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS
+
+DD: LLVM 0x00000004 Unknown note type: (0x00000004)
+DD-NEXT: description data: 00 00 00 00
+
+## Extract and check "inner" ELF for the default link.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd.o %t/out-dd /dev/null
+RUN: llvm-readelf -Ssg %t/in-dd.o | FileCheck --check-prefix=IN %s
+
+## Extract and check "inner" ELF for the `--gc-sections` link.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd-gc.o %t/out-dd-gc /dev/null
+RUN: llvm-readelf -Ssg %t/in-dd-gc.o | FileCheck --check-prefix=IN %s
+
+IN: There are 9 section headers, starting at offset {{.*}}:
+IN: Name Type Address Off Size ES Flg Lk Inf Al
+IN: .text PROGBITS 0000000000000000 {{.*}} {{.*}} 00 AX 0 0 4
+IN-NEXT: .rela.text RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
+IN-NEXT: .debug_xyz PROGBITS 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+IN-NEXT: .rela.debug_xyz RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
+IN-NEXT: .note.GNU-stack PROGBITS 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+IN-NEXT: .symtab SYMTAB 0000000000000000 {{.*}} {{.*}} 18 [[#]] [[#]] 8
+IN-NEXT: .shstrtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+IN-NEXT: .strtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+
+IN: Symbol table '.symtab' contains 26 entries:
+IN-DAG: {{.*}} 0 SECTION LOCAL DEFAULT [[#]] .text
+IN-DAG: {{.*}} 0 SECTION LOCAL DEFAULT [[#]] .debug_xyz
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.static_func.dyndbg.main
+IN-DAG: {{.*}} 0 FUNC WEAK HIDDEN [[#]] __dyndbg.comdat_func
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.func
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND inlined_in_opt
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND comdat_func
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND inlined_for_unopt_and_non_alloc
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_data_for_unopt
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.main
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND foo
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND func
+IN-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] __dyndbg.inlined_in_opt
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.unused_func
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND used_by_unused_func
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.inlined_for_unopt_and_non_alloc
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.static_func.dyndbg.foo
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.foo
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_func_for_unopt
+IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.used_by_unused_func
+IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+## Groups have been resolved.
+IN: There are no section groups in this file.
+
+## Check disabling of incompatible ICF options.
+RUN: ld.lld -e main --icf=all -o %t/out-dd-icf-all %t/main.o %t/foo.o %t/shared.so 2>&1 \
+RUN: | FileCheck --check-prefix=ICF-WARN %s
+RUN: cmp %t/out-dd-icf-all %t/out-dd
+RUN: ld.lld -e main --icf=safe -o %t/out-dd-icf-safe %t/main.o %t/foo.o %t/shared.so 2>&1 \
+RUN: | FileCheck --check-prefix=ICF-WARN %s
+RUN: cmp %t/out-dd-icf-safe %t/out-dd
+
+ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
+
+## Check relocatable link of simulated dynamic debugging objects.
+RUN: ld.lld -r -o %t/rel.o %t/main.o %t/foo.o
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/rel-dd.o %t/rel.o /dev/null
+RUN: ld.lld -r -o %t/rel-unopt.o %t/main-unopt.o %t/foo-unopt.o
+RUN: cmp %t/rel-dd.o %t/rel-unopt.o
+
+## Link the above relocatable link output with and without `--gc-sections`.
+RUN: ld.lld -e main -o %t/out-rel-dd %t/rel.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-rel-dd | FileCheck -DNSYM=24 --check-prefixes=DD,NO-GC,REL %s
+RUN: ld.lld -e main --gc-sections -o %t/out-rel-dd-gc %t/rel.o %t/shared.so
+RUN: llvm-readelf -Srsn %t/out-rel-dd-gc | FileCheck -DNSYM=22 --check-prefixes=DD,GC,REL %s
+
+## Extract and check "inner" ELF for the above links.
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd.o %t/out-rel-dd /dev/null
+RUN: llvm-readelf -Ssg %t/in-rel-dd.o | FileCheck --check-prefix=IN %s
+RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd-gc.o %t/out-rel-dd-gc /dev/null
+RUN: llvm-readelf -Ssg %t/in-rel-dd-gc.o | FileCheck --check-prefix=IN %s
+
+#--- main.s
+# Optimized version of "main"
+
+.local static_func
+.type static_func, at function
+.section .text.static_func,"ax", at progbits
+static_func:
+ ret
+
+.global static_func.dyndbg.main
+.hidden static_func.dyndbg.main
+static_func.dyndbg.main = static_func
+
+.weak comdat_func
+.type comdat_func, at function
+.section .text.comdat_func,"axG", at progbits,comdat_func,comdat
+comdat_func:
+ ret
+
+.global func
+.type func, at function
+.section .text.func,"ax", at progbits
+func:
+ call static_func
+ mov static_data,%rax
+ ret
+
+.global main
+.type main, at function
+.section .text.main,"ax", at progbits
+main:
+ call foo
+ call func
+ ret
+
+.global inlined_in_opt
+.type inlined_in_opt, at function
+.section .text.inlined_in_opt,"ax", at progbits
+inlined_in_opt:
+ ret
+
+.global unused_func
+.type unused_func, at function
+.section .text.unused_func,"ax", at progbits
+unused_func:
+ call used_by_unused_func
+ ret
+
+.global inlined_for_unopt_and_non_alloc
+.type inlined_for_unopt_and_non_alloc, at function
+.section .text.inlined_for_unopt_and_non_alloc,"ax", at progbits
+inlined_for_unopt_and_non_alloc:
+ ret
+
+.local static_data
+.type static_data, at object
+.section .data.static_data,"aw", at progbits
+static_data:
+.asciz "main static data"
+
+.global static_data.dyndbg.main
+.hidden static_data.dyndbg.main
+static_data.dyndbg.main = static_data
+
+#--- main-unopt.s
+# Unoptimized version of "main"
+
+.global __dyndbg.static_func.dyndbg.main
+.hidden __dyndbg.static_func.dyndbg.main
+.type __dyndbg.static_func.dyndbg.main, at function
+.section .text.__dyndbg.static_func.dyndbg.main,"ax", at progbits
+__dyndbg.static_func.dyndbg.main:
+ ret
+
+.weak __dyndbg.comdat_func
+.hidden __dyndbg.comdat_func
+.type __dyndbg.comdat_func, at function
+.section .text.__dyndbg.comdat_func,"axG", at progbits,__dyndbg.comdat_func,comdat
+__dyndbg.comdat_func:
+ ret
+
+.global __dyndbg.func
+.hidden __dyndbg.func
+.type __dyndbg.func, at function
+.section .text.__dyndbg.func,"ax", at progbits
+__dyndbg.func:
+ call static_func.dyndbg.main
+ mov static_data.dyndbg.main,%rax
+
+ # The following is only in the unoptimized due to inlining in the optimized.
+ call inlined_in_opt
+ call comdat_func
+ call inlined_for_unopt_and_non_alloc
+ mov shared_data_for_unopt,%rax
+
+ ret
+
+.global __dyndbg.main
+.hidden __dyndbg.main
+.type __dyndbg.main, at function
+.section .text.__dyndbg.main,"ax", at progbits
+__dyndbg.main:
+ call foo
+ call func
+ ret
+
+.global __dyndbg.inlined_in_opt
+.type __dyndbg.inlined_in_opt, at function
+.section .text.__dyndbg.inlined_in_opt,"ax", at progbits
+__dyndbg.inlined_in_opt:
+ ret
+
+.global __dyndbg.unused_func
+.hidden __dyndbg.unused_func
+.type __dyndbg.unused_func, at function
+.section .text.__dyndbg.unused_func,"ax", at progbits
+__dyndbg.unused_func:
+ call used_by_unused_func
+ ret
+
+.global __dyndbg.inlined_for_unopt_and_non_alloc
+.hidden __dyndbg.inlined_for_unopt_and_non_alloc
+.type __dyndbg.inlined_for_unopt_and_non_alloc, at function
+.section .text.__dyndbg.inlined_for_unopt_and_non_alloc,"ax", at progbits
+__dyndbg.inlined_for_unopt_and_non_alloc:
+ ret
+
+#--- foo.s
+# Optimized version of "foo"
+
+.local static_func
+.type static_func, at function
+.section .text.static_func,"ax", at progbits
+static_func:
+ ret
+
+.global static_func.dyndbg.foo
+.hidden static_func.dyndbg.foo
+static_func.dyndbg.foo = static_func
+
+.weak comdat_func
+.type comdat_func, at function
+.section .text.comdat_func,"axG", at progbits,comdat_func,comdat
+comdat_func:
+ ret
+
+.global foo
+.type foo, at function
+.section .text.foo,"ax", at progbits
+foo:
+ call static_func
+ mov static_data,%rax
+ ret
+
+.global used_by_unused_func
+.type used_by_unused_func, at function
+.section .text.used_by_unused_func,"ax", at progbits
+used_by_unused_func:
+ ret
+
+.local static_data
+.type static_data, at object
+.section .data.static_data,"aw", at progbits
+static_data:
+.asciz "foo static data"
+
+.global static_data.dyndbg.foo
+.hidden static_data.dyndbg.foo
+static_data.dyndbg.foo = static_data
+
+#--- foo-unopt.s
+# Unoptimized version of "foo"
+
+.global __dyndbg.static_func.dyndbg.foo
+.hidden __dyndbg.static_func.dyndbg.foo
+.type __dyndbg.static_func.dyndbg.foo, at function
+.section .text.__dyndbg.static_func.dyndbg.foo,"ax", at progbits
+__dyndbg.static_func.dyndbg.foo:
+ ret
+
+.weak __dyndbg.comdat_func
+.hidden __dyndbg.comdat_func
+.type __dyndbg.comdat_func, at function
+.section .text.__dyndbg.comdat_func,"axG", at progbits,__dyndbg.comdat_func,comdat
+__dyndbg.comdat_func:
+ ret
+
+.global __dyndbg.foo
+.hidden __dyndbg.foo
+.type __dyndbg.foo, at function
+.section .text.__dyndbg.foo,"ax", at progbits
+__dyndbg.foo:
+ call static_func.dyndbg.foo
+ mov static_data.dyndbg.foo,%rax
+
+ # The following is only in the unoptimized due to inlining in the optimized.
+ call shared_func_for_unopt
+ call comdat_func
+
+ ret
+
+.global __dyndbg.used_by_unused_func
+.hidden __dyndbg.used_by_unused_func
+.type __dyndbg.used_by_unused_func, at function
+.section .text.__dyndbg.used_by_unused_func,"ax", at progbits
+__dyndbg.used_by_unused_func:
+ ret
+
+.section .debug_xyz,"", at progbits
+.quad undef_used_by_non_alloc
+.quad inlined_for_unopt_and_non_alloc
+
+#--- shared.s
+.global shared_func_for_unopt
+.type shared_func_for_unopt, at function
+shared_func_for_unopt:
+ ret
+
+.data
+
+.global shared_data_for_unopt
+.type shared_data_for_unopt, at object
+shared_data_for_unopt:
+.word 0xcafe
diff --git a/lld/test/ELF/trace-symbols.s b/lld/test/ELF/trace-symbols.s
index 785414e2bd5b54..ae17899588d315 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -3,15 +3,23 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
+# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
+# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
# RUN: ld.lld -shared %t1 -o %t1.so
# RUN: ld.lld -shared %t2 -o %t2.so
# RUN: rm -f %t1.a
# RUN: llvm-ar rcs %t1.a %t1
# RUN: rm -f %t2.a
# RUN: llvm-ar rcs %t2.a %t2
+## Create dynamic debugging style object.
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
+# RUN: %p/Inputs/trace-symbols-dyndbg-opt.s -o %t3
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
+# RUN: %p/Inputs/trace-symbols-dyndbg-unopt.s -o %t-unopt
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t-unopt %t3
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t3
# RUN: ld.lld -y foo -shared %t1 %t1.so -o /dev/null | \
# RUN: FileCheck --check-prefix=PREEMPT %s --implicit-check-not=foo
@@ -19,75 +27,80 @@
# PREEMPT-NEXT: trace-symbols.s.tmp1.so: shared definition of foo
# RUN: ld.lld -y foo -trace-symbol common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTRFOO %s
+# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTRFOO %s
# OBJECTRFOO: trace-symbols.s.tmp: reference to foo
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTDCOMMON %s
+# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTDCOMMON %s
# OBJECTDCOMMON: trace-symbols.s.tmp1: common definition of common
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD1FOO %s
+# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD1FOO %s
# OBJECTD1FOO: trace-symbols.s.tmp: reference to foo
# OBJECTD1FOO: trace-symbols.s.tmp1: common definition of common
# OBJECTD1FOO: trace-symbols.s.tmp1: definition of foo
# OBJECTD1FOO: trace-symbols.s.tmp2: definition of foo
-# RUN: ld.lld -y foo %t1 %t2 %t -o %t3 | FileCheck -check-prefix=REFLAST %s
+# RUN: ld.lld -y foo %t1 %t2 %t -o /dev/null | FileCheck -check-prefix=REFLAST %s
# REFLAST: trace-symbols.s.tmp1: definition of foo
# REFLAST: trace-symbols.s.tmp2: definition of foo
# REFLAST: trace-symbols.s.tmp: reference to foo
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
# RUN: ld.lld -y foo -y common --trace-symbol=hsymbol \
-# RUN: %t %t2 %t1 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
+# RUN: %t %t2 %t1 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
# RUN: FileCheck -check-prefix=OBJECTD2FOO %s
# OBJECTD2FOO: trace-symbols.s.tmp2: definition of foo
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
# RUN: FileCheck -check-prefix=FOO_AND_COMMON %s
# FOO_AND_COMMON: trace-symbols.s.tmp: reference to foo
# FOO_AND_COMMON: trace-symbols.s.tmp2: definition of foo
# FOO_AND_COMMON: trace-symbols.s.tmp1.a({{.*}}.tmp1): lazy definition of common
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
# RUN: FileCheck -check-prefix=SHLIBDCOMMON %s
# SHLIBDCOMMON: trace-symbols.s.tmp1.so: shared definition of common
-# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o /dev/null | \
# RUN: FileCheck -check-prefix=SHLIBD2FOO %s
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
# RUN: FileCheck -check-prefix=NO-SHLIBD2FOO %s
# SHLIBD2FOO: trace-symbols.s.tmp2.so: shared definition of foo
# NO-SHLIBD2FOO-NOT: trace-symbols.s.tmp2.so: definition of foo
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
# RUN: FileCheck -check-prefix=ARCHIVEDCOMMON %s
# ARCHIVEDCOMMON-NOT: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of \
# common
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
# RUN: FileCheck -check-prefix=ARCHIVED1FOO %s
# ARCHIVED1FOO: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of foo
-# RUN: ld.lld -y foo %t %t1.a %t2.a -o %t3 | \
+# RUN: ld.lld -y foo %t %t1.a %t2.a -o /dev/null | \
# RUN: FileCheck -check-prefix=ARCHIVED2FOO %s
# ARCHIVED2FOO: trace-symbols.s.tmp2.a(trace-symbols.s.tmp2): definition of foo
-# RUN: ld.lld -y bar %t %t1.so %t2.so -o %t3 | \
+# RUN: ld.lld -y bar %t %t1.so %t2.so -o /dev/null | \
# RUN: FileCheck -check-prefix=SHLIBDBAR %s
# SHLIBDBAR: trace-symbols.s.tmp2.so: shared definition of bar
-# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o %t3 | \
+# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o /dev/null | \
# RUN: FileCheck -check-prefix=SHLIBRBAR %s
# SHLIBRBAR: trace-symbols.s.tmp1.so: reference to bar
-# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o %t3 | \
+# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o /dev/null | \
# RUN: FileCheck -check-prefix=STARTLIB %s
# STARTLIB: trace-symbols.s.tmp1: reference to bar
+# RUN: ld.lld -y foo -shared %t2 %t3 -o /dev/null | FileCheck -check-prefix=DYNDBG %s
+# DYNDBG: trace-symbols.s.tmp2: definition of foo
+# DYNDBG: trace-symbols.s.tmp3: reference to foo
+# DYNDBG: trace-symbols.s.tmp3: dynamic debugging reference to foo
+
## Check we do not crash when trying to trace special symbol.
# RUN: ld.lld -trace-symbol=_end %t %t1 %t2 -o /dev/null
diff --git a/llvm/include/llvm/BinaryFormat/ELF.h b/llvm/include/llvm/BinaryFormat/ELF.h
index 46d806e1b9d8ab..6bb387799c4a18 100644
--- a/llvm/include/llvm/BinaryFormat/ELF.h
+++ b/llvm/include/llvm/BinaryFormat/ELF.h
@@ -1810,6 +1810,7 @@ enum : unsigned {
// LLVM-specific notes.
enum {
NT_LLVM_HWASAN_GLOBALS = 3,
+ NT_LLVM_DYNAMIC_DEBUGGING = 4,
};
// GNU note types.
>From 4b6bd4ffa5cb0d04098e119812b288cea433a69e Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 6 Aug 2026 19:35:22 +0100
Subject: [PATCH 02/15] Address review comments and suggestions
---
lld/ELF/Config.h | 2 +-
lld/ELF/Driver.cpp | 37 +++++++++++++++++++++----------------
lld/ELF/InputFiles.cpp | 5 ++---
3 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index de61f3674d4ef8..cd8cbbb09f60df 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -208,8 +208,8 @@ class LinkerDriver {
LinkerDriver(LinkerDriver &) = delete;
void linkerMain(ArrayRef<const char *> args);
void addFile(StringRef path, bool withLOption);
- void addLibrary(StringRef name);
void addFile(std::unique_ptr<ELFFileBase> ef);
+ void addLibrary(StringRef name);
private:
Ctx &ctx;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 7130da9608e610..d54142b0d91e4a 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -298,6 +298,11 @@ void LinkerDriver::addFile(StringRef path, bool withLOption) {
loadFiles();
}
+// Add an ELF input file directly.
+void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
+ files.push_back(std::move(ef));
+}
+
// Add a given library by searching it from input search paths.
void LinkerDriver::addLibrary(StringRef name) {
if (std::optional<std::string> path = searchLibrary(ctx, name))
@@ -307,11 +312,6 @@ void LinkerDriver::addLibrary(StringRef name) {
{name});
}
-// Add an ELF input file directly.
-void LinkerDriver::addFile(std::unique_ptr<ELFFileBase> ef) {
- files.push_back(std::move(ef));
-}
-
// This function is called on startup. We need this for LTO since
// LTO calls LLVM functions to compile bitcode files to native code.
// Technically this can be delayed until we read bitcode files, but
@@ -3249,13 +3249,18 @@ static void postParseObjectFile(ELFFileBase *file) {
}
}
+// Objects that support Dynamic Debugging contain an ELF Dynamic Debugging
+// section that embeds an unoptimized ELF file with debug information. We
+// extract these embedded ELF files to make a consolidated unoptimized
+// relocatable ELF file to embed in an ELF Dynamic Debugging section in the
+// output. See llvm/docs/DynamicDebugging.md for more details.
template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
- Ctx dc;
- LinkerScript script(dc);
- initContext(dc, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
+ Ctx dctx;
+ LinkerScript script(dctx);
+ initContext(dctx, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
ctx.e.exitEarly, ctx.e.disableOutput);
- dc.inDynDbgLink = true;
- dc.dynDbgRelocatable = !ctx.arg.relocatable;
+ dctx.inDynDbgLink = true;
+ dctx.dynDbgRelocatable = !ctx.arg.relocatable;
for (auto *file : ctx.objectFiles) {
auto *obj = cast<ObjFile<ELFT>>(file);
@@ -3263,7 +3268,7 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
auto content = obj->dynDbgSec->content();
MemoryBufferRef mb({(const char *)content.data(), content.size()},
obj->mb.getBufferIdentifier());
- dc.driver.addFile(createObjFile(dc, mb));
+ dctx.driver.addFile(createObjFile(dctx, mb));
}
}
@@ -3271,17 +3276,17 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
return;
std::vector<const char *> args{
- dc.arg.progName.data(), "-r", "-o", "-",
- dc.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
+ dctx.arg.progName.data(), "-r", "-o", "-",
+ dctx.saver.save(Twine("-O") + Twine(ctx.arg.optimize)).data()};
if (ctx.arg.resolveGroups)
args.push_back("--force-group-allocation");
- dc.driver.linkerMain(args);
- if (errCount(dc) > 0 || !dc.dynDbgOutput) {
+ dctx.driver.linkerMain(args);
+ if (errCount(dctx) > 0 || !dctx.dynDbgOutput) {
Err(ctx) << "Failed to create relocatable dynamic debug object";
return;
}
- ctx.dynDbgOutput.swap(dc.dynDbgOutput);
+ ctx.dynDbgOutput.swap(dctx.dynDbgOutput);
}
// Do actual linking. Note that when this function is called,
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 79298baa9c3991..039ac3c7c143a6 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -591,7 +591,6 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
if (LLVM_LIKELY(sec.sh_type == SHT_PROGBITS))
continue;
-
if (LLVM_LIKELY(sec.sh_type == SHT_GROUP)) {
StringRef signature = getShtGroupSignature(objSections, sec);
ArrayRef<Elf_Word> entries =
@@ -851,8 +850,8 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
}
switch (type) {
case SHT_GROUP: {
- // Discard groups for the embedded unoptimized dynamic debugging
- // relocatable link.
+ // Discard groups for non-relocatable links and for embedded unoptimized
+ // dynamic debugging relocatable links.
if (!ctx.arg.relocatable || ctx.dynDbgRelocatable)
sections[i] = &InputSection::discarded;
// Use the verdict parse() recorded for this group instead of repeating
>From 9f2ba1351e365cd6fb28176d8822d81d016a29a7 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Fri, 7 Aug 2026 15:18:47 +0100
Subject: [PATCH 03/15] Restore code for a change that is no longer required
This change was needed for LLVM 21 to avoid use of uninitialised
variables but this is no longer the case.
---
lld/ELF/Config.h | 4 ++--
lld/ELF/Driver.cpp | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index cd8cbbb09f60df..e617f0c1895dac 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -234,8 +234,8 @@ class LinkerDriver {
public:
// See InputFile::groupId.
- uint32_t nextGroupId = 0;
- bool isInGroup = false;
+ uint32_t nextGroupId;
+ bool isInGroup;
std::unique_ptr<InputFile> armCmseImpLib;
SmallVector<std::pair<StringRef, unsigned>, 0> archiveFiles;
};
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index d54142b0d91e4a..cb22b5e7740775 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -2241,6 +2241,8 @@ void LinkerDriver::createFiles(opt::InputArgList &args) {
// Iterate over argv to process input files and positional arguments.
std::optional<MemoryBufferRef> defaultScript;
+ nextGroupId = 0;
+ isInGroup = false;
bool hasInput = false, hasScript = false;
for (auto *arg : args) {
switch (arg->getOption().getID()) {
>From 73e665f4bbc2616a10124cc83568643fe5668db2 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Fri, 7 Aug 2026 16:37:42 +0100
Subject: [PATCH 04/15] Add test comment about relocatable link only symbols
---
lld/test/ELF/dynamic-debug.test | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 19a99619e95e77..2cb0585fbf986e 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -92,6 +92,8 @@ DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
DD-DAG: {{.*}} 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+## The following symbols are only present when linking the relocatable link
+## output of the simulated dynamic debugging objects.
REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS
>From 1e02b4e14675362e880cc882c969ca7dff1237b1 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 11 Aug 2026 16:01:47 +0100
Subject: [PATCH 05/15] Improve dynamic debugging reporting of unresolved
symbols
* Matches more closely to existing LLD unresolved behaviour
* Add some comments
* Add testing
---
lld/ELF/Writer.cpp | 30 ++++-
lld/test/ELF/invalid-dynamic-debug.test | 161 ++++++++++++++++++++++++
2 files changed, 186 insertions(+), 5 deletions(-)
create mode 100644 lld/test/ELF/invalid-dynamic-debug.test
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index bd0d394386aac4..ab6221fd70173f 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -1920,26 +1920,46 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
scanRelocations<ELFT>(ctx);
// Process symbols referenced by the embedded unoptimized part of dynamic
- // debugging.
+ // debugging. Report references to undefined symbols and ensure that
+ // references to shared symbols have PLT/GOT entries as appropriate.
if (ctx.hasDynDbg) {
bool ignoreUnresolved =
(ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
- bool warnOnly = (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+ bool warnUnresolved =
+ (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
for (Symbol *sym : ctx.symtab->getSymbols()) {
if (!sym->isDynDbgRef)
continue;
if (sym->isUndefined()) {
- if (ignoreUnresolved || sym->isWeak())
- continue;
+ bool isWarning;
+
+ // If versioned, issue an error (even if the symbol is weak) because
+ // we don't know the defining filename which is required to construct
+ // a Verneed entry.
+ if (sym->hasVersionSuffix)
+ isWarning = false;
+ else {
+ if (sym->isWeak())
+ continue;
+
+ bool canBeExternal =
+ !sym->isLocal() && sym->visibility() == STV_DEFAULT;
+ if (ignoreUnresolved && canBeExternal)
+ continue;
+
+ isWarning =
+ (warnUnresolved && canBeExternal) || ctx.arg.noinhibitExec;
+ }
+ // Report with actual dynamic debugging input section if available.
static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
ArrayRef<uint8_t>());
ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
InputSectionBase *isec =
dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
ctx.undefErrs.push_back(
- {cast<Undefined>(sym), {{isec, 0}}, warnOnly});
+ {cast<Undefined>(sym), {{isec, 0}}, isWarning});
continue;
}
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
new file mode 100644
index 00000000000000..f1297462036fa2
--- /dev/null
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -0,0 +1,161 @@
+REQUIRES: x86
+
+## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
+## of various invalid situations.
+
+RUN: rm -rf %t && split-file %s %t
+
+## Create the "outer" optimized objects.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
+## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+
+## Create object without dynamic debugging.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/no-dyndbg.s -o %t/no-dyndbg.o
+
+## Default link.
+RUN: not ld.lld -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+CHECK: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+CHECK-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+CHECK-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
+CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-EMPTY:
+
+## Default link with reverse input file order.
+RUN: not ld.lld -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+REVERSE: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+REVERSE-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
+REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-EMPTY:
+
+## Links with `--warn-unresolved-symbols`.
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+## Links with `--noinhibit-exec`.
+RUN: not ld.lld --noinhibit-exec -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --noinhibit-exec -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+## Links with `--unresolved-symbols=ignore-all`.
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="%t/main.o" --check-prefix=ERROR \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="%t/foo.o" --check-prefix=ERROR \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+ERROR: error: undefined hidden symbol: undef_hidden
+ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+ERROR-EMPTY:
+ERROR-NEXT: error: undefined symbol: undef_weak_ver at v1
+ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+
+#--- main.s
+# Optimized version of "main"
+
+.global main
+.type main, at function
+.section .text.main,"ax", at progbits
+main:
+ ret
+
+#--- main-unopt.s
+# Unoptimized version of "main"
+
+.weak undef_weak
+.weak undef_weak_ver
+.symver undef_weak_ver, undef_weak_ver at v1
+
+.hidden undef_hidden
+
+.global __dyndbg.main
+.hidden __dyndbg.main
+.type __dyndbg.main, at function
+.section .text.__dyndbg.main,"ax", at progbits
+__dyndbg.main:
+ call undef_inner
+ call undef_weak
+ call undef_weak_ver
+ call undef_hidden
+ ret
+
+#--- foo.s
+# Optimized version of "foo"
+
+.global foo
+.type foo, at function
+.section .text.foo,"ax", at progbits
+foo:
+ ret
+
+#--- foo-unopt.s
+# Unoptimized version of "foo"
+
+.weak undef_weak
+.weak undef_weak_ver
+.symver undef_weak_ver, undef_weak_ver at v1
+
+.hidden undef_hidden
+
+.global __dyndbg.foo
+.hidden __dyndbg.foo
+.type __dyndbg.foo, at function
+.section .text.__dyndbg.foo,"ax", at progbits
+__dyndbg.foo:
+ call undef_inner
+ call undef_weak
+ call undef_weak_ver
+ call undef_hidden
+ call undef_no_dyndbg_inner
+ ret
+
+#--- no-dyndbg.s
+# No dynamic debugging
+
+.global bar
+.type bar, at function
+.section .text.bar,"ax", at progbits
+bar:
+ call undef_no_dyndbg_inner
+ ret
>From db8e2adf8268699dd4eaa9353f245bfd2940c530 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Thu, 3 Sep 2026 00:10:29 -0700
Subject: [PATCH 06/15] Simplify dynamic debugging and reject malformed
embedded objects
Define elf::maybeReportUndefined and call it for dynamic debugging references.
Read the embedded object with llvm::object::ELFFile accessors.
Move the ICF override out of the per-section parse loop, drop numNestedCtx in
favour of comparing against lctx, and derive the note header size from the
name.
---
lld/Common/CommonLinkerContext.cpp | 16 +---
lld/ELF/Driver.cpp | 40 +++++----
lld/ELF/InputFiles.cpp | 134 +++++++++++++----------------
lld/ELF/InputFiles.h | 1 +
lld/ELF/Relocations.cpp | 15 ++--
lld/ELF/SyntheticSections.cpp | 12 +--
lld/ELF/Writer.cpp | 43 +++------
7 files changed, 115 insertions(+), 146 deletions(-)
diff --git a/lld/Common/CommonLinkerContext.cpp b/lld/Common/CommonLinkerContext.cpp
index cafd78df4a2ba5..f883c4cbd96424 100644
--- a/lld/Common/CommonLinkerContext.cpp
+++ b/lld/Common/CommonLinkerContext.cpp
@@ -22,13 +22,10 @@ using namespace lld;
// state.
static CommonLinkerContext *lctx;
-static uint32_t numNestedCtx;
-
CommonLinkerContext::CommonLinkerContext() {
- if (lctx) {
- ++numNestedCtx;
+ // A nested context, e.g. the dynamic debugging link, does not install itself.
+ if (lctx)
return;
- }
lctx = this;
// Fire off the static initializations in CGF's constructor.
@@ -42,13 +39,8 @@ CommonLinkerContext::~CommonLinkerContext() {
for (auto &it : instances)
it.second->~SpecificAllocBase();
- if (numNestedCtx) {
- assert(lctx != this);
- --numNestedCtx;
- return;
- }
-
- lctx = nullptr;
+ if (lctx == this)
+ lctx = nullptr;
}
CommonLinkerContext &lld::commonContext() {
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index cb22b5e7740775..84449eb9beb41c 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -113,19 +113,15 @@ llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
return {filename, ec, flags};
}
-static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0,
- llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly,
- bool disableOutput) {
- ErrorHandler &e = ctx.e;
- e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
- e.logName = args::getFilenameWithoutExe(arg0);
- e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
- "--error-limit=0 to see all errors)";
+// Set up the parts of Ctx that both the top-level link and the nested dynamic
+// debugging link need. The caller initializes ctx.e beforehand.
+static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0) {
+ ctx.e.logName = args::getFilenameWithoutExe(arg0);
+ ctx.e.errorLimitExceededMsg = "too many errors emitted, stopping now (use "
+ "--error-limit=0 to see all errors)";
ctx.script = &script;
ctx.symAux.emplace_back();
ctx.symtab = std::make_unique<SymbolTable>(ctx);
-
ctx.arg.progName = arg0;
}
@@ -137,8 +133,8 @@ bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
auto *context = new Ctx;
Ctx &ctx = *context;
LinkerScript script(ctx);
- initContext(ctx, script, args[0], stdoutOS, stderrOS, exitEarly,
- disableOutput);
+ ctx.e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
+ initContext(ctx, script, args[0]);
ctx.driver.linkerMain(args);
@@ -3259,16 +3255,16 @@ static void postParseObjectFile(ELFFileBase *file) {
template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
Ctx dctx;
LinkerScript script(dctx);
- initContext(dctx, script, ctx.arg.progName, ctx.e.outs(), ctx.e.errs(),
- ctx.e.exitEarly, ctx.e.disableOutput);
+ dctx.e.initialize(ctx.e.outs(), ctx.e.errs(), ctx.e.exitEarly,
+ ctx.e.disableOutput);
+ initContext(dctx, script, ctx.arg.progName);
dctx.inDynDbgLink = true;
dctx.dynDbgRelocatable = !ctx.arg.relocatable;
for (auto *file : ctx.objectFiles) {
auto *obj = cast<ObjFile<ELFT>>(file);
if (obj->dynDbgSec) {
- auto content = obj->dynDbgSec->content();
- MemoryBufferRef mb({(const char *)content.data(), content.size()},
+ MemoryBufferRef mb(toStringRef(obj->dynDbgSec->content()),
obj->mb.getBufferIdentifier());
dctx.driver.addFile(createObjFile(dctx, mb));
}
@@ -3284,11 +3280,11 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
args.push_back("--force-group-allocation");
dctx.driver.linkerMain(args);
if (errCount(dctx) > 0 || !dctx.dynDbgOutput) {
- Err(ctx) << "Failed to create relocatable dynamic debug object";
+ Err(ctx) << "failed to create relocatable dynamic debug object";
return;
}
- ctx.dynDbgOutput.swap(dctx.dynDbgOutput);
+ ctx.dynDbgOutput = std::move(dctx.dynDbgOutput);
}
// Do actual linking. Note that when this function is called,
@@ -3310,6 +3306,14 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &args) {
parseFiles(ctx, files);
+ // ICF is incompatible with dynamic debugging: the inner ELF references outer
+ // symbols that folding would merge away.
+ if (ctx.hasDynDbg && ctx.arg.icf != ICFLevel::None) {
+ Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
+ "debugging";
+ ctx.arg.icf = ICFLevel::None;
+ }
+
// Create dynamic sections for dynamic linking and static PIE.
ctx.hasDynsym = !ctx.sharedFiles.empty() || ctx.arg.isPic;
ctx.arg.exportDynamic &= ctx.hasDynsym;
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 039ac3c7c143a6..069ecc973f9616 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -644,21 +644,10 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) {
}
if (sec.sh_type == SHT_LLVM_DYNDBG_ELF) {
- // Check for presence of dynamic debugging section and create the input
- // section but mark for discard.
- StringRef name = check(obj.getSectionName(sec, shstrtab));
- if (name == dynDbgSecName) {
+ if (check(obj.getSectionName(sec, shstrtab)) == dynDbgSecName) {
sections[i] = &InputSection::discarded;
- dynDbgSec = std::make_unique<InputSection>(*this, sec, name);
+ dynDbgSec = std::make_unique<InputSection>(*this, sec, dynDbgSecName);
ctx.hasDynDbg = true;
-
- // If ICF is enabled, warn and disable it because it's incompatible with
- // dynamic debugging.
- if (ctx.arg.icf != ICFLevel::None) {
- Warn(ctx) << "ICF disabled because it is incompatible with dynamic "
- "debugging";
- ctx.arg.icf = ICFLevel::None;
- }
}
continue;
}
@@ -1272,69 +1261,68 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
sym->referenced = true;
}
- // Process the undefined symbols of the "inner" embedded unoptimized dynamic
- // debugging object to ensure that the "outer" ELF contains the dependencies
- // of the "inner" ELF. Also tag which "outer" global symbols are dynamic
- // debugging references, i.e. used in an "inner" relocation for a SHT_PROGBITS
- // and SHF_ALLOC section, via the `isDynDbgRef` flag.
- if (dynDbgSec) {
- auto content = dynDbgSec->content();
- MemoryBufferRef dbgMb({(const char *)content.data(), content.size()},
- mb.getBufferIdentifier());
- std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
- ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(efb.get());
- if (dbgObj) {
- SmallVector<bool, 0> globalUsed(dbgObj->numSymbols - dbgObj->firstGlobal);
- ArrayRef<Elf_Shdr> shdrs = dbgObj->template getELFShdrs<ELFT>();
- for (size_t i = 0, end = shdrs.size(); i != end; ++i) {
- const Elf_Shdr &sh = shdrs[i];
- if (!isStaticRelSecType(sh.sh_type))
- continue;
-
- const Elf_Shdr &target = shdrs[sh.sh_info];
- if (target.sh_type != SHT_PROGBITS ||
- (target.sh_flags & SHF_ALLOC) == 0)
- continue;
-
- auto setSymUsed = [&,
- firstGlobal = dbgObj->firstGlobal](uint32_t symIdx) {
- if (symIdx >= firstGlobal)
- globalUsed[symIdx - firstGlobal] = true;
- };
-
- auto isec = std::make_unique<InputSection>(*dbgObj, sh, StringRef());
- isec->relSecIdx = i;
- auto relocs = isec->template relsOrRelas<ELFT>(/*supportsCrel=*/false);
- if (relocs.areRelocsRel()) {
- for (auto const &r : relocs.rels)
- setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
- } else {
- for (auto const &r : relocs.relas)
- setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
- }
- }
+ if (dynDbgSec)
+ initDynDbgSymbols();
+}
- ArrayRef<Elf_Sym> dbgSyms = dbgObj->template getGlobalELFSyms<ELFT>();
- assert(dbgSyms.size() == globalUsed.size());
- for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
- const Elf_Sym &s = dbgSyms[i];
- if (s.st_shndx != SHN_UNDEF)
- continue;
+// Add the undefined symbols of the embedded unoptimized dynamic debugging
+// object so that the outer link resolves the inner link's dependencies. Tag
+// those reached by an inner relocation against a SHT_PROGBITS SHF_ALLOC
+// section with `isDynDbgRef`; the rest are only needed by debug sections.
+template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
+ MemoryBufferRef dbgMb(toStringRef(dynDbgSec->content()),
+ mb.getBufferIdentifier());
+ std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
+ // Compare ekind (note ObjFile<ELFT>::classof only tests InputFile::kind()).
+ if (efb->ekind != ekind) {
+ Err(ctx) << this << ": " << dynDbgSecName
+ << " contains an incompatible ELF type";
+ return;
+ }
+ auto &dbgObj = cast<ObjFile<ELFT>>(*efb);
+ const object::ELFFile<ELFT> obj = dbgObj.getObj();
+
+ ArrayRef<Elf_Sym> dbgSyms = dbgObj.template getGlobalELFSyms<ELFT>();
+ SmallVector<bool, 0> globalUsed(dbgSyms.size());
+ auto setSymUsed = [&, firstGlobal = dbgObj.firstGlobal](uint32_t symIdx) {
+ if (symIdx >= firstGlobal)
+ globalUsed[symIdx - firstGlobal] = true;
+ };
- StringRef name = CHECK2(s.getName(dbgObj->stringTable), this);
- Symbol *sym = symtab->addSymbol(
- Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
- sym->isUsedInRegularObj = true;
- sym->referenced = true;
- if (globalUsed[i]) {
- sym->isDynDbgRef = true;
- if (sym->traced)
- Msg(ctx) << this << ": dynamic debugging reference to " << name;
- }
- }
+ for (const Elf_Shdr &sh : dbgObj.template getELFShdrs<ELFT>()) {
+ if (!isStaticRelSecType(sh.sh_type))
+ continue;
+ const Elf_Shdr &target = *CHECK2(obj.getSection(sh.sh_info), &dbgObj);
+ if (target.sh_type != SHT_PROGBITS || !(target.sh_flags & SHF_ALLOC))
+ continue;
+ if (sh.sh_type == SHT_CREL) {
+ auto [rels, relas] = CHECK2(obj.crels(sh), &dbgObj);
+ for (const Elf_Rel &r : rels)
+ setSymUsed(r.getSymbol(false));
+ for (const Elf_Rela &r : relas)
+ setSymUsed(r.getSymbol(false));
+ } else if (sh.sh_type == SHT_RELA) {
+ for (const Elf_Rela &r : CHECK2(obj.relas(sh), &dbgObj))
+ setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
} else {
- Err(ctx) << this << ": " << dynDbgSecName
- << " contains an incompatible ELF type";
+ for (const Elf_Rel &r : CHECK2(obj.rels(sh), &dbgObj))
+ setSymUsed(r.getSymbol(ctx.arg.isMips64EL));
+ }
+ }
+
+ for (size_t i = 0, end = dbgSyms.size(); i != end; ++i) {
+ const Elf_Sym &s = dbgSyms[i];
+ if (s.st_shndx != SHN_UNDEF)
+ continue;
+ StringRef name = CHECK2(s.getName(dbgObj.stringTable), this);
+ Symbol *sym = ctx.symtab->addSymbol(
+ Undefined{this, name, s.getBinding(), s.st_other, s.getType()});
+ sym->isUsedInRegularObj = true;
+ sym->referenced = true;
+ if (globalUsed[i]) {
+ sym->isDynDbgRef = true;
+ if (sym->traced)
+ Msg(ctx) << this << ": dynamic debugging reference to " << name;
}
}
}
diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h
index 22c29f9ea7909f..50ec881f05b65b 100644
--- a/lld/ELF/InputFiles.h
+++ b/lld/ELF/InputFiles.h
@@ -297,6 +297,7 @@ template <class ELFT> class ObjFile : public ELFFileBase {
void initializeSections(bool ignoreComdats,
const llvm::object::ELFFile<ELFT> &obj);
void initializeSymbols(const llvm::object::ELFFile<ELFT> &obj);
+ void initDynDbgSymbols();
void initializeJustSymbols();
InputSectionBase *getRelocTarget(uint32_t idx, uint32_t info);
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 7aaae802f4d7ca..4864f3410d4411 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -654,12 +654,12 @@ void elf::reportUndefinedSymbols(Ctx &ctx) {
// Report an undefined symbol if necessary.
// Returns true if the undefined symbol will produce an error message.
-bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
- std::lock_guard<std::mutex> lock(ctx.relocMutex);
+bool elf::maybeReportUndefined(Ctx &ctx, Undefined &sym, InputSectionBase &sec,
+ uint64_t offset) {
// If versioned, issue an error (even if the symbol is weak) because we don't
// know the defining filename which is required to construct a Verneed entry.
if (sym.hasVersionSuffix) {
- ctx.undefErrs.push_back({&sym, {{sec, offset}}, false});
+ ctx.undefErrs.push_back({&sym, {{&sec, offset}}, false});
return true;
}
if (sym.isWeak())
@@ -678,16 +678,21 @@ bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
// PPC32 .got2 is similar but cannot be fixed. Multiple .got2 is infeasible
// because .LC0-.LTOC is not representable if the two labels are in different
// .got2
- if (sym.discardedSecIdx != 0 && (sec->name == ".got2" || sec->name == ".toc"))
+ if (sym.discardedSecIdx != 0 && (sec.name == ".got2" || sec.name == ".toc"))
return false;
bool isWarning =
(ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn && canBeExternal) ||
ctx.arg.noinhibitExec;
- ctx.undefErrs.push_back({&sym, {{sec, offset}}, isWarning});
+ ctx.undefErrs.push_back({&sym, {{&sec, offset}}, isWarning});
return !isWarning;
}
+bool RelocScan::maybeReportUndefined(Undefined &sym, uint64_t offset) {
+ std::lock_guard<std::mutex> lock(ctx.relocMutex);
+ return elf::maybeReportUndefined(ctx, sym, *sec, offset);
+}
+
bool RelocScan::checkTlsLe(uint64_t offset, Symbol &sym, RelType type) {
if (!ctx.arg.shared)
return false;
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index d3d9889dbbf0b6..fef8dd30eb0d21 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4431,20 +4431,22 @@ void DynamicDebugSection::writeTo(uint8_t *buf) {
ctx.dynDbgOutput->getBufferSize());
}
+constexpr char dynDbgNoteName[] = "LLVM";
+
DynamicDebugNote::DynamicDebugNote(Ctx &ctx)
: SyntheticSection(ctx, ".note.llvm.dyndbg", SHT_NOTE, 0, 4) {}
size_t DynamicDebugNote::getSize() const {
- return /*hdrsz=*/12 + /*namesz=*/8 + /*descsz=*/sizeof(uint32_t);
+ return sizeof(llvm::ELF::Elf64_Nhdr) + alignTo(sizeof(dynDbgNoteName), 4) +
+ /*descsz=*/sizeof(uint32_t);
}
void DynamicDebugNote::writeTo(uint8_t *buf) {
- write32(ctx, buf, 5); // Name size
+ write32(ctx, buf, sizeof(dynDbgNoteName)); // Name size
write32(ctx, buf + 4, sizeof(uint32_t)); // Content size
write32(ctx, buf + 8, NT_LLVM_DYNAMIC_DEBUGGING); // Type
- memcpy(buf + 12, "LLVM", 5); // Name string
- uint32_t version = 0;
- write32(ctx, buf + 20, version);
+ memcpy(buf + 12, dynDbgNoteName, sizeof(dynDbgNoteName));
+ write32(ctx, buf + 12 + alignTo(sizeof(dynDbgNoteName), 4), 0); // Version
}
static OutputSection *findSection(Ctx &ctx, StringRef name) {
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index ab6221fd70173f..e2792dc31d031c 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -371,7 +371,7 @@ template <class ELFT> void Writer<ELFT>::run() {
// link()) instead of committing the buffer, which would write to the
// process's stdout.
if (ctx.inDynDbgLink)
- ctx.dynDbgOutput.swap(buffer);
+ ctx.dynDbgOutput = std::move(buffer);
else if (ctx.arg.outputFile == "-") {
ctx.e.outs() << StringRef(
reinterpret_cast<const char *>(buffer->getBufferStart()),
@@ -1923,43 +1923,20 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
// debugging. Report references to undefined symbols and ensure that
// references to shared symbols have PLT/GOT entries as appropriate.
if (ctx.hasDynDbg) {
- bool ignoreUnresolved =
- (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Ignore);
- bool warnUnresolved =
- (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn);
+ InputSection *unknownSec = make<InputSection>(
+ ctx.internalFile, dynDbgSecName, 0, 0, 0, 0, ArrayRef<uint8_t>());
for (Symbol *sym : ctx.symtab->getSymbols()) {
if (!sym->isDynDbgRef)
continue;
if (sym->isUndefined()) {
- bool isWarning;
-
- // If versioned, issue an error (even if the symbol is weak) because
- // we don't know the defining filename which is required to construct
- // a Verneed entry.
- if (sym->hasVersionSuffix)
- isWarning = false;
- else {
- if (sym->isWeak())
- continue;
-
- bool canBeExternal =
- !sym->isLocal() && sym->visibility() == STV_DEFAULT;
- if (ignoreUnresolved && canBeExternal)
- continue;
-
- isWarning =
- (warnUnresolved && canBeExternal) || ctx.arg.noinhibitExec;
- }
-
- // Report with actual dynamic debugging input section if available.
- static InputSection dummy(ctx.internalFile, dynDbgSecName, 0, 0, 0, 0,
- ArrayRef<uint8_t>());
- ObjFile<ELFT> *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
- InputSectionBase *isec =
- dbgObj && dbgObj->dynDbgSec ? dbgObj->dynDbgSec.get() : &dummy;
- ctx.undefErrs.push_back(
- {cast<Undefined>(sym), {{isec, 0}}, isWarning});
+ // Report against the referencing dynamic debugging section when the
+ // symbol's file has one.
+ auto *dbgObj = dyn_cast<ObjFile<ELFT>>(sym->file);
+ InputSectionBase *isec = dbgObj && dbgObj->dynDbgSec
+ ? dbgObj->dynDbgSec.get()
+ : unknownSec;
+ maybeReportUndefined(ctx, cast<Undefined>(*sym), *isec, 0);
continue;
}
>From 1540e55aff0eb61cabda3a046bb6181d7a271d08 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 3 Sep 2026 14:24:14 +0100
Subject: [PATCH 07/15] Change testing to address review comments
* Use `cd %t` to simplify "invalid-dynamic-debug.test"
* Create separate trace symbol test for dynamic debugging and restore
the existing "trace-symbols.s" test
---
.../ELF/Inputs/trace-symbols-dyndbg-opt.s | 4 --
.../ELF/Inputs/trace-symbols-dyndbg-unopt.s | 5 --
lld/test/ELF/invalid-dynamic-debug.test | 70 +++++++++----------
lld/test/ELF/trace-symbols-dynamic-debug.test | 38 ++++++++++
lld/test/ELF/trace-symbols.s | 51 +++++---------
5 files changed, 92 insertions(+), 76 deletions(-)
delete mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
delete mode 100644 lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
create mode 100644 lld/test/ELF/trace-symbols-dynamic-debug.test
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
deleted file mode 100644
index 1fba360bb54aba..00000000000000
--- a/lld/test/ELF/Inputs/trace-symbols-dyndbg-opt.s
+++ /dev/null
@@ -1,4 +0,0 @@
-.globl baz
-.type baz, @function
-baz:
-nop
diff --git a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s b/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
deleted file mode 100644
index 7688e526340d96..00000000000000
--- a/lld/test/ELF/Inputs/trace-symbols-dyndbg-unopt.s
+++ /dev/null
@@ -1,5 +0,0 @@
-.globl __dyndbg.baz
-.hidden __dyndbg.baz
-.type __dyndbg.baz, @function
-__dyndbg.baz:
-callq foo
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
index f1297462036fa2..02fbfeae3a66dd 100644
--- a/lld/test/ELF/invalid-dynamic-debug.test
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -3,86 +3,86 @@ REQUIRES: x86
## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
## of various invalid situations.
-RUN: rm -rf %t && split-file %s %t
+RUN: rm -rf %t && split-file %s %t && cd %t
## Create the "outer" optimized objects.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main.s -o main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo.s -o foo.o
## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main-unopt.s -o main-unopt.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo-unopt.s -o foo-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=main-unopt.o main.o
RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 main.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=foo-unopt.o foo.o
RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
## Create object without dynamic debugging.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/no-dyndbg.s -o %t/no-dyndbg.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux no-dyndbg.s -o no-dyndbg.o
## Default link.
-RUN: not ld.lld -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
CHECK: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-CHECK-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
-CHECK-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
+CHECK-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
CHECK-EMPTY:
CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
CHECK-EMPTY:
CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
CHECK-EMPTY:
CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
-CHECK-NEXT: >>> referenced by [[DIR]]/main.o:(.debug_llvm_dyndbg+0x0)
+CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
CHECK-EMPTY:
## Default link with reverse input file order.
-RUN: not ld.lld -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
REVERSE: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-REVERSE-NEXT: >>> referenced by [[DIR]]/no-dyndbg.o:(.text.bar+0x1)
+REVERSE-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
REVERSE-EMPTY:
REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
REVERSE-EMPTY:
REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
REVERSE-EMPTY:
REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
-REVERSE-NEXT: >>> referenced by [[DIR]]/foo.o:(.debug_llvm_dyndbg+0x0)
+REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
REVERSE-EMPTY:
## Links with `--warn-unresolved-symbols`.
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" \
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--noinhibit-exec`.
-RUN: not ld.lld --noinhibit-exec -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" \
+RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --noinhibit-exec -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning -DDIR="%t" --check-prefix=REVERSE \
+RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--unresolved-symbols=ignore-all`.
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/main.o %t/foo.o %t/no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="%t/main.o" --check-prefix=ERROR \
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null %t/no-dyndbg.o %t/foo.o %t/main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="%t/foo.o" --check-prefix=ERROR \
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
+RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
ERROR: error: undefined hidden symbol: undef_hidden
diff --git a/lld/test/ELF/trace-symbols-dynamic-debug.test b/lld/test/ELF/trace-symbols-dynamic-debug.test
new file mode 100644
index 00000000000000..b0d20bf904adcc
--- /dev/null
+++ b/lld/test/ELF/trace-symbols-dynamic-debug.test
@@ -0,0 +1,38 @@
+REQUIRES: x86
+
+## Test dynamic debugging `-trace-symbol=<symbol>`
+
+RUN: rm -rf %t && split-file %s %t
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+
+## Create dynamic debugging style object.
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar.s -o %t/bar.o
+RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar-unopt.s -o %t/bar-unopt.o
+RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/bar-unopt.o %t/bar.o
+RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/bar.o
+
+# RUN: ld.lld -trace-symbol=foo -shared %t/foo.o %t/bar.o -o /dev/null | FileCheck %s
+# CHECK: foo.o: definition of foo
+# CHECK: bar.o: reference to foo
+# CHECK: bar.o: dynamic debugging reference to foo
+
+#--- foo.s
+.globl foo
+.type foo, at function
+foo:
+ ret
+
+#--- bar.s
+.globl bar
+.type bar, at function
+bar:
+ ret
+
+#--- bar-unopt.s
+.globl __dyndbg.baz
+.hidden __dyndbg.baz
+.type __dyndbg.baz, at function
+__dyndbg.baz:
+ call foo
+ ret
diff --git a/lld/test/ELF/trace-symbols.s b/lld/test/ELF/trace-symbols.s
index ae17899588d315..785414e2bd5b54 100644
--- a/lld/test/ELF/trace-symbols.s
+++ b/lld/test/ELF/trace-symbols.s
@@ -3,23 +3,15 @@
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
+# RUN: %p/Inputs/trace-symbols-foo-weak.s -o %t1
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
+# RUN: %p/Inputs/trace-symbols-foo-strong.s -o %t2
# RUN: ld.lld -shared %t1 -o %t1.so
# RUN: ld.lld -shared %t2 -o %t2.so
# RUN: rm -f %t1.a
# RUN: llvm-ar rcs %t1.a %t1
# RUN: rm -f %t2.a
# RUN: llvm-ar rcs %t2.a %t2
-## Create dynamic debugging style object.
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-dyndbg-opt.s -o %t3
-# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux \
-# RUN: %p/Inputs/trace-symbols-dyndbg-unopt.s -o %t-unopt
-# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t-unopt %t3
-# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t3
# RUN: ld.lld -y foo -shared %t1 %t1.so -o /dev/null | \
# RUN: FileCheck --check-prefix=PREEMPT %s --implicit-check-not=foo
@@ -27,80 +19,75 @@
# PREEMPT-NEXT: trace-symbols.s.tmp1.so: shared definition of foo
# RUN: ld.lld -y foo -trace-symbol common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTRFOO %s
+# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTRFOO %s
# OBJECTRFOO: trace-symbols.s.tmp: reference to foo
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTDCOMMON %s
+# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTDCOMMON %s
# OBJECTDCOMMON: trace-symbols.s.tmp1: common definition of common
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD1FOO %s
+# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD1FOO %s
# OBJECTD1FOO: trace-symbols.s.tmp: reference to foo
# OBJECTD1FOO: trace-symbols.s.tmp1: common definition of common
# OBJECTD1FOO: trace-symbols.s.tmp1: definition of foo
# OBJECTD1FOO: trace-symbols.s.tmp2: definition of foo
-# RUN: ld.lld -y foo %t1 %t2 %t -o /dev/null | FileCheck -check-prefix=REFLAST %s
+# RUN: ld.lld -y foo %t1 %t2 %t -o %t3 | FileCheck -check-prefix=REFLAST %s
# REFLAST: trace-symbols.s.tmp1: definition of foo
# REFLAST: trace-symbols.s.tmp2: definition of foo
# REFLAST: trace-symbols.s.tmp: reference to foo
# RUN: ld.lld -y foo -trace-symbol=common -trace-symbol=hsymbol \
-# RUN: %t %t1 %t2 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: %t %t1 %t2 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
# RUN: ld.lld -y foo -y common --trace-symbol=hsymbol \
-# RUN: %t %t2 %t1 -o /dev/null | FileCheck -check-prefix=OBJECTD2FOO %s
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
+# RUN: %t %t2 %t1 -o %t3 | FileCheck -check-prefix=OBJECTD2FOO %s
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
# RUN: FileCheck -check-prefix=OBJECTD2FOO %s
# OBJECTD2FOO: trace-symbols.s.tmp2: definition of foo
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
# RUN: FileCheck -check-prefix=FOO_AND_COMMON %s
# FOO_AND_COMMON: trace-symbols.s.tmp: reference to foo
# FOO_AND_COMMON: trace-symbols.s.tmp2: definition of foo
# FOO_AND_COMMON: trace-symbols.s.tmp1.a({{.*}}.tmp1): lazy definition of common
-# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t1.so %t2 -o %t3 | \
# RUN: FileCheck -check-prefix=SHLIBDCOMMON %s
# SHLIBDCOMMON: trace-symbols.s.tmp1.so: shared definition of common
-# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2.so %t1.so -o %t3 | \
# RUN: FileCheck -check-prefix=SHLIBD2FOO %s
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
# RUN: FileCheck -check-prefix=NO-SHLIBD2FOO %s
# SHLIBD2FOO: trace-symbols.s.tmp2.so: shared definition of foo
# NO-SHLIBD2FOO-NOT: trace-symbols.s.tmp2.so: definition of foo
-# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o /dev/null | \
+# RUN: ld.lld -y foo -y common %t %t2 %t1.a -o %t3 | \
# RUN: FileCheck -check-prefix=ARCHIVEDCOMMON %s
# ARCHIVEDCOMMON-NOT: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of \
# common
-# RUN: ld.lld -y foo %t %t1.a %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.so -o %t3 | \
# RUN: FileCheck -check-prefix=ARCHIVED1FOO %s
# ARCHIVED1FOO: trace-symbols.s.tmp1.a(trace-symbols.s.tmp1): definition of foo
-# RUN: ld.lld -y foo %t %t1.a %t2.a -o /dev/null | \
+# RUN: ld.lld -y foo %t %t1.a %t2.a -o %t3 | \
# RUN: FileCheck -check-prefix=ARCHIVED2FOO %s
# ARCHIVED2FOO: trace-symbols.s.tmp2.a(trace-symbols.s.tmp2): definition of foo
-# RUN: ld.lld -y bar %t %t1.so %t2.so -o /dev/null | \
+# RUN: ld.lld -y bar %t %t1.so %t2.so -o %t3 | \
# RUN: FileCheck -check-prefix=SHLIBDBAR %s
# SHLIBDBAR: trace-symbols.s.tmp2.so: shared definition of bar
-# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o /dev/null | \
+# RUN: ld.lld -y foo -y bar %t %t1.so %t2.so -o %t3 | \
# RUN: FileCheck -check-prefix=SHLIBRBAR %s
# SHLIBRBAR: trace-symbols.s.tmp1.so: reference to bar
-# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o /dev/null | \
+# RUN: ld.lld -y foo -y bar %t -u bar --start-lib %t1 %t2 --end-lib -o %t3 | \
# RUN: FileCheck -check-prefix=STARTLIB %s
# STARTLIB: trace-symbols.s.tmp1: reference to bar
-# RUN: ld.lld -y foo -shared %t2 %t3 -o /dev/null | FileCheck -check-prefix=DYNDBG %s
-# DYNDBG: trace-symbols.s.tmp2: definition of foo
-# DYNDBG: trace-symbols.s.tmp3: reference to foo
-# DYNDBG: trace-symbols.s.tmp3: dynamic debugging reference to foo
-
## Check we do not crash when trying to trace special symbol.
# RUN: ld.lld -trace-symbol=_end %t %t1 %t2 -o /dev/null
>From d97fee9c5771a1df6e2552be7145879763c52c3f Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Fri, 4 Sep 2026 19:01:35 +0100
Subject: [PATCH 08/15] Prefer pipe with continuation `| \` at end of previous
line
---
lld/test/ELF/dynamic-debug.test | 8 ++---
lld/test/ELF/invalid-dynamic-debug.test | 48 ++++++++++++-------------
2 files changed, 28 insertions(+), 28 deletions(-)
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 2cb0585fbf986e..32ac1d140ce85d 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -150,11 +150,11 @@ IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
IN: There are no section groups in this file.
## Check disabling of incompatible ICF options.
-RUN: ld.lld -e main --icf=all -o %t/out-dd-icf-all %t/main.o %t/foo.o %t/shared.so 2>&1 \
-RUN: | FileCheck --check-prefix=ICF-WARN %s
+RUN: ld.lld -e main --icf=all -o %t/out-dd-icf-all %t/main.o %t/foo.o %t/shared.so 2>&1 | \
+RUN: FileCheck --check-prefix=ICF-WARN %s
RUN: cmp %t/out-dd-icf-all %t/out-dd
-RUN: ld.lld -e main --icf=safe -o %t/out-dd-icf-safe %t/main.o %t/foo.o %t/shared.so 2>&1 \
-RUN: | FileCheck --check-prefix=ICF-WARN %s
+RUN: ld.lld -e main --icf=safe -o %t/out-dd-icf-safe %t/main.o %t/foo.o %t/shared.so 2>&1 | \
+RUN: FileCheck --check-prefix=ICF-WARN %s
RUN: cmp %t/out-dd-icf-safe %t/out-dd
ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
index 02fbfeae3a66dd..42b02e74b65929 100644
--- a/lld/test/ELF/invalid-dynamic-debug.test
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -24,9 +24,9 @@ RUN: --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux no-dyndbg.s -o no-dyndbg.o
## Default link.
-RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
CHECK: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
CHECK-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
@@ -43,9 +43,9 @@ CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
CHECK-EMPTY:
## Default link with reverse input file order.
-RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
REVERSE: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
REVERSE-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
@@ -62,28 +62,28 @@ REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
REVERSE-EMPTY:
## Links with `--warn-unresolved-symbols`.
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--noinhibit-exec`.
-RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--unresolved-symbols=ignore-all`.
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 \
-RUN: | FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+RUN: FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
+RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
ERROR: error: undefined hidden symbol: undef_hidden
ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
>From 591663a071adc6a5db28ad0261e2f7222b8d1c1a Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 8 Sep 2026 17:36:21 +0100
Subject: [PATCH 09/15] Simplify the tests and consolidate their style
Add test coverage relating to `--gc-sections`.
---
lld/test/ELF/dynamic-debug.test | 252 +++++++-----------
lld/test/ELF/invalid-dynamic-debug.test | 150 +++++------
lld/test/ELF/trace-symbols-dynamic-debug.test | 26 +-
3 files changed, 189 insertions(+), 239 deletions(-)
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 32ac1d140ce85d..111c2f6f5208c2 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -1,184 +1,142 @@
-REQUIRES: x86
-
+# REQUIRES: x86
## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
## of various specific situations that can arise with such objects. These are mainly
## related to dependencies from the "inner" unoptimized objects.
-RUN: rm -rf %t && split-file %s %t
+# RUN: rm -rf %t && split-file %s %t && cd %t
## Create the "outer" optimized objects.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main.s -o %t/main.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 main.s -o main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo.s -o foo.o
## Link only the "outer" optimized objects using `--gc-sections`.
-RUN: ld.lld -e main --gc-sections -o %t/out %t/main.o %t/foo.o
-RUN: llvm-readelf -s %t/out | FileCheck %s
-
-CHECK: Symbol table '.symtab' contains 12 entries:
-CHECK-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
-CHECK-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
-CHECK-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.main
-CHECK-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.main
-CHECK-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
-CHECK-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
-CHECK-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.foo
-CHECK-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
-CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] func
-CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] main
-CHECK-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] foo
+# RUN: ld.lld -e main --gc-sections -o out.nodd main.o foo.o
+# RUN: llvm-readelf -s out.nodd | FileCheck --check-prefix=NO-DD %s
+# NO-DD: Symbol table '.symtab'
+# NO-DD-NOT: unused_func
## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/main-unopt.s -o %t/main-unopt.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo-unopt.s -o %t/foo-unopt.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/main-unopt.o %t/main.o
-RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/main.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/foo-unopt.o %t/foo.o
-RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 main-unopt.s -o main-unopt.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo-unopt.s -o foo-unopt.o
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=main-unopt.o main.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 main.o
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=foo-unopt.o foo.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
## Create shared object to test handling of "inner" only dependencies.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/shared.s -o %t/shared.o
-RUN: ld.lld -shared -o %t/shared.so %t/shared.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 shared.s -o shared.o
+# RUN: ld.lld -shared -o shared.so shared.o
## Link simulated dynamic debugging objects.
-RUN: ld.lld -e main -o %t/out-dd %t/main.o %t/foo.o %t/shared.so
-RUN: llvm-readelf -Srsn %t/out-dd | FileCheck -DNSYM=21 --check-prefixes=DD,NO-GC %s
+# RUN: ld.lld -e main -o out main.o foo.o shared.so
+# RUN: llvm-readelf -Srsn out | FileCheck --check-prefixes=DD,NO-GC %s
## Link simulated dynamic debugging objects using `--gc-sections`.
## Note that `--gc-sections` is not yet supported for the "inner" unoptimized relocatable
## link.
-RUN: ld.lld -e main --gc-sections -o %t/out-dd-gc %t/main.o %t/foo.o %t/shared.so
-RUN: llvm-readelf -Srsn %t/out-dd-gc | FileCheck -DNSYM=19 --check-prefixes=DD,GC %s
+# RUN: ld.lld -e main --gc-sections -o out.gc main.o foo.o shared.so
+# RUN: llvm-readelf -Srsn out.gc | FileCheck --check-prefixes=DD,GC %s
## Check dynamic debugging related sections.
-DD: There are 20 section headers, starting at offset {{.*}}:
-DD: Name Type Address Off Size ES Flg Lk Inf Al
-DD-DAG: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 0 0 8
-DD-DAG: .note.llvm.dyndbg NOTE 0000000000000000 {{.*}} {{.*}} 00 0 0 4
+# DD-DAG: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 0 0 8
+# DD-DAG: .note.llvm.dyndbg NOTE 0000000000000000 {{.*}} {{.*}} 00 0 0 4
## Check GOT/PLT entries in "outer" ELF for shared symbols only referenced by "inner"
## unoptimized objects.
-DD: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
-DD: {{.*}} {{.*}} R_X86_64_GLOB_DAT 0000000000000000 shared_data_for_unopt
-DD: Relocation section '.rela.plt' at offset {{.*}} contains 1 entries:
-DD: {{.*}} {{.*}} R_X86_64_JUMP_SLOT 0000000000000000 shared_func_for_unopt
-
-NO-GC: Symbol table '.dynsym' contains 4 entries:
-GC: Symbol table '.dynsym' contains 3 entries:
-DD-DAG: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
-DD-DAG: 0000000000000000 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
-NO-GC-DAG: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
-
-DD: Symbol table '.symtab' contains [[NSYM]] entries:
-DD-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
-DD-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
-DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.main
-DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.main
-DD-DAG: {{.*}} 0 FUNC LOCAL DEFAULT [[#]] static_func
-DD-DAG: {{.*}} 0 OBJECT LOCAL DEFAULT [[#]] static_data
-DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.foo
-DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
-DD-DAG: {{.*}} 0 NOTYPE LOCAL HIDDEN [[#]] _DYNAMIC
-DD-DAG: {{.*}} 0 FUNC WEAK DEFAULT [[#]] comdat_func
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] func
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] main
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] foo
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_in_opt
-NO-GC-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] unused_func
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] used_by_unused_func
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
-DD-DAG: {{.*}} 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
-DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
-NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+# DD: Relocation section '.rela.dyn' at offset {{.*}} contains 1 entries:
+# DD: {{.*}} {{.*}} R_X86_64_GLOB_DAT 0000000000000000 shared_data_for_unopt
+# DD: Relocation section '.rela.plt' at offset {{.*}} contains 1 entries:
+# DD: {{.*}} {{.*}} R_X86_64_JUMP_SLOT 0000000000000000 shared_func_for_unopt
+
+# NO-GC: Symbol table '.dynsym' contains 4 entries:
+# GC: Symbol table '.dynsym' contains 3 entries:
+# DD-DAG: {{.*}} 0 OBJECT GLOBAL DEFAULT UND shared_data_for_unopt
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT UND shared_func_for_unopt
+
+# DD: Symbol table '.symtab' contains
+# DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.main
+# DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.main
+# DD-DAG: {{.*}} 0 FUNC LOCAL HIDDEN [[#]] static_func.dyndbg.foo
+# DD-DAG: {{.*}} 0 OBJECT LOCAL HIDDEN [[#]] static_data.dyndbg.foo
+# DD-DAG: {{.*}} 0 FUNC WEAK DEFAULT [[#]] comdat_func
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_in_opt
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] inlined_for_unopt_and_non_alloc
+# DD-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] used_by_unused_func
+# NO-GC-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] unused_func
+# NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
## The following symbols are only present when linking the relocatable link
## output of the simulated dynamic debugging objects.
-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS
+# REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
+# REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
+
+# DD: LLVM 0x00000004 Unknown note type: (0x00000004)
+# DD-NEXT: description data: 00 00 00 00
-DD: LLVM 0x00000004 Unknown note type: (0x00000004)
-DD-NEXT: description data: 00 00 00 00
+# GC-NOT: {{ }}unused_func
+# GC-NOT: undef_used_by_non_alloc
## Extract and check "inner" ELF for the default link.
-RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd.o %t/out-dd /dev/null
-RUN: llvm-readelf -Ssg %t/in-dd.o | FileCheck --check-prefix=IN %s
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.o out /dev/null
+# RUN: llvm-readelf -Ssg in.o | FileCheck --check-prefix=IN %s
## Extract and check "inner" ELF for the `--gc-sections` link.
-RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-dd-gc.o %t/out-dd-gc /dev/null
-RUN: llvm-readelf -Ssg %t/in-dd-gc.o | FileCheck --check-prefix=IN %s
-
-IN: There are 9 section headers, starting at offset {{.*}}:
-IN: Name Type Address Off Size ES Flg Lk Inf Al
-IN: .text PROGBITS 0000000000000000 {{.*}} {{.*}} 00 AX 0 0 4
-IN-NEXT: .rela.text RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
-IN-NEXT: .debug_xyz PROGBITS 0000000000000000 {{.*}} {{.*}} 00 0 0 1
-IN-NEXT: .rela.debug_xyz RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
-IN-NEXT: .note.GNU-stack PROGBITS 0000000000000000 {{.*}} {{.*}} 00 0 0 1
-IN-NEXT: .symtab SYMTAB 0000000000000000 {{.*}} {{.*}} 18 [[#]] [[#]] 8
-IN-NEXT: .shstrtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
-IN-NEXT: .strtab STRTAB 0000000000000000 {{.*}} {{.*}} 00 0 0 1
-
-IN: Symbol table '.symtab' contains 26 entries:
-IN-DAG: {{.*}} 0 SECTION LOCAL DEFAULT [[#]] .text
-IN-DAG: {{.*}} 0 SECTION LOCAL DEFAULT [[#]] .debug_xyz
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.static_func.dyndbg.main
-IN-DAG: {{.*}} 0 FUNC WEAK HIDDEN [[#]] __dyndbg.comdat_func
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.func
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.main
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.main
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND inlined_in_opt
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND comdat_func
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND inlined_for_unopt_and_non_alloc
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_data_for_unopt
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.main
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND foo
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND func
-IN-DAG: {{.*}} 0 FUNC GLOBAL DEFAULT [[#]] __dyndbg.inlined_in_opt
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.unused_func
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND used_by_unused_func
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.inlined_for_unopt_and_non_alloc
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.static_func.dyndbg.foo
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.foo
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.foo
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.foo
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_func_for_unopt
-IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.used_by_unused_func
-IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.gc.o out.gc /dev/null
+# RUN: llvm-readelf -Ssg in.gc.o | FileCheck --check-prefix=IN %s
+
+# IN: Name Type Address Off Size ES Flg Lk Inf Al
+# IN: .text PROGBITS 0000000000000000 {{.*}} {{.*}} 00 AX 0 0 4
+# IN-NEXT: .rela.text RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
+# IN-NEXT: .debug_xyz PROGBITS 0000000000000000 {{.*}} {{.*}} 00 0 0 1
+# IN-NEXT: .rela.debug_xyz RELA 0000000000000000 {{.*}} {{.*}} 18 I [[#]] [[#]] 8
+
+# IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.main
+# IN-DAG: {{.*}} 0 FUNC WEAK HIDDEN [[#]] __dyndbg.comdat_func
+# IN-DAG: {{.*}} 0 FUNC GLOBAL HIDDEN [[#]] __dyndbg.unused_func
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.main
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.main
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_func.dyndbg.foo
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND static_data.dyndbg.foo
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND comdat_func
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND used_by_unused_func
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_func_for_unopt
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND shared_data_for_unopt
+# IN-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
## Groups have been resolved.
-IN: There are no section groups in this file.
+# IN: There are no section groups in this file.
## Check disabling of incompatible ICF options.
-RUN: ld.lld -e main --icf=all -o %t/out-dd-icf-all %t/main.o %t/foo.o %t/shared.so 2>&1 | \
-RUN: FileCheck --check-prefix=ICF-WARN %s
-RUN: cmp %t/out-dd-icf-all %t/out-dd
-RUN: ld.lld -e main --icf=safe -o %t/out-dd-icf-safe %t/main.o %t/foo.o %t/shared.so 2>&1 | \
-RUN: FileCheck --check-prefix=ICF-WARN %s
-RUN: cmp %t/out-dd-icf-safe %t/out-dd
+# RUN: ld.lld -e main --icf=all -o out.icf main.o foo.o shared.so 2>&1 | \
+# RUN: FileCheck --check-prefix=ICF-WARN %s
+# RUN: cmp out.icf out
+# RUN: ld.lld -e main --icf=safe -o out.icf-safe main.o foo.o shared.so 2>&1 | \
+# RUN: FileCheck --check-prefix=ICF-WARN %s
+# RUN: cmp out.icf-safe out
-ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
+# ICF-WARN: ICF disabled because it is incompatible with dynamic debugging
## Check relocatable link of simulated dynamic debugging objects.
-RUN: ld.lld -r -o %t/rel.o %t/main.o %t/foo.o
-RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/rel-dd.o %t/rel.o /dev/null
-RUN: ld.lld -r -o %t/rel-unopt.o %t/main-unopt.o %t/foo-unopt.o
-RUN: cmp %t/rel-dd.o %t/rel-unopt.o
+# RUN: ld.lld -r -o rel.o main.o foo.o
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=rel-in.o rel.o /dev/null
+# RUN: ld.lld -r -o rel-unopt.o main-unopt.o foo-unopt.o
+# RUN: cmp rel-in.o rel-unopt.o
## Link the above relocatable link output with and without `--gc-sections`.
-RUN: ld.lld -e main -o %t/out-rel-dd %t/rel.o %t/shared.so
-RUN: llvm-readelf -Srsn %t/out-rel-dd | FileCheck -DNSYM=24 --check-prefixes=DD,NO-GC,REL %s
-RUN: ld.lld -e main --gc-sections -o %t/out-rel-dd-gc %t/rel.o %t/shared.so
-RUN: llvm-readelf -Srsn %t/out-rel-dd-gc | FileCheck -DNSYM=22 --check-prefixes=DD,GC,REL %s
+# RUN: ld.lld -e main -o out.rel rel.o shared.so
+# RUN: llvm-readelf -Srsn out.rel | FileCheck --check-prefixes=DD,NO-GC,REL %s
+# RUN: ld.lld -e main --gc-sections -o out.rel-gc rel.o shared.so
+# RUN: llvm-readelf -Srsn out.rel-gc | FileCheck --check-prefixes=DD,GC,REL %s
## Extract and check "inner" ELF for the above links.
-RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd.o %t/out-rel-dd /dev/null
-RUN: llvm-readelf -Ssg %t/in-rel-dd.o | FileCheck --check-prefix=IN %s
-RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=%t/in-rel-dd-gc.o %t/out-rel-dd-gc /dev/null
-RUN: llvm-readelf -Ssg %t/in-rel-dd-gc.o | FileCheck --check-prefix=IN %s
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel.o out.rel /dev/null
+# RUN: llvm-readelf -Ssg in.rel.o | FileCheck --check-prefix=IN %s
+# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel-gc.o out.rel-gc /dev/null
+# RUN: llvm-readelf -Ssg in.rel-gc.o | FileCheck --check-prefix=IN %s
#--- main.s
-# Optimized version of "main"
+## Optimized version of "main"
.local static_func
.type static_func, at function
@@ -242,7 +200,7 @@ static_data:
static_data.dyndbg.main = static_data
#--- main-unopt.s
-# Unoptimized version of "main"
+## Unoptimized version of "main"
.global __dyndbg.static_func.dyndbg.main
.hidden __dyndbg.static_func.dyndbg.main
@@ -266,7 +224,7 @@ __dyndbg.func:
call static_func.dyndbg.main
mov static_data.dyndbg.main,%rax
- # The following is only in the unoptimized due to inlining in the optimized.
+ ## The following is only in the unoptimized due to inlining in the optimized.
call inlined_in_opt
call comdat_func
call inlined_for_unopt_and_non_alloc
@@ -305,7 +263,7 @@ __dyndbg.inlined_for_unopt_and_non_alloc:
ret
#--- foo.s
-# Optimized version of "foo"
+## Optimized version of "foo"
.local static_func
.type static_func, at function
@@ -348,14 +306,7 @@ static_data:
static_data.dyndbg.foo = static_data
#--- foo-unopt.s
-# Unoptimized version of "foo"
-
-.global __dyndbg.static_func.dyndbg.foo
-.hidden __dyndbg.static_func.dyndbg.foo
-.type __dyndbg.static_func.dyndbg.foo, at function
-.section .text.__dyndbg.static_func.dyndbg.foo,"ax", at progbits
-__dyndbg.static_func.dyndbg.foo:
- ret
+## Unoptimized version of "foo"
.weak __dyndbg.comdat_func
.hidden __dyndbg.comdat_func
@@ -372,10 +323,9 @@ __dyndbg.foo:
call static_func.dyndbg.foo
mov static_data.dyndbg.foo,%rax
- # The following is only in the unoptimized due to inlining in the optimized.
+ ## The following is only in the unoptimized due to inlining in the optimized.
call shared_func_for_unopt
call comdat_func
-
ret
.global __dyndbg.used_by_unused_func
diff --git a/lld/test/ELF/invalid-dynamic-debug.test b/lld/test/ELF/invalid-dynamic-debug.test
index 42b02e74b65929..41039dd516afa1 100644
--- a/lld/test/ELF/invalid-dynamic-debug.test
+++ b/lld/test/ELF/invalid-dynamic-debug.test
@@ -1,98 +1,98 @@
-REQUIRES: x86
+# REQUIRES: x86
## This test simulates the input from dynamic debugging objects to test ELF LLD's handling
## of various invalid situations.
-RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: rm -rf %t && split-file %s %t && cd %t
## Create the "outer" optimized objects.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main.s -o main.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo.s -o foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main.s -o main.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo.s -o foo.o
## Create and embed the "inner" unoptimized objects to simulate dynamic debugging objects.
## Note that the embedded `.debug_llvm_dyndbg` section should be aligned to 8.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main-unopt.s -o main-unopt.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo-unopt.s -o foo-unopt.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=main-unopt.o main.o
-RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 main.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=foo-unopt.o foo.o
-RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux main-unopt.s -o main-unopt.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux foo-unopt.s -o foo-unopt.o
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=main-unopt.o main.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 main.o
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=foo-unopt.o foo.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 foo.o
## Create object without dynamic debugging.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux no-dyndbg.s -o no-dyndbg.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux no-dyndbg.s -o no-dyndbg.o
## Default link.
-RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-
-CHECK: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-CHECK-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
-CHECK-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
-CHECK-EMPTY:
-CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
-CHECK-EMPTY:
-CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
-CHECK-EMPTY:
-CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
-CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
-CHECK-EMPTY:
+# RUN: not ld.lld -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+# CHECK: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+# CHECK-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
+# CHECK-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
+# CHECK-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+# CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
+# CHECK-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+# CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
+# CHECK-NEXT: error: undefined symbol: undef_weak_ver at v1
+# CHECK-NEXT: >>> referenced by main.o:(.debug_llvm_dyndbg+0x0)
+# CHECK-EMPTY:
## Default link with reverse input file order.
-RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-
-REVERSE: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
-REVERSE-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
-REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
-REVERSE-EMPTY:
-REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
-REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
-REVERSE-EMPTY:
-REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
-REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
-REVERSE-EMPTY:
-REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
-REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
-REVERSE-EMPTY:
+# RUN: not ld.lld -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=error -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+# REVERSE: [[ERR_OR_WARN]]: undefined symbol: undef_no_dyndbg_inner
+# REVERSE-NEXT: >>> referenced by no-dyndbg.o:(.text.bar+0x1)
+# REVERSE-NEXT: >>> referenced by <internal>:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
+# REVERSE-NEXT: [[ERR_OR_NOIEXEC_WARN]]: undefined hidden symbol: undef_hidden
+# REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
+# REVERSE-NEXT: [[ERR_OR_WARN]]: undefined symbol: undef_inner
+# REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
+# REVERSE-NEXT: error: undefined symbol: undef_weak_ver at v1
+# REVERSE-NEXT: >>> referenced by foo.o:(.debug_llvm_dyndbg+0x0)
+# REVERSE-EMPTY:
## Links with `--warn-unresolved-symbols`.
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+# RUN: not ld.lld --warn-unresolved-symbols -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+# RUN: not ld.lld --warn-unresolved-symbols -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=error --check-prefix=REVERSE \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--noinhibit-exec`.
-RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+# RUN: not ld.lld --noinhibit-exec -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+# RUN: not ld.lld --noinhibit-exec -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DERR_OR_NOIEXEC_WARN=warning --check-prefix=REVERSE \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
## Links with `--unresolved-symbols=ignore-all`.
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
-RUN: FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
-RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
-
-ERROR: error: undefined hidden symbol: undef_hidden
-ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
-ERROR-EMPTY:
-ERROR-NEXT: error: undefined symbol: undef_weak_ver at v1
-ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+# RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null main.o foo.o no-dyndbg.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DFILE="main.o" --check-prefix=ERROR \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+# RUN: not ld.lld --unresolved-symbols=ignore-all -o /dev/null no-dyndbg.o foo.o main.o 2>&1 | \
+# RUN: FileCheck -DERR_OR_WARN=warning -DFILE="foo.o" --check-prefix=ERROR \
+# RUN: --implicit-check-not=error: --implicit-check-not=warning: %s
+
+# ERROR: error: undefined hidden symbol: undef_hidden
+# ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
+# ERROR-EMPTY:
+# ERROR-NEXT: error: undefined symbol: undef_weak_ver at v1
+# ERROR-NEXT: >>> referenced by [[FILE]]:(.debug_llvm_dyndbg+0x0)
#--- main.s
-# Optimized version of "main"
+## Optimized version of "main"
.global main
.type main, at function
@@ -101,7 +101,7 @@ main:
ret
#--- main-unopt.s
-# Unoptimized version of "main"
+## Unoptimized version of "main"
.weak undef_weak
.weak undef_weak_ver
@@ -130,7 +130,7 @@ foo:
ret
#--- foo-unopt.s
-# Unoptimized version of "foo"
+## Unoptimized version of "foo"
.weak undef_weak
.weak undef_weak_ver
@@ -151,7 +151,7 @@ __dyndbg.foo:
ret
#--- no-dyndbg.s
-# No dynamic debugging
+## No dynamic debugging
.global bar
.type bar, at function
diff --git a/lld/test/ELF/trace-symbols-dynamic-debug.test b/lld/test/ELF/trace-symbols-dynamic-debug.test
index b0d20bf904adcc..74e6d2f04ab078 100644
--- a/lld/test/ELF/trace-symbols-dynamic-debug.test
+++ b/lld/test/ELF/trace-symbols-dynamic-debug.test
@@ -1,21 +1,21 @@
-REQUIRES: x86
-
+# REQUIRES: x86
## Test dynamic debugging `-trace-symbol=<symbol>`
-RUN: rm -rf %t && split-file %s %t
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/foo.s -o %t/foo.o
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 foo.s -o foo.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 bar.s -o bar.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 bar-unopt.s -o bar-unopt.o
## Create dynamic debugging style object.
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar.s -o %t/bar.o
-RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %t/bar-unopt.s -o %t/bar-unopt.o
-RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=%t/bar-unopt.o %t/bar.o
-RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
-RUN: --set-section-alignment=.debug_llvm_dyndbg=8 %t/bar.o
+# RUN: llvm-objcopy --add-section=.debug_llvm_dyndbg=bar-unopt.o bar.o
+# RUN: llvm-objcopy --set-section-type=.debug_llvm_dyndbg=0x6fff4c10 \
+# RUN: --set-section-alignment=.debug_llvm_dyndbg=8 bar.o
+
+# RUN: ld.lld -trace-symbol=foo -shared foo.o bar.o | FileCheck %s
-# RUN: ld.lld -trace-symbol=foo -shared %t/foo.o %t/bar.o -o /dev/null | FileCheck %s
-# CHECK: foo.o: definition of foo
-# CHECK: bar.o: reference to foo
-# CHECK: bar.o: dynamic debugging reference to foo
+# CHECK: foo.o: definition of foo
+# CHECK-NEXT: bar.o: reference to foo
+# CHECK-NEXT: bar.o: dynamic debugging reference to foo
#--- foo.s
.globl foo
>From 1ccb5a5222f815ba2adc9f07a2d4d47626bb444c Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 8 Sep 2026 17:39:03 +0100
Subject: [PATCH 10/15] Remove unneeded change to `SHT_GROUP` handling
The use of the option `--force-group-allocation` for the "inner"
relocatable link is sufficient to achieve the desired output.
---
lld/ELF/InputFiles.cpp | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 069ecc973f9616..e82df161a53de7 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -839,9 +839,7 @@ void ObjFile<ELFT>::initializeSections(bool ignoreComdats,
}
switch (type) {
case SHT_GROUP: {
- // Discard groups for non-relocatable links and for embedded unoptimized
- // dynamic debugging relocatable links.
- if (!ctx.arg.relocatable || ctx.dynDbgRelocatable)
+ if (!ctx.arg.relocatable)
sections[i] = &InputSection::discarded;
// Use the verdict parse() recorded for this group instead of repeating
// the signature hashing and comdatGroups lookup.
>From 380c10792122e635a8809e1ef02a103051be84b6 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 8 Sep 2026 17:39:03 +0100
Subject: [PATCH 11/15] Do not emit `.note.llvm.dyndbg` for `-r` relocatable
link
Add checking of relocatable output to test.
---
lld/ELF/SyntheticSections.cpp | 6 ++++--
lld/test/ELF/dynamic-debug.test | 17 +++++++++++++----
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index fef8dd30eb0d21..510d13251978d0 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4677,8 +4677,10 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
if (ctx.dynDbgOutput) {
ctx.in.dynDbg = std::make_unique<DynamicDebugSection>(ctx);
add(*ctx.in.dynDbg);
- ctx.in.dynDbgNote = std::make_unique<DynamicDebugNote>(ctx);
- add(*ctx.in.dynDbgNote);
+ if (!ctx.arg.relocatable) {
+ ctx.in.dynDbgNote = std::make_unique<DynamicDebugNote>(ctx);
+ add(*ctx.in.dynDbgNote);
+ }
}
}
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 111c2f6f5208c2..234bc9e6436389 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -69,8 +69,8 @@
# NO-GC-DAG: {{.*}} 0 NOTYPE GLOBAL DEFAULT UND undef_used_by_non_alloc
## The following symbols are only present when linking the relocatable link
## output of the simulated dynamic debugging objects.
-# REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
-# REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
+# DD-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS main.o
+# DD-REL-DAG: {{.*}} 0 FILE LOCAL DEFAULT ABS foo.o
# DD: LLVM 0x00000004 Unknown note type: (0x00000004)
# DD-NEXT: description data: 00 00 00 00
@@ -119,15 +119,24 @@
## Check relocatable link of simulated dynamic debugging objects.
# RUN: ld.lld -r -o rel.o main.o foo.o
+# RUN: llvm-readelf -Sg rel.o | FileCheck --check-prefix=REL %s
+
+# REL-NOT: .note.llvm.dyndbg
+# REL: COMDAT group section [{{.*}}] `.group' [comdat_func] contains 1 sections:
+# REL-NEXT: Name
+# REL-NEXT: .text.comdat_func
+
+## Extract "inner" ELF from the above relocatable link and compare to a
+## relocatable link of the "inner" unoptimized objects.
# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=rel-in.o rel.o /dev/null
# RUN: ld.lld -r -o rel-unopt.o main-unopt.o foo-unopt.o
# RUN: cmp rel-in.o rel-unopt.o
## Link the above relocatable link output with and without `--gc-sections`.
# RUN: ld.lld -e main -o out.rel rel.o shared.so
-# RUN: llvm-readelf -Srsn out.rel | FileCheck --check-prefixes=DD,NO-GC,REL %s
+# RUN: llvm-readelf -Srsn out.rel | FileCheck --check-prefixes=DD,NO-GC,DD-REL %s
# RUN: ld.lld -e main --gc-sections -o out.rel-gc rel.o shared.so
-# RUN: llvm-readelf -Srsn out.rel-gc | FileCheck --check-prefixes=DD,GC,REL %s
+# RUN: llvm-readelf -Srsn out.rel-gc | FileCheck --check-prefixes=DD,GC,DD-REL %s
## Extract and check "inner" ELF for the above links.
# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel.o out.rel /dev/null
>From e32c96097e82e48d11fcbfb7cdbf0ad6260e1990 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 8 Sep 2026 17:39:03 +0100
Subject: [PATCH 12/15] Fix code for compressed debug sections and add testing
---
lld/ELF/Driver.cpp | 2 +-
lld/ELF/InputFiles.cpp | 2 +-
lld/test/ELF/dynamic-debug.test | 13 +++++++++++++
3 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 84449eb9beb41c..887a992d2674f2 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -3264,7 +3264,7 @@ template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
for (auto *file : ctx.objectFiles) {
auto *obj = cast<ObjFile<ELFT>>(file);
if (obj->dynDbgSec) {
- MemoryBufferRef mb(toStringRef(obj->dynDbgSec->content()),
+ MemoryBufferRef mb(toStringRef(obj->dynDbgSec->contentMaybeDecompress()),
obj->mb.getBufferIdentifier());
dctx.driver.addFile(createObjFile(dctx, mb));
}
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index e82df161a53de7..f24479d645edef 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1268,7 +1268,7 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
// those reached by an inner relocation against a SHT_PROGBITS SHF_ALLOC
// section with `isDynDbgRef`; the rest are only needed by debug sections.
template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
- MemoryBufferRef dbgMb(toStringRef(dynDbgSec->content()),
+ MemoryBufferRef dbgMb(toStringRef(dynDbgSec->contentMaybeDecompress()),
mb.getBufferIdentifier());
std::unique_ptr<ELFFileBase> efb = createObjFile(ctx, dbgMb);
// Compare ekind (note ObjFile<ELFT>::classof only tests InputFile::kind()).
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 234bc9e6436389..f34323cf134876 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -107,6 +107,19 @@
## Groups have been resolved.
# IN: There are no section groups in this file.
+## Compress debug sections of the simulated dynamic debugging objects with zstd compression.
+# RUN: llvm-objcopy --compress-debug-sections=zstd main.o main-zstd.o
+# RUN: llvm-objcopy --compress-debug-sections=zstd foo.o foo-zstd.o
+# RUN: llvm-readelf -S main-zstd.o | FileCheck --check-prefix=ZSTD %s
+# RUN: llvm-readelf -S foo-zstd.o | FileCheck --check-prefix=ZSTD %s
+
+# ZSTD: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 C 0 0 8
+
+## Link simulated dynamic debugging objects with zstd compressed debug sections and
+## check the output.
+# RUN: ld.lld -e main -o out.zstd main-zstd.o foo-zstd.o shared.so
+# RUN: llvm-readelf -Srsn out.zstd | FileCheck --check-prefixes=DD,NO-GC %s
+
## Check disabling of incompatible ICF options.
# RUN: ld.lld -e main --icf=all -o out.icf main.o foo.o shared.so 2>&1 | \
# RUN: FileCheck --check-prefix=ICF-WARN %s
>From 302bf65eeef4ff4057ae04044f473869f8b17b2d Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Tue, 8 Sep 2026 19:38:39 +0100
Subject: [PATCH 13/15] Make the zstd compressed debug sections testing
conditional
---
lld/test/ELF/dynamic-debug.test | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index f34323cf134876..14835eab8f1c07 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -108,17 +108,17 @@
# IN: There are no section groups in this file.
## Compress debug sections of the simulated dynamic debugging objects with zstd compression.
-# RUN: llvm-objcopy --compress-debug-sections=zstd main.o main-zstd.o
-# RUN: llvm-objcopy --compress-debug-sections=zstd foo.o foo-zstd.o
-# RUN: llvm-readelf -S main-zstd.o | FileCheck --check-prefix=ZSTD %s
-# RUN: llvm-readelf -S foo-zstd.o | FileCheck --check-prefix=ZSTD %s
+# RUN: %if zstd %{ llvm-objcopy --compress-debug-sections=zstd main.o main-zstd.o %}
+# RUN: %if zstd %{ llvm-objcopy --compress-debug-sections=zstd foo.o foo-zstd.o %}
+# RUN: %if zstd %{ llvm-readelf -S main-zstd.o | FileCheck --check-prefix=ZSTD %s %}
+# RUN: %if zstd %{ llvm-readelf -S foo-zstd.o | FileCheck --check-prefix=ZSTD %s %}
# ZSTD: .debug_llvm_dyndbg LLVM_DYNDBG_ELF 0000000000000000 {{.*}} {{.*}} 00 C 0 0 8
## Link simulated dynamic debugging objects with zstd compressed debug sections and
## check the output.
-# RUN: ld.lld -e main -o out.zstd main-zstd.o foo-zstd.o shared.so
-# RUN: llvm-readelf -Srsn out.zstd | FileCheck --check-prefixes=DD,NO-GC %s
+# RUN: %if zstd %{ ld.lld -e main -o out.zstd main-zstd.o foo-zstd.o shared.so %}
+# RUN: %if zstd %{ llvm-readelf -Srsn out.zstd | FileCheck --check-prefixes=DD,NO-GC %s %}
## Check disabling of incompatible ICF options.
# RUN: ld.lld -e main --icf=all -o out.icf main.o foo.o shared.so 2>&1 | \
>From c7a22d6329bd8550153e9bfa59f7a80b30983ca8 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 10 Sep 2026 17:13:46 +0100
Subject: [PATCH 14/15] Disable reproduce for the "inner" relocatable link
---
lld/ELF/Driver.cpp | 2 +-
lld/test/ELF/dynamic-debug.test | 18 ++++++++++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 887a992d2674f2..788e58a9b93d3f 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -672,7 +672,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (args.hasArg(OPT_v) || args.hasArg(OPT_version))
Msg(ctx) << getLLDVersion() << " (compatible with GNU linkers)";
- if (const char *path = getReproduceOption(args)) {
+ if (const char *path = getReproduceOption(args); !ctx.inDynDbgLink && path) {
// Note that --reproduce is a debug option so you can ignore it
// if you are trying to understand the whole picture of the code.
Expected<std::unique_ptr<TarWriter>> errOrWriter =
diff --git a/lld/test/ELF/dynamic-debug.test b/lld/test/ELF/dynamic-debug.test
index 14835eab8f1c07..3f7dd8e96edf3a 100644
--- a/lld/test/ELF/dynamic-debug.test
+++ b/lld/test/ELF/dynamic-debug.test
@@ -157,6 +157,24 @@
# RUN: llvm-objcopy --dump-section=.debug_llvm_dyndbg=in.rel-gc.o out.rel-gc /dev/null
# RUN: llvm-readelf -Ssg in.rel-gc.o | FileCheck --check-prefix=IN %s
+## Check link with environment variable LLD_REPRODUCE set.
+# RUN: env LLD_REPRODUCE=repro.tar ld.lld -e main -o out.repro main.o foo.o shared.so
+# RUN: tar tf repro.tar | FileCheck --check-prefix=REPRO %s
+# RUN: tar xOf repro.tar repro/response.txt | FileCheck --check-prefix=RSP %s
+
+# REPRO: repro/response.txt
+# REPRO-NEXT: repro/version.txt
+# REPRO-NEXT: repro/{{.*}}/main.o
+# REPRO-NEXT: repro/{{.*}}/foo.o
+# REPRO-NEXT: repro/{{.*}}/shared.so
+
+# RSP: --chroot .
+# RSP-NEXT: --entry main
+# RSP-NEXT: -o out.repro
+# RSP-NEXT: {{.*}}/main.o
+# RSP-NEXT: {{.*}}/foo.o
+# RSP-NEXT: {{.*}}/shared.so
+
#--- main.s
## Optimized version of "main"
>From d6d37e5cec379a186e23291cedf986f5422060a5 Mon Sep 17 00:00:00 2001
From: Andrew Ng <andrew.ng at sony.com>
Date: Thu, 10 Sep 2026 17:36:36 +0100
Subject: [PATCH 15/15] Remove `SHT_PROGBITS` requirement for dynamic debug
references
---
lld/ELF/InputFiles.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index f24479d645edef..f1ae39b534c6b0 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -1265,8 +1265,8 @@ void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
// Add the undefined symbols of the embedded unoptimized dynamic debugging
// object so that the outer link resolves the inner link's dependencies. Tag
-// those reached by an inner relocation against a SHT_PROGBITS SHF_ALLOC
-// section with `isDynDbgRef`; the rest are only needed by debug sections.
+// those reached by an inner relocation against a SHF_ALLOC section with
+// `isDynDbgRef`; the rest are only needed by debug sections.
template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
MemoryBufferRef dbgMb(toStringRef(dynDbgSec->contentMaybeDecompress()),
mb.getBufferIdentifier());
@@ -1291,7 +1291,7 @@ template <class ELFT> void ObjFile<ELFT>::initDynDbgSymbols() {
if (!isStaticRelSecType(sh.sh_type))
continue;
const Elf_Shdr &target = *CHECK2(obj.getSection(sh.sh_info), &dbgObj);
- if (target.sh_type != SHT_PROGBITS || !(target.sh_flags & SHF_ALLOC))
+ if (!(target.sh_flags & SHF_ALLOC))
continue;
if (sh.sh_type == SHT_CREL) {
auto [rels, relas] = CHECK2(obj.crels(sh), &dbgObj);
More information about the llvm-commits
mailing list